Local tools
Local tools let a Martha agent reach the machine you're running the CLI on. The agent stays the same single agent — same conversation, same grants, same audit — but gains tools that execute on your computer instead of server-side. They all ride one transport (the _is_local_exec wait-gate); each is just another entry the server stamps and the CLI runs:
host_exec— run a shell command in a scoped workspace on your machine. Builds, tests, local files, driving local tooling. Passbackground: trueto dispatch it as a background job instead of waiting.host_screenshot— capture a web page on your machine with a headless browser and hand the image back to the agent, so it can actually see what rendered (not just read text). The screenshot becomes a native image in the model's context.host_browser— drive a persistent local browser with structured actions:navigate,click,read_console,read_dom,screenshot. Instead of hand-writing Playwright throughhost_exec, the agent navigates and then reads the console — so it seesdoom.wasm 404 / init hungdirectly — or screenshots the current page (canvas / WebGL included). The browser persists across calls within a turn, so navigate → read_console → screenshot observe the same page.host_read/host_write/host_edit— first-class file ops in the scoped workspace, instead of hand-rollingcat <<'EOF'heredocs andsed.host_editdoes an exact-string replace and fails closed if the target is absent or (withoutreplace_all) not unique.local_agent— delegate a whole high-level task to a capable local coding agent (Claude Code / Codex / cursor-agent) that has full local context, and stream its result back. Configured viaMARTHA_LOCAL_AGENT_CMD(e.g.claude -p,codex exec --skip-git-repo-check); the task is passed argv-only so it's never shell-interpreted. Also takesbackground: truefor long delegations.host_output/host_kill— poll a background job for its new output + status, or stop it.
The agent reaches your machine through your own martha chat session — there's no separate runner identity or daemon. The CLI you're already talking through is the runner.
Running it
Local tools are off unless you opt in per session:
martha chat --local-exec --yes--local-execdeclares thelocal_execcapability for this chat session. Without it, the host tools aren't dispatched to your machine — the agent gets a "not available here" result and proceeds in plain text.--yesauto-approves host execution (required for non-interactive runs). Each command runs in a scoped workspace under your current directory.
The agent can only use a host tool if it's been granted that tool (like any other tool — see Permissions & access). Grant the specific tools (host_exec, host_browser, host_read/host_write/host_edit, local_agent, …) to the agent or API client you're chatting as.
host_screenshot and host_browser need Playwright on your machine (kept optional to keep the CLI lightweight):
npm i -D playwright && npx playwright install chromiumlocal_agent needs whichever local agent you point MARTHA_LOCAL_AGENT_CMD at installed and authenticated (it defaults to codex exec). The task is appended as a single argv element, so the configured command should take its prompt as a positional argument (e.g. claude -p).
Enabling it for an agent (operator setup)
A host tool is just a platform function the agent must be granted — there's no separate per-agent switch. To let an agent use local tools end to end:
Confirm the platform flag is on.
LOCAL_EXEC_ENABLEDdefaults to1in production (it's the platform-wide kill-switch, not the per-agent gate). Set theLOCAL_EXEC_ENABLEDdeploy variable to0to disable everywhere.Grant the tools to the agent (or the API client the user chats as). Grant the dispatchers you want —
host_execand/orlocal_agent— plus the file/browser tools as needed, and (for background work)host_outputandhost_kill:bash# to an agent: martha agents add-function <agent> host_exec martha agents add-function <agent> host_output martha agents add-function <agent> host_kill # …or to an API client: martha clients grant <client> function host_execOr in the admin UI: open the agent (Definitions → Agents → edit) and add the host tools from its functions list — they appear there like any other function. The API-client equivalent lives under the client's function access.
TIP
Grant
host_outputandhost_killtoo, or the agent can start a background job but never collect or stop it.The user opts in per session with
martha chat --local-exec --yes— a human session (service-account / API-key sessions can't dispatch host tools by design).
This is the human-attached path: a host tool runs while you're connected through martha chat --local-exec, and your CLI is the runner. For the autonomous path — a machine you attach once as an agent's dedicated executor, so the agent can run host tools with no human in the loop (from a chat someone else drives, a trigger, or a schedule) — see Host runner (martha agents setup-host-runner). Same tools; a standalone martha runner --tools executes them over a per-agent channel.
What the agent sees
host_exec returns stdout / exit code as text. Long-running commands (a dev server) are detached and reported as backgrounded so they survive the turn.
host_screenshot and host_browser's screenshot action are the difference between a blind shell-jockey and an agent that can look at its own work. The runner captures the page, uploads the bytes straight to object storage, and the screenshot is composed into the model's next turn as a real image. The agent can read a number drawn inside a <canvas>, tell whether a page rendered or came up black, or spot a stuck loader — and fix it. host_browser's read_console returns the real browser console, page errors, and failed requests as text — the fastest way for the agent to see why a local page is broken.
host_read returns file text; host_write / host_edit report what they changed. local_agent returns whatever the delegated agent printed — the runner waits for it to finish (a long run is just latency; the gate holds up to 24h).
On a model that isn't vision-capable, an image is described to text via a VLM instead, so the turn still works.
Background jobs (dispatch & poll)
For work that takes a while — a build, a test suite, a dev server, a long local_agent delegation — the agent can choose to run it in the background instead of blocking the conversation. This is the same model as Claude Code's run_in_background + BashOutput + KillBash: the agent decides per call, and pulls the result by polling.
- Dispatch. The agent calls
host_exec(orlocal_agent) withbackground: true. The runner spawns the command detached in its own process group, writing output to a file-backed log under.martha-runner/, and returns a handle immediately —{ job_id, status: "running", log_path }— without waiting. The conversation keeps moving. - Poll. The agent calls
host_output({ job_id }). It returns only the new output since the last poll (capped, withbytes_remainingif there's more to drain) plusstatus— one ofrunning,done,killed,crashed,unknown,expired— and, once finished, theexit_code. - Stop.
host_kill({ job_id })terminates the job's whole process group and marks it terminal.
The result lives in a file on your machine, and job_ids are recorded in .martha-runner/journal.json, so a job survives the CLI exiting: a later martha chat --session <id> in the same directory can poll a job an earlier process started. When the command finishes it records its exit code to a completion sentinel (.martha-runner/{job_id}.exit), written atomically — that's how a poll tells done from running, and crashed (the process died without recording an exit) from either. This is a pull model: nothing auto-injects a completion into the conversation; the agent collects the result when it polls.
Foreground stays the default and is unchanged — short commands return their result inline. Use background: true for the long ones.
Platform support
Background jobs are POSIX-only (macOS, Linux): the runner uses a /bin/sh wrapper, process-group signals, and the OS process table to track jobs. Foreground host_exec is unaffected. Cross-machine polling isn't supported — the log is local, so reconnect-and-collect is same-machine only.
Safety model
Local execution is gated, not open:
- Human sessions only. Service accounts and agent API keys can't dispatch host tools.
- Capability + grant. The session must declare
local_execand the agent must be granted the specific tool. - Server-stamped markers. Every host tool is recognized by a server-set marker keyed off the canonical platform name — a tenant can't name a function
host_exec(orlocal_agent) and inherit host execution. - Scoped workspace + consent. Commands run in a scoped working directory;
--yesis the explicit opt-in to auto-run.local_agentpasses the delegated task argv-only, so it's never shell-interpreted. - Tenant-scoped artifacts. Screenshot uploads are minted under your tenant's storage prefix and validated on receipt, so a result can't reference another tenant's object.
- Single-claim gate. A result is only accepted for a host-tool call that is actually awaiting it right now; a stray or duplicate/late result is rejected with
409instead of being signalled into the conversation. - Audit. Dispatch and receipt are recorded like any other tool call.
Kill-switch
The whole path is gated by the LOCAL_EXEC_ENABLED environment flag on the server (a platform-wide kill-switch, independent of per-session opt-in and grants). It's on by default in production; set the LOCAL_EXEC_ENABLED deploy variable to 0 to disable local execution platform-wide without a code change.