How to run a capsule on your subscription
A capsule normally reaches a model over HTTPS with an API key you declare in its manifest. Under transport: process it instead drives a harness CLI you are already logged into — the claude binary on your machine — so the work is billed to that login rather than to a key. Murmur still owns the task, the tools, the trace and the A2A door; the harness owns the model calls and the conversation.
What is a process driver?
A process driver is a WASM artifact declared with runtime: driver that knows how to drive one harness CLI: which executable to run, which arguments to pass, how to read its output, and what its exit codes mean. Murmur's runtime holds none of that — it resolves a binary, moves bytes, and records what the driver says happened.
A process driver is granted nothing: no environment, no files, no network, and no Murmur host interface. murmur/driver-claude-code is the driver for the Claude CLI.
The relevant manifest options are:
| Option | Controls |
|---|---|
| inference.transport | Whether the capsule calls a provider API or drives a harness subprocess |
| inference.driver.artifact | The process driver artifact that knows this harness |
| inference.command | The executable to run instead of the one the driver names |
| inference.max_turns | How many model steps one task may take |
| inference.system_prompt_file | The instructions the agent runs under |
| capabilities.env.allow | Every environment variable the harness process may see |
| lifecycle.conversation | Whether tasks in one context continue the same harness session |
What this transport costs you
Read this before you choose it. Four things are true here that are not true of transport: http.
| Cost | What it means |
|---|---|
| The harness runs under the capsule's class | The claude process runs under the same containment as a shell command: on scoped it is in the session's network and PID namespaces behind the egress proxy, under seccomp, Landlock, the session cgroup and rlimits; on sealed it also runs inside the composed root. What it needs beyond that — its install folder, its config and login folder, its inference host — the process driver declares and the manifest grants through capabilities.harness and capabilities.network.allow. See The process harness. |
| The capsule depends on your machine | It runs on your login and on whichever claude version is installed. The manifest pins neither, so the same manifest on two machines is not the same capsule. |
| Murmur counts what it is told | The harness reaches its provider with its own credentials, so no request passes through Murmur. The token counts in the trace are the harness's own report of what it spent, relayed by the driver — not requests Murmur measured. inference.max_session_tokens and spend.machine_tokens_per_day are enforced against those reported numbers. |
| The harness's own configuration does not load | The driver launches the harness with --setting-sources "", so the harness reads none of its own configuration on this machine: its CLAUDE.md, its settings and its own hook scripts stay out. The agent's instructions come from the manifest, which can name files in the work folder, such as the repository's AGENTS.md or CLAUDE.md, through inference.instruction_files. The capsule's own runtime: hook artifacts run exactly as they do on transport: http. |
These are the harness's own numbers, not Murmur's. On transport: http Murmur builds every request and counts it before sending; here it counts what the harness said it spent, and a harness that reports nothing leaves the count absent rather than zero. A manifest that sets either ceiling against a driver that reports no usage is refused at launch with E-RUN-038, so a ceiling never stands over a run nothing could measure.
Step 1 — log in to the Claude CLI
The capsule authenticates as you. Install the CLI and log in once:
claude login
claude --version
The version it prints is the version your capsule runs on — the manifest pins neither it nor your login.
The capsule reads that login out of ~/.claude under the HOME it is given, which is why HOME is one of the variables the driver requires. An API key cannot reach the harness through the manifest at all: capabilities.env.allow refuses every credential-shaped name with E-CAP-016, ANTHROPIC_API_KEY among them.
A run whose harness session reports an API key anyway prints warning[W-SEC-031] and keeps going: the harness still reports what it spent and both ceilings still count it, but the bill arrives against that key rather than the subscription. On a subscription login the warning does not appear.
Step 2 — create murmur.yaml
Create a murmur.yaml file. inference.driver.artifact is required under this transport, and every variable the driver's describe() requires must appear in capabilities.env.allow:
name: my-capsule
version: "0.1.0"
artifacts:
- name: murmur/driver-claude-code
version: "0.9.0"
runtime: driver
- name: murmur/tool-read
version: "0.4.0"
runtime: tool
- name: murmur/tool-write
version: "0.4.0"
runtime: tool
capabilities:
env:
allow:
- HOME
- PATH
resources:
memory_bytes: 2147483648 # the RLIMIT_AS the harness runs under; raise it if a turn fails to start
inference:
transport: process
max_turns: 100
system_prompt_file: ./instructions.md
driver:
artifact: murmur/driver-claude-code
lifecycle:
conversation: threaded
The harness runs under the capsule's class. A process driver built against murmur:driver@0.6.0 declares what its harness needs beyond that — Claude Code's install folder, its config and login folder, and its inference host — and the manifest grants the matching directories under capabilities.harness.paths and the host under capabilities.network.allow. mur run refuses a process driver built against an earlier murmur:driver version with E-WIT-LEVEL-001 at every containment class, so install a murmur/driver-claude-code release built against 0.6.0, then run mur run --explain-scope to see each need it declares and grant it. Leave inference.model out to run on whatever model your subscription defaults to.
Create an instructions.md file next to it with the agent's instructions. That file is the whole of what the agent is told about its job.
Step 3 — install the artifacts
mur install
Different ways to install artifacts
mur install fetches an artifact from the source of its scope. murmur/ artifacts come from
the built-in source, murmur-nexus/default-artifacts, with no configuration.
Option A — name the source of a scope in ~/.murmur/config.yaml:
registry:
sources:
- repo: <owner>/<repo>
token: "${GITHUB_TOKEN}"
Then install by full name, with or without a version:
mur install <owner>/<artifact-name>@<version>
Option B — pass a full GitHub reference and skip configuration entirely:
mur install github:<owner>/<repo>@<tag>
See Installing artifacts to learn more.
The driver is resolved like any other artifact. mur run then checks it three times before the harness starts, and refuses the launch on the first failure:
| Check | Refused with |
|---|---|
The artifact exports the process driver interface at the version this mur serves |
E-WIT-LEVEL-001 for another version, E-RUN-029 for none |
It loads, and its describe() returns usable variable names |
E-RUN-032 |
Every variable describe() requires is declared in capabilities.env.allow |
E-CAP-019 |
Step 4 — run a task
Create a task.md file with the work you want done, then run the capsule:
mur run --task task.md
mur run --task task.md
murmur: url localhost:52222
session: ses_019ed2af53da75c2aefee84ee10c34af
status: ok
The harness's answer is written to out/result.txt in the session's records directory, exactly as on transport: http.
Step 5 — give the agent tools
The harness's own built-in tools are stripped. Murmur stands up a loopback tool server for the run, hands the driver its address and the capsule's tool names, and the driver points the harness at it — so the model is offered the capsule's tools and nothing else, and Murmur executes every call it makes.
Two kinds of entry reach the model this way:
| Declared as | Reaches the model as |
|---|---|
A runtime: tool artifact, such as murmur/tool-read |
One tool per artifact, under the part of its name after the /: tool-read |
| A binary in capabilities.shell.allow | One tool per binary, running in the capsule workdir |
runtime: driver and runtime: hook artifacts are not offered to the model, on this transport as on the other.
Murmur runs the harness's tool calls concurrently, up to the concurrent tool call limit, so a long shell command does not hold up the other calls the harness makes while it runs.
If the harness stops waiting for a call and closes its connection, a shell command that call started moves to the background at once, whether or not lifecycle.shell_grace_secs is set: the agent can read it through background-command when lifecycle.shell_grace_secs is set, and its result also arrives later as a completion task. trace.jsonl records a harness_note for the call.
Every call the harness makes is checked before it runs, in the same order as on transport: http:
- The tool's input_schema — refused if the call's input lacks a field the schema lists in
required. - capabilities.filesystem.read_only — refused if the call writes a protected path.
- An
on-tool-callpolicy hook, where the capsule declares one — refused if the hook denies the call.
A refused call never runs. The model is handed a failed tool result carrying the refusal's reason, and trace.jsonl records the refusal.
Watch what the harness actually called:
mur trace show
Different ways to identify a session
mur trace show with no argument reads the most recent session:
mur trace show
To name another one, pass an ordinal counting back from the newest (@2), the last 4 or more
characters of its ID (3e4b), the full ID, or a path to its trace.jsonl:
mur trace show @2
mur trace show 3e4b
mur trace show ses_6801f81dd28b4a9daf434e8324c4793e
mur trace show path/to/trace.jsonl
Use --workdir <path> if your session directories are not under ./workdir. Every command
that names a session takes the same addresses — see
Session addresses.
Other trace exploration commands
mur trace has four subcommands for exploring session output:
mur trace show — print the full trace for a session to the terminal.
mur trace steps — show a turn-by-turn summary of what the agent did in a session. Pass --verbose to include a truncated summary of each tool's input.
mur trace diff — compare the traces of two sessions side by side, or with no arguments the two most recent. Useful for spotting behavioural regressions between runs.
mur trace report — generate a structured summary report from a session's trace. Covers token usage, tool calls, latency, and other session-level metrics.
The session's harness_start record names the driver, the harness, the absolute path of the binary spawned, the version it reported, and the capsule tool names the bridge offered. It records no argument values: a driver is free to put the tool server's bearer token in one.
Step 6 — continue a conversation
lifecycle.conversation: threaded maps each A2A context to one harness session. The first task in a context starts a session; every later task in that context resumes it, and the harness — not Murmur — carries the history. Murmur writes no conversation.jsonl here.
The mapping lives in a harness-session.json file under ~/.murmur/conversations/. mur run --resume continues from it. The entry is written when a turn ends having produced something — text, a thought, a tool call, or a turn that ran to completion — so a turn interrupted before the harness produced anything leaves nothing behind, and the next task in that context starts a conversation the harness will answer to.
If the harness has lost a conversation Murmur is still naming, the next task in that context fails with E-RUN-036, and every task after it fails the same way. The entry is left exactly as it was: answering from nothing would be memory loss the person never sees. Dropping it is something you ask for, and it is recorded:
mur run --context ctx_0193f2 --forget-session --task "carry on"
curl -s localhost:41234 \
-H 'content-type: application/json' \
-H 'A2A-Version: 1.0' \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","contextId":"ctx_0193f2","role":"ROLE_USER","parts":[{"text":"carry on"}],"extensions":["https://docs.murmur.nexus/reference/agent-card/#murmur-door-v1"],"metadata":{"https://docs.murmur.nexus/reference/agent-card/#murmur-door-v1":{"forgetSession":true}}}}}'
Either way the entry is deleted, the turn runs as a new conversation under the same context id, and the trace records a harness_session_forgotten event naming the context, the id that was dropped and who asked. The flag applies to the launch's first task, and the forgetSession field to the task its message starts; it goes on a message without a taskId. Only this transport has a harness session, so a forget asked of any other capsule is refused: E-RUN-039 from the CLI, -32602 from the door.
Because the harness holds the history, --resume-mode compact has nothing to compact and refuses with E-RUN-037. context.max_tokens and inference.compaction parse but do nothing: the harness manages its own context.
Step 7 — know the three fixed limits
Three limits bound a run, and none is a manifest setting.
| Limit | Value | What it bounds |
|---|---|---|
| Inactivity | 600 seconds | How long a run may go with neither a line of harness output nor a tool call before the harness is killed with E-RUN-035. Any output and any tool call start the window again, so a harness that is working is never interrupted for taking a long time. |
| Interrupt grace | 10 seconds | How long a harness that was asked to stop has to end on its own before it is killed. |
| Concurrent tool calls | 16 | How many of the harness's tool calls Murmur runs at once. A call beyond that is refused at once with a failed tool result saying nothing ran; it is not queued. |
A full manifest
A coding capsule with tool artifacts, a hook and a shell allowlist on this transport:
name: my-coding-capsule
version: "1.0.0"
artifacts:
- name: murmur/driver-claude-code
runtime: driver
version: "0.9.0"
- name: murmur/tool-read
runtime: tool
version: "0.4.0"
- name: murmur/tool-edit
runtime: tool
version: "0.4.0"
- name: murmur/tool-code-graph
runtime: tool
version: "0.7.0"
- name: murmur/hook-diff-summary
runtime: hook
version: "0.13.0"
capabilities:
filesystem:
scope: "."
- name: murmur/tool-corpus
runtime: tool
version: "0.9.0"
capabilities:
state: {}
config:
config_version: 1
read_recent: { default: 10, max: 50 }
search: { default_k: 5, max_k: 25 }
prefix_map: { session-note: snt }
types:
note:
schema_version: 1
schema:
type: object
required: [text]
properties:
text: { type: string }
tags: { type: array, items: { type: string } }
additionalProperties: false
capabilities:
env:
allow:
- HOME
- PATH
shell:
allow:
- bash
- git
- cat
- ls
- head
- tail
- wc
- grep
- find
- sed
- diff
- cp
- mv
- rm
- mkdir
- python3
inference:
transport: process
max_turns: 100
system_prompt_file: ./instructions.md
driver:
artifact: murmur/driver-claude-code
lifecycle:
task_acceptance: queue
queue_depth: 8
after_task: sleep
shell_grace_secs: 600
conversation: threaded
It declares no compaction hook and no context.max_tokens: both are inert under this transport, and a manifest that named them would promise a mechanism that never runs.
Summary
| Manifest setting | Effect |
|---|---|
inference.transport: process |
Drives a harness CLI as a subprocess instead of calling a provider API |
inference.driver.artifact |
Required — names the process driver for that harness |
inference.command |
Overrides the executable the driver's describe() names |
capabilities.env.allow |
The whole environment the harness sees; every variable the driver requires must appear, or the launch is refused with E-CAP-019 |
capabilities.shell.allow |
Each binary becomes one tool the model may call through Murmur |
A tool's input_schema required list, capabilities.filesystem.read_only, an on-tool-call policy hook |
All three govern every tool call the harness makes, the same way they govern one on transport: http |
lifecycle.conversation: threaded |
Each A2A context resumes one harness session; the harness holds the history |
inference.max_session_tokens |
Enforced against the harness's own reported counts; the run stops at the first turn that reaches it |
context.max_tokens, inference.compaction |
Parse but do nothing; the harness manages its own context |
| Inactivity limit, 600 seconds | Fixed; a silent harness is killed with E-RUN-035 |
| Interrupt grace, 10 seconds | Fixed; a harness asked to stop is killed after it |
spend.machine_tokens_per_day |
Counts this capsule's turns too, from the same reported counts |
| Token counts in the trace | The harness's own report, relayed by the driver; absent for a driver that reports none |