RAgents
Guide: Distributed work

Guide

Distributed work

Run the host locally or remotely, keep tools beside the project, and move runs between servers.

On this page

Work without a checkout

Using RAgents does not require a repository. pnpm build:package creates the npm package @schlenkr/ragents from this checkout in dist/ragents; with --pack, it also creates dist/schlenkr-ragents-<version>.tgz. The package contains the server, engine, executor, all plugins, and the scripts behind its subcommands. From the documentation it includes only LICENSE and a separate English README.md for the npm listing, sourced from scripts/package/README.md. It carries the finished web interface and every built-in plugin as a bundle, so nothing is built on the user's machine. Its package.json records the source commit as ragents.hostVersion. Install it like any other npm package:

npm install -g @schlenkr/ragents
RAGENTS_TOKEN=<token> ragents connect https://ragents.example.com

For an npx invocation without a global install, see Install from npm.

Node.js 22.19 or newer is required, without Git, pnpm, or a source build. The subcommands are:

  • ragents run <folder> "<task>", send, journal, and stop let an agent work on a project.
  • ragents connect <server-url> fetches a client profile with its plugin bundles, provisions tools, and starts a server. It supports --port <n>, --clean, and --no-start, like pnpm connect.
  • ragents start <profile|path> starts an included profile (core, developer, or showcase), a custom ragents.config.<profile>.ts anywhere on disk, or a previously fetched version.
  • ragents provision [<profile>|--workspace] installs only the required tools.
  • ragents workspace-client <server-url> [folders ...] registers this machine as a workspace.
  • ragents plugin build <folder...> builds plugin sources into bundles, with type checks; see Build and ship a plugin.
  • ragents --version (or -v) prints the version of the package or standalone installation.

The package runs like the checkout, with the same files in the same places and TypeScript loaded at runtime through tsx. On first use, the command creates the node_modules/@ragents/* links that pnpm supplies in a checkout. This is idempotent and requires no elevated privileges.

Custom profiles and plugins

A developer will usually keep a custom profile and plugins somewhere on disk. ragents start <path> starts it from the package, while ragents run --profile <path> runs it without an interface for an agent. Both resolve the path in the same way:

ragents start ~/projects/mine/ragents.config.mine.ts
ragents run ~/projects/mine "Build this" --profile ~/projects/mine/ragents.config.mine.ts

The file must be named ragents.config.<profile>.ts; that name becomes the profile name. Startup provisions the plugin tools and then starts the server. Nothing is built: the package carries the finished web interface, which is the same for every profile, and the server loads the web halves of the profile's plugins from their bundles at runtime.

The server loads plugins only as bundles. The package carries its built-in plugins as bundles under bundles/; a custom plugin is built first with ragents plugin build <source-folder...>, which writes ./dist/plugins/<custom-plugin-id> by default. The package brings everything this build needs, including the type checks against the host API. The profile names that bundle by a path relative to the profile file, such as "./dist/plugins/<custom-plugin-id>"; a source folder in the profile stops the start and names the build command. A bundle imports from the host only the modules of the host API list, resolved by apps/server/src/host-resolution-hooks.mjs, and must be rebuilt when the host API changes. Its web/ half is served under /plugins/<id>/web/ and loaded by the browser; its Tailwind classes join the one stylesheet the server compiles at startup. The build tool bundles libraries such as lucide-react from the host, so the package also includes the dependencies declared by apps/web/package.json. The host does not check whether such a bundle still matches its sources; rebuild it before starting.

An external plugin project can use a host symlink to access host tools. With a package installation, that link points to the package instead of a checkout. Set RAGENTS_HOST to the appropriate location:

export RAGENTS_HOST="$(npm root -g)/@schlenkr/ragents"

A script in the external repository can create the host symlink from that value. If that script identifies a checkout by pnpm-workspace.yaml, it should test for something the package shares instead, such as apps/server/src/main.ts beside package.json. Start with ragents start <path-to-profile>; the symlink is used only by scripts and tsconfig files in the external repository.

The VS Code extension starts the same profile from the same package. Set ragents.hostPath to the package directory (<npm-prefix>/lib/node_modules/@schlenkr/ragents) rather than a checkout. If the setting is empty and the extension is not running from a checkout, it fetches the package itself as described under Run panel and VS Code extension.

Connect to a server

A developer can run RAgents locally with the profile and models of a central server. This requires a local host with the same host API as that server: either the @schlenkr/ragents package, which needs only Node, or this checkout with Git, Node, pnpm, pnpm install, pnpm build:agent, pnpm build:plugins, and pnpm build:web. It also requires a personal token for a user in the server profile with profile.fetch and models.use permissions:

RAGENTS_TOKEN=<token> ragents connect https://ragents.example.com
RAGENTS_TOKEN=<token> ragents connect https://ragents.example.com --port 4720 --clean
RAGENTS_TOKEN=<token> ragents connect https://ragents.example.com --no-start

In a checkout, the same command is pnpm connect <server-url> and uses the same implementation.

connect fetches the client-profile description and checks the local host before it downloads anything. The host must offer the host API number the server's bundles were built against, and it must have a built-in bundle for every plugin the profile names by ID. The same commit is not required. On a mismatch, connect stops and names the fix: npm install -g @schlenkr/ragents@<version> for the package, where the server supplies the version, or the server's commit plus pnpm build:plugins and pnpm build:web in a checkout. It then downloads the archive, which holds only the profile file and the finished bundles it names by path, verifies its SHA-256, stores the version under ~/.local/share/ragents/remote/<host>/<profile>/profiles/<version>/, and starts the server with that profile. Data goes to ~/.local/share/ragents/remote/<host>/<profile>/data/, and the web interface is the local host's own; nothing is built or installed on the developer machine. Existing versions are not downloaded again. --clean removes older ones, --no-start stops after fetching and reports the profile and data paths, and --port <n> overrides the profile port. ragents start <profile> can restart an included profile or the latest fetched version without contacting the server. Personal env(...) values from the client profile, such as the relay token, must exist in the shell. Between download and startup, connect provisions language servers and the browser; a prerequisite it cannot install, such as dotnet or a separate Chrome, stops with instructions.

The server needs ragents.profile-distribution with CLIENT_PROFILE_FILE, plus ragents.model-relay with MODEL_ALIASES in its host section for models. Plugins the client profile names by path must be bundles built with ragents plugin build; the server checks them at startup. Users receive token: env("...") and the two permissions. Relay responses and all client-profile content, including prompts, skills, run scripts, bundles, and configuration, are present on the developer machine. Only the provider, actual model, and key remain secret. The relay replaces model with the alias and removes provider, so model context, journal, catalog, and interface expose only the alias.

Common first-run pitfalls

  • Two environment variables, often with the same value, are needed: connect reads RAGENTS_TOKEN, while the name used by RELAY_TOKEN: env("...") must also be set.
  • COMPACTION_PROVIDER: "relay" with an empty COMPACTION_MODEL can start without a text model at reasoning level off; automatic titles stay disabled and Settings explains why. An explicitly named alias must be such a model or startup fails.
  • The relay must be reachable when the local server starts because it fetches the alias catalog once. Alias changes require restarting the local server.
  • Revoking access affects the next model request immediately, not the running server process. The run then ends as failed and records the relay response in the journal.
  • Rejected requests appear in the relay server's logs/server.log, not relay.log. The latter records only forwarded calls with user, alias, target, status, and token count.
  • The archive carries no web interface. After updating the host package, a fetched version starts with the new web interface as long as the host API stays the same. Otherwise the start refuses the fetched bundles, and connect names the version to install.

Run a CLI workstation

ragents workspace-client <server-url> [folders ...] registers this machine with the same tools and executor as the VS Code extension. No folder means the current directory; --id and --label set its stable identity and display name. The npm installation includes ripgrep on every supported platform and the curated Bash on Windows. Plugin tools are provisioned before registration.

Set RAGENTS_TOKEN for a personal or existing session token. For automatic sign-in and renewal, configure RAGENTS_USER and RAGENTS_PASSWORD in the process environment. Neither is a command-line option. Invalid or absent credentials when sign-in is required produce a visible failure and a nonzero exit. A temporary connection loss reconnects and registers the workstation again.

Closing the terminal or SSH session, closing stdin, or ending the owning process unregisters the workstation and stops its children. SIGINT, SIGTERM, and SIGHUP also stop it. For an intentional background service, pass --detached and let a service manager own it, or run:

nohup ragents workspace-client https://ragents.example.com /home/user/project --detached >workspace.log 2>&1 </dev/null &

The flag ignores terminal closure, parent loss, and SIGHUP; it does not daemonize. SIGINT and SIGTERM still unregister and shut down. Use the service manager's stop command to end a service.

Transfer a run

A run can move from one server to another and continue there, for example from a notebook to an always-on machine or from a central server to a developer for local inspection.

RAGENTS_TOKEN=<token> pnpm run-transfer http://localhost:4723 http://server.example:4724 <runId>
RAGENTS_TOKEN=<token> pnpm run-transfer <source> <target> <runId> --workspace /path/to/project

The script exports the run from the source with ragents.runs.export and imports it on the target with ragents.runs.import. If each side uses a different token, set RAGENTS_SOURCE_TOKEN and RAGENTS_TARGET_TOKEN. The transfer includes the journal and payloads, which also hold the model contexts of the run's agents, the stored contents the run refers to (attachments and the media of the model contexts), actor programs, and all plugin storage for the run, including ragents.documents files and the new folder of a run that works on the server. A folder on a workstation stays there, and the run keeps its binding to that workstation. Running processes, language servers, and browsers are not transferred; they are recreated on the target when next used.

The transfer enforces these prerequisites:

  • Both servers use the same host version and workspace executor. There is no override.
  • The run is stopped, with no active turn or waiting input.
  • Its ID is unused on the target. An existing run, folder, or archive entry rejects the import.
  • A run bound to a source project folder needs --workspace <path> pointing to an existing target folder, given as an absolute path on the target server. A workspace binding is retained; that workspace reconnects to the target with the same ID and as the run owner, or anonymously for an ownerless run.
  • The archive must stay below 16 MiB because it travels as Base64 through the message layer. A workspace containing node_modules will exceed this; an existing folder on the server usually will not.
  • Symbolic links travel only when they are relative and stay inside the run's storage. Links under node_modules that point elsewhere, such as a package manager's absolute links, are left out and come back with the next install in the workspace. Any other link pointing outside stops the export with run-transfer-link and names the links; replace them with files or relative links.

Export copies the run. It remains on the source and must be deleted there explicitly after a real move, otherwise two journals with the same ID diverge. On the target, the run appears stopped and continues with the next message. Its model context still contains absolute source paths; reusing one fails at the workspace boundary, while a relative path reaches the target. After transfer, inspect the journal with pnpm driver journal <runId> and plugin storage under ${DATA_DIR}/sessions/<runId>/plugins/.

Work on Windows

The local host and workspace also run on Windows. Requirements are:

  • The VS Code extension for Windows brings its own bash. The Marketplace delivers a win32-x64 or win32-arm64 build that contains a slim bash with the GNU tools (coreutils, grep, sed, awk, find, diff, patch, tar, unzip, cygpath and more), taken from a fixed Git for Windows release, plus rg.exe (ripgrep) from a fixed ripgrep release. The bash tool uses only this bash, for the workspace and for the local host; an installed Git Bash or a bash.exe on PATH is never used. PowerShell and cmd.exe are not used either.
  • Code search runs through rg, which skips node_modules, bin, obj and everything else .gitignore excludes. The extension builds for macOS and Linux carry rg as well and put it at the front of the bash tool's PATH, on the workstation and for the local host. The system prompt tells the model to search with rg only when the machine running bash has one; otherwise it tells the model to exclude dependency and build folders from grep -r.
  • Git is your own git.exe on PATH, for example from Git for Windows. The bundled bash contains no git, so your login works as usual: Git Credential Manager, ~/.gitconfig and ~/.ssh. On your own machine the bash inherits your whole environment, except the variables of VS Code itself and BASH_ENV/ENV.
  • CLI workstation (ragents workspace-client) carries the same Bash and ripgrep as the extension. npm installs only the optional tools package for this machine; keep optional dependencies enabled. A checkout uses the extension build from pnpm bundle:rg and pnpm bundle:bash. A standalone server (ragents start) still takes RAGENTS_BASH and RAGENTS_RG from its environment; these variables do not override workstation bundles.
  • Node.js is enough with @schlenkr/ragents. A checkout additionally needs pnpm, and pnpm install, pnpm build:agent, and scripts/start.sh need a bash of your own, such as Git Bash.
  • The .NET SDK is required when the profile includes Roslyn or FSAC. Provisioning downloads the language servers; the TypeScript server comes from the host directory.
  • A server on Windows has no process sandbox. Its profile file must switch it off explicitly with PROCESS_SANDBOX: "off" in the ragents.workspace section, otherwise startup fails. A Windows workstation connected to a server needs nothing, because the sandbox applies only to the server.

The data directory is %LOCALAPPDATA%\ragents\<profile> and server-provided profiles use %LOCALAPPDATA%\ragents\remote\<host>\<profile>\. DATA_DIR overrides this. Startup fails when LOCALAPPDATA is absent. Unix permissions 0700 and 0711 do not apply on Windows, where isolation per run depends on the user account.

Windows has no process group for command termination, and the bundled MSYS bash does not hang its children into the Windows process tree, so taskkill /T on the bash alone misses them. Every bash call therefore carries a marker in its environment; on timeout, stop, and after the call, a short helper bash lists the MSYS process groups of that call and RAgents ends them and their native trees with taskkill /T /F. A process that survives this is an error. This is forceful and has no grace period. There is also no process table: for runs using a Windows workspace, the process rail explains this limitation. Stopping a run still terminates Bash process trees, while a deliberately detached service continues. Workspace tools do not depend on the process rail.

Windows support was exercised on a physical Windows 11 machine on 29.09.2026: the bundled bash and rg, the default timeout, stop, and the cleanup of background jobs. Beyond that it is covered by unit tests that simulate the platform. A first real run should verify pnpm connect, read, edit, bash output and cancellation, diagnostics, and a workspace through pnpm workspace-client, and with the bundled bash git fetch and git push over HTTPS with Git Credential Manager and over SSH.

Server process sandbox

What a run starts on the server (Bash, commands, language servers, TypeScript snippets, actor programs) runs in a process sandbox: it reads and writes only the folders of its run, sees neither other runs nor the home of the server account, and can reach public web domains by default. Rules and limits are in plugins.md under "Server process sandbox". It does not apply on a workstation; there the run works with the developer's Bash and credentials. Only what such a run starts on the server, such as a Bash in @actors, runs inside it.

Prerequisites that startup checks:

  • macOS: nothing extra, sandbox-exec is part of the system.
  • Linux: bubblewrap, socat, and ripgrep (Debian and Ubuntu: apt-get install bubblewrap socat ripgrep), plus user namespaces. On Ubuntu 24.04 and later this requires sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 or an AppArmor profile. If the server runs as root, it needs CAP_SETFCAP; it is better to run it under its own account.
  • Linux in a container: Docker's default profile forbids the namespaces. Startup has been verified with --security-opt seccomp=unconfined --security-opt apparmor=unconfined --security-opt systempaths=unconfined (in Compose security_opt) and a non-root user in the container; --privileged works too, but grants more than necessary.
  • Windows: no sandbox. Startup aborts as long as the profile file does not explicitly switch it off.

The profile file controls it in the ragents.workspace section:

"ragents.workspace": {
  PROCESS_SANDBOX: "on",
  PROCESS_SANDBOX_NETWORK: ["*"], // public web domains on ports 80 and 443 (the default)
},

curl, package downloads and API requests no longer need a domain entry for each public website. IP literals, localhost and internal services need explicit entries, for example ["*", "127.0.0.1:8080"]; for an internal hostname in public mode, allow its IP and port too. The server's own address is always allowed. A list such as ["registry.npmjs.org", "*.example.com"] restricts access to those targets; [] leaves only the own server reachable. Network permission does not distinguish downloads from uploads or destructive API calls. File isolation remains active. PROCESS_SANDBOX: "off" explicitly disables the whole sandbox, for example on Windows. On macOS, pnpm through corepack needs a packageManager in the workspace's package.json, because corepack otherwise aborts at a blocked folder above it.

Source: docs/operations.md. This chapter as Markdown.

Assign tokens and permissions