Host runner (autonomous host tools)
Local tools let an agent reach your machine while you sit in martha chat --local-exec — you are the hands. The host runner removes the human: you attach a machine once as an agent's dedicated executor, and the agent then runs its host tools there on its own — from a chat someone else drives, a trigger, or a schedule — with nobody at a terminal.
Same tools (host_exec, host_read/host_write/host_edit, host_browser, local_agent, …), same grants, same audit. The only thing that changes is who executes: a long-lived martha runner process instead of your chat CLI, reached over a per-agent channel.
chat / trigger / schedule
│ agent calls host_exec
▼
Martha (cloud) ──park──► channel:{tenant}:{agent} ──SSE──► martha runner (your machine)
▲ │ executes in a scoped dir
└──────────────── tool.result over /api/channel/ingress ◄──────┘
│ turn resumes with the real output
▼Quick start
Two commands to set up, one to attach. You don't need to understand the pieces below to do this.
Onboarding
Starting from scratch? martha init --host-runner sets up your profile, a chat copilot, and wires it as a host runner in one guided flow.
# 1. Create a Martha-hosted agent (its reasoning runs in Martha)
martha agents create --name ops_runner --reasoning martha \
--model anthropic/claude-sonnet-4-6 \
--prompt "You run host tools on an attached machine to do real work. Use them; report concrete results."
# 2. Make it drivable as a host runner — mints the key, grants the host tools,
# lets a chat client drive it, enables chat takeover, and prints the exact
# runner command to paste:
martha agents setup-host-runner ops_runnersetup-host-runner prints something like:
✓ 'ops_runner' is ready as a host runner
• minted a runner key
• granted host tools: host_exec, host_read, host_write, host_edit, host_output, host_kill
• allowed chat client 'Acme' to drive it
• enabled chat takeover (runs_as_chat)
Runner key (shown once — store it like a secret):
martha_ak_…
On the machine you want the agent to drive:
export MARTHA_TOKEN=martha_ak_…
martha runner --tools --allow-host-exec --cwd ~/agent-workspace# 3. On the target machine, run exactly what it printed:
export MARTHA_TOKEN=martha_ak_…
martha runner --tools --allow-host-exec --cwd ~/agent-workspaceThat's it. The runner prints attached as agent … channel connected. Now anything that makes ops_runner call a host tool runs on that machine.
Options
setup-host-runner accepts --client <nameOrId> (which chat client may drive the agent — defaults to the tenant's default client), --tools host_exec,host_read,… (override the default host-tool set), and --rotate-key (mint a fresh runner key, e.g. if you lost the old one).
Try it
martha chat --agent ops_runner \
--message "In your working dir: read sales.csv, compute total revenue per region, write summary.txt, then cat it back."The agent calls host_exec / host_read / host_write in sequence; each parks, the runner executes it on your machine, and the turn resumes with the real output. Files it writes are really on your disk in the --cwd. The same origination happens with no chat at all — a trigger or scheduled run whose agent calls a host tool routes to the attached runner identically. That's the point: unattended.
Check who's attached at any time:
martha agents runner-presence # every host-runner agent
martha agents runner-presence ops_runner # just one
# ● connected ops_runner (my-laptop)Something not working? martha agents doctor <agent> runs a green/red checklist (runner key, host tools, chat-client access, chat takeover, runner attached) and prints the exact fix for each failing item. Driving a mis-wired host-runner agent from martha chat --agent also prints that fix before the turn, instead of silently answering in plain text.
What setup-host-runner does (and doing it by hand)
The one-shot wires four things. You'll only need this section if you're scripting it yourself or debugging:
- Runner key — a scoped API key on the agent (
api_key_scope=runner), authorizing channeltool.*frames only. (martha agents generate-key <agent>.) - Host-tool grants — the agent may only run a tool it's been granted. (
martha agents add-function <agent> host_exec ….) - Chat-client access — a chat can only select an agent its client is granted. (
martha clients grant <client> agent <agent>.) - Chat takeover — with several agents on a client, a granted agent shows up as a delegatable sub-agent tool; selecting it as the primary chat agent only flattens its own host tools into the turn when it's marked
runs_as_chat. (Set it in the admin UI: Definitions → Agents → edit → "Chat persona".)
Triggers and workflow nodes that target the agent don't need steps 3–4 — those only matter for driving it from chat.
Presence & revocation
Each runner connection is a live registry entry, so you can see and control who's attached. The admin agents page shows a precise Host runner: connected (host) / disconnected badge; the CLI shows it via martha agents runner-presence.
Kick a connection without rotating the key:
martha agents revoke-runner ops_runner # all connections
martha agents revoke-runner ops_runner --connection <id> # just oneThe connect-token is invalidated immediately and the stream drops within one heartbeat. Note a still-running martha runner transparently reconnects (resilience by design) — to take a machine down for good, stop the runner process, or rotate the runner key (martha agents setup-host-runner <agent> --rotate-key), which invalidates the old key so it can't reconnect.
Safety model
The runner is powerful, so it's boxed in:
- Scoped credential. The runner key is
runner-scoped: channeltool.*frames only. It's rejected (403) at task-claim,event.emit, asks, and document/collection + citation reads — even though it authenticates as the agent. Its per-connection connect-token inherits exactly that scope. - Ephemeral connect-token. The durable key rides the wire only on the connect handshake; the steady-state stream + result posts carry a short-lived, per-connection, individually-revocable token.
- Grant-gated tools. The agent can only run a host tool it was explicitly granted; server-set markers key off the canonical platform name, so a tenant can't name a function
host_execand inherit host execution. - Scoped workspace + consent. Commands run in the runner's
--cwd;--allow-host-execis the machine owner's opt-in.local_agentpasses its task argv-only (never shell-interpreted). - Single-claim gate. A result is accepted only for a call actually awaiting it right now; stray, duplicate, or late results are rejected (409) instead of being signalled into the turn.
- Audit. Dispatch and receipt are recorded like any other tool call.
Kill-switch
The whole channel is gated by CHANNEL_ENABLED on the server — off → /api/channel/* 404s and nothing parks. It's the CHANNEL_ENABLED deploy variable; set it to 0 (or unset) + redeploy to disable the host-runner channel platform-wide without a code change. (LOCAL_EXEC_ENABLED, the local-tools kill-switch, gates host-tool dispatch itself and is on by default.)
Host runner vs. chat --local-exec
chat --local-exec (local tools) | Host runner (this guide) | |
|---|---|---|
| Who executes | your interactive chat CLI | a standalone martha runner |
| Attached | only while you're in the chat | as long as the runner runs |
| Trigger | your own message | any run that makes the agent call a host tool (chat / trigger / schedule) |
| Identity | your human session | the agent's scoped runner key |
| Use it for | ad-hoc, "do this on my laptop while I watch" | giving an agent durable hands on a machine |
Same tools underneath — pick the human-attached path for interactive work, the runner for autonomy.