remote-agent is a minimal working implementation of a local daemon plus remote executor model.
The main v1 path that works today:
- A remote
executorestablishes an outbound connection through Azure Web PubSub - A local
daemondiscovers executors through Azure Web PubSub and maintains inventory - The local
daemonexposes a streamable HTTP MCP server - In normal usage, start that MCP server first, then connect Codex / Copilot to
http://host:port/mcp remote.runsupports remote command execution on a single executorremote.cancelsupports cancelling long-running requests
Capabilities that are not finished yet:
fs.*artifact.*- interactive
session.* git.*- advanced recovery
- advanced permission controls
packages/contracts shared protocol / schema / registry types
packages/executor remote executor process
packages/daemon local daemon + MCP server
Current MCP tools:
executors.listexecutors.getexecutors.findexecutor.instruction.setremote.runremote.cancelremote.request_status
Notes:
- There are no file-oriented MCP tools such as
remote.list_dirorremote.read_fileyet. executors.getreturns the currentinstructionfor that executor. If it is missing and you already know what that machine is for, callexecutor.instruction.setonce with a full replacement instruction.- The standard integration path is not to let Codex / Copilot spawn the stdio process directly. Start the daemon HTTP MCP server first and connect to its
/mcpURL. - The
stdioentry is still available, but it is only recommended for local development and protocol debugging.
- Node.js 22+
- npm
- one Azure Web PubSub resource
- one fixed hub, currently
rexecby default
For a real end-to-end test, you need at minimum:
- a local machine that can run the
daemon - another machine, VM, or at least another isolated working directory that can run the
executor - the same Azure Web PubSub
connection string
npm install
npm run buildDuring development, if you want to start the executor and daemon like normal CLI tools from any directory, run this once as well:
npm link -w packages/executor
npm link -w packages/daemonThis exposes the local CLIs from both workspace packages on your global PATH:
ra-execra-daemonra-mcp
After that, you can run them directly from any directory, for example:
ra-exec --executor-id exec-dev-01
ra-daemon
ra-mcpIf you update the source code, rebuild first:
npm run buildThese CLI aliases now launch through a Node launcher script inside each package and then forward into the built dist output. That keeps the npm link shim stable on Windows as well, because it executes through node instead of depending on .js file associations.
Quick regression:
npm testRequired:
WEB_PUBSUB_CONNECTION_STRING
Common optional settings:
WEB_PUBSUB_HUBDAEMON_IDDAEMON_INVENTORY_LOG_INTERVAL_MSDAEMON_REGISTRY_REFRESH_INTERVAL_MSDAEMON_EXECUTOR_HEARTBEAT_INTERVAL_MSDAEMON_EXECUTOR_SUSPECT_AFTER_MSDAEMON_EXECUTOR_OFFLINE_AFTER_MSDAEMON_TOKEN_EXPIRATION_MINUTESDAEMON_VERSIONDAEMON_HOSTNAMEDAEMON_MCP_HOSTDAEMON_MCP_PORTDAEMON_MCP_PATHDAEMON_MCP_ALLOWED_HOSTS
DAEMON_MCP_* defaults:
DAEMON_MCP_HOST=127.0.0.1DAEMON_MCP_PORT=8765DAEMON_MCP_PATH=/mcp
So the default URL is:
http://127.0.0.1:8765/mcp
WEB_PUBSUB_HUB default:
WEB_PUBSUB_HUB=rexec
DAEMON_MCP_ALLOWED_HOSTS is a comma-separated list, for example:
localhost,127.0.0.1,daemon.example.com
The executor supports three connection modes:
WEB_PUBSUB_CONNECTION_STRINGplus optionalWEB_PUBSUB_HUBEXECUTOR_CLIENT_ACCESS_URLWEB_PUBSUB_ENDPOINTplus optionalWEB_PUBSUB_HUB
At the moment, the most stable and convenient real-world mode is the first one: let the executor mint its own client token locally from the connection string. WEB_PUBSUB_HUB now defaults to rexec, so that value is used automatically when not provided.
Required:
EXECUTOR_ID, or the startup argument--executor-id <id>/--id <id>- any one of the three connection modes above
Common optional settings:
EXECUTOR_TAGSEXECUTOR_HEARTBEAT_INTERVAL_MSEXECUTOR_TOKEN_EXPIRATION_MINUTESEXECUTOR_VERSIONEXECUTOR_HOSTNAMEEXECUTOR_OSEXECUTOR_ARCH
EXECUTOR_TAGS format:
env=dev,region=use,role=builder
npm install
npm run buildRecommended: run it on another machine, in a VM, or at least from another isolated directory.
If you already ran npm link above, you can also use ra-exec directly without switching back to the repo directory.
Windows PowerShell:
$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\executor\dist\main.js --executor-id exec-dev-01Equivalent linked command:
$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-exec --executor-id exec-dev-01Development mode:
$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npx tsx .\packages\executor\src\main.ts --executor-id exec-dev-01This is the standard runtime path. Codex / Copilot should connect to this HTTP MCP URL instead of launching main.js directly, and instead of defaulting to stdio.
For stable usage, running the built output is recommended:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\daemon\dist\mcp-main.jsIf you already ran npm link, you can also run it directly from any directory:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-mcpNote: ra-daemon is bound to the plain daemon entry, which corresponds to packages/daemon/src/main.ts. It starts the command execution / diagnostics process only and does not start the HTTP MCP server. During development, if you want to start just the plain daemon as a normal command, you can run:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-daemonEquivalent workspace script:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npm run start -w packages/daemonDevelopment mode:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npm run dev:http -w packages/daemonAfter startup, the daemon prints a log similar to this on stderr:
mcp streamable http listening
url=http://127.0.0.1:8765/mcp
If you only want to validate discovery or direct remote run before MCP, you can start the plain daemon by itself:
$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\daemon\dist\main.jsThis entry does not start the HTTP MCP server.
The stdio entry is kept only for development and protocol debugging.
Built output:
npm run start:stdio -w packages/daemonDevelopment mode:
npm run dev:stdio -w packages/daemonThe normal Codex integration should point to an already-running streamable HTTP MCP URL.
A typical ~/.codex/config.toml:
[mcp_servers.remote_agent]
url = "http://127.0.0.1:8765/mcp"If your daemon is bound elsewhere, replace that with the matching http://host:port/path.
Recommendations:
- start the
remote-agentHTTP MCP server separately first - then let Codex connect to that URL
- never commit a real
WEB_PUBSUB_CONNECTION_STRING - if you later add a reverse proxy, auth headers, or HTTPS, add the matching fields in the Codex MCP config layer
Suggested validation order:
- Start an executor
- Start
mcp-main.js - Confirm in Codex that
remote_agentis connected - Run
executors.listfirst - Then run one harmless
remote.run
Recommended starter prompts:
List the currently online executors.
Run a harmless directory listing command on exec-dev-01.
Use dir /b on Windows and ls -1 on Linux/macOS.
If you already know what a specific executor is for, you can also write its instruction first:
Use executors.get for exec-dev-01.
If the executor instruction is missing, set it to a full replacement instruction that explains the executor's environment and intended use.
GitHub Copilot should also connect to the same HTTP MCP URL.
Recommended setup:
- Start the
remote-agentstreamable HTTP MCP server separately first - Add an MCP server with
type = httpin Copilot's MCP config - Point the URL to
http://127.0.0.1:8765/mcp
If you mainly use VS Code, open MCP: Open User Configuration from the command palette and configure remote-agent as a remote HTTP server. A typical config looks like this:
{
"servers": {
"remote-agent": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp"
}
}
}If you mainly use Copilot CLI, the idea is the same: add an HTTP MCP server and point it to the same /mcp URL.
Suggested validation order:
- Confirm the executor is online
- Confirm the daemon log already printed
mcp streamable http listening - Call
executors.listfirst from Copilot - Then run one harmless directory listing
Recommended test prompts:
Use the remote-agent MCP server to list available executors.
Use the remote-agent MCP server to run a harmless directory listing on executor exec-dev-01.
Use dir /b on Windows and ls -1 on Unix-like systems.
npm testCovers:
- contracts schema
- daemon registry / routing
- daemon MCP in-memory / streamable HTTP integration
- executor config / discovery / task runner
npm run buildThis verifies:
- TypeScript types
- declaration emit
- workspace-level build health
This test starts real processes for:
- one local streamable HTTP MCP daemon
- one executor in a separate temporary working directory
- one official MCP client
It then validates through Azure Web PubSub:
- executor discovery
- harmless directory listing
- long-task cancellation
Run it with:
$env:REMOTE_AGENT_LIVE_WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
$env:REMOTE_AGENT_LIVE_WEB_PUBSUB_HUB = "rexec"
npm run test:integrationStart an executor first, then start the HTTP MCP daemon.
Expected:
- the daemon log includes
discovery probe published - the daemon log includes inventory for that executor
- an MCP client calling
executors.getcan retrieve that executor
This mode is useful when you want to rule out the MCP layer first:
$env:DAEMON_ID = "daemon-smoke-02"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
$env:DAEMON_RUN_EXECUTOR_ID = "exec-smoke-01"
$env:DAEMON_RUN_COMMAND = "echo hello-from-daemon"
node .\packages\daemon\dist\main.jsExpected:
- the daemon prints
remote run result stdoutcontainshello-from-daemon
There is no fs.list MCP tool yet, so directory listing currently goes through a command.
Windows:
Use tool remote.run with:
- executorId: exec-smoke-01
- command: dir /b
Linux/macOS:
Use tool remote.run with:
- executorId: exec-smoke-01
- command: ls -1
Expected:
- return value has
ok = true stdoutcontains directory names
Windows:
Use tool remote.run with:
- executorId: exec-smoke-01
- command: ping -n 30 127.0.0.1 >nul
- waitForCompletion: false
After you get the requestId:
Use tool remote.cancel with:
- requestId: <the request id>
Then poll:
Use tool remote.request_status with:
- requestId: <the request id>
Expected:
- final
status = completed ok = falseexitCode = null
Check these first:
- whether the executor has actually started
- whether
WEB_PUBSUB_HUBmatches on both sides - whether the executor and daemon use the same Web PubSub resource
- whether the daemon log includes
discovery probe published - whether the executor log includes
hello published
Troubleshoot it layer by layer:
- Bypass MCP and run the daemon directly with
DAEMON_RUN_EXECUTOR_IDplusDAEMON_RUN_COMMAND - If the direct path works, inspect the MCP URL configuration next
- If the direct path also fails, inspect executor
stderrand daemonstderr
Check these carefully:
- whether
mcp-main.jsis actually running - whether
DAEMON_MCP_HOST/DAEMON_MCP_PORT/DAEMON_MCP_PATHmatch the client configuration - whether the value you gave to Codex / Copilot is the full URL
- if the daemon is bound to
0.0.0.0, the client must connect to a real reachable address, not the literal string0.0.0.0 - if
DAEMON_MCP_ALLOWED_HOSTSis configured, whether the request host is in the allowlist
If you are intentionally using stdio during development, check:
- whether you started
mcp-stdio-main.js - whether any normal logs are being written to
stdout - whether the
command/argspath is correct
The recommended cancel flow right now is:
- Call
remote.runwithwaitForCompletion: false - Get the
requestId - Call
remote.cancel - Poll
remote.request_status
WEB_PUBSUB_CONNECTION_STRINGis a high-privilege secret and must not be committed to the repository.- The current default HTTP MCP address is a local unauthenticated setup, suitable for local use and development environments, not for direct exposure to the public internet.
- If you later expose MCP through a reverse proxy or public address, add HTTPS, authentication, and access control at the proxy layer.
- If this is later shared by multiple users, the current
v1development trust model is not a good long-term security posture.
If you only want the shortest answer to "how should I use this right now?":
npm installnpm run build- start one executor
- start
node packages/daemon/dist/mcp-main.js - point the MCP server in Codex or Copilot at
http://127.0.0.1:8765/mcp - run
executors.listfirst - then run one harmless
remote.run