CLI agents
Octipus can run work through vendor CLIs using their existing login and configuration. This is intended to make existing subscriptions useful inside Octipus where providers allow it. Eligibility, third-party usage policies, quotas, subscription coverage and additional charges are provider decisions. A successful CLI login does not establish that a particular request is included in a subscription.
Pi supplies the TUI components. It is not an additional agent integration.
Executable paths and pipeline recovery
Section titled “Executable paths and pipeline recovery”At startup, Octipus reads the user’s login PATH and checks configured CLI
executables. Standard ~/.local/bin and ~/.npm-global/bin installations are
included. Set cliModels.path in Settings to use an explicit PATH instead;
restart after changing it. The executable settings are
cliModels.claudeCode.binaryPath, cliModels.codexCli.binaryPath, and
cliModels.antigravityCli.binaryPath. Each accepts a command name or absolute
path. Their first-boot environment equivalents are OCTIPUS_CLI_PATH,
OCTIPUS_CLAUDE_BINARY, OCTIPUS_CODEX_BINARY, and
OCTIPUS_ANTIGRAVITY_BINARY. Authenticated detailed health reports executable
paths or the PATH searched when resolution fails.
create_pipeline returns a handle after saving the graph and initial checkpoint.
Use get_pipeline_status({id}) for stage results and remaining items, and
resume_pipeline({id, fromStage?}) to recover a failed or stopped run.
fromStage accepts an exact stage name or node key within retained checkpoints;
omitting it resumes the latest checkpoint. Earlier completed outputs are reused;
the interrupted stage runs again. The coordinator collects pipeline results and
writes its own final response.
resume_pipeline acknowledges accepted scheduling with status: "resuming"
before waiting for replay consent. The durable status then exposes that consent
as awaiting_approval with pendingApproval details. A scheduling watchdog fails
a resume if no stage starts within two minutes, excluding time spent awaiting
an explicit approval. Status reads are read-only and time out after 15 seconds
if storage cannot answer; they do not acquire the runner’s execution lock.
stop_pipeline({id}) stops the run in its current session and saves the attempt
as resumable stopped, distinct from failed. update_pipeline_items({id, skip: [itemId], add: [{title, detail, acceptance}], order: [itemId], evidence})
queues changes atomically for the next item boundary. The owner can use these
tools from a later root turn in the same session/workspace. Stop before skipping
the active item; edits to stopped or paused runs apply before resume. Completed
items keep their results. New items are appended; order lists existing items
first, followed by unlisted items.
Stage events include item id/title, attempt id/number, and the start reason.
get_pipeline_status exposes the durable event history in history and each
item’s stages, including results, retry reasons, and approval decisions. Old
events lacking item/attempt identity remain unassigned; their provenance is
not guessed. Running summaries are derived from the current stage and item.
Use includeHistory: false for frequent status polling; monitor probes do this
automatically rather than fetching every previous report on each check.
For a background continuation, monitor__create accepts a tool source with
name: "get_pipeline_status", args: {id: "<pipeline handle>"}, and
condition: {path: "status", operator: "in", value: ["completed", "failed", "paused", "stopped", "awaiting_approval"]}.
Save a continuation that inspects the result, then end the turn. The monitor reads
the same session- and workspace-scoped status without waiting for the pipeline
to finish. collect_children waits for completion and is not a monitor probe;
resume_pipeline is a mutation and cannot be used as a probe either.
When the session root is busy collecting results, the monitor delivers steering
to that root instead of queuing behind its turn. Delivery includes a fresh
observation beside the original match (or a specific refresh error), so an old
approval match cannot be mistaken for the current state.
Each plan item traverses the configured body stages. Stage execution-contract
failures retry with the failure reason, up to maxRetries (additional attempts).
Retry counts survive checkpoints. Stopping preserves the current stage for resume;
restarting the server pauses interrupted runs instead of restarting paid work.
Pipeline progress updates the work plan linked when the pipeline was launched.
The linked plan keeps its id, title, goal and step ids; only a plan the pipeline
creates itself is named after the pipeline. A stage whose handoff reports
stopped: true has not delivered and is retried as a contract failure, for the
same item. plan__add_items skips items whose title is already on the plan.
Approvals inside a pipeline
Section titled “Approvals inside a pipeline”A CLI worker cannot hold a tool call open indefinitely, so it ends its turn after
five minutes without an answer or when its CLI disconnects. A pipeline never
fails for that reason. The stage is marked awaiting_approval, the blocked call
is raised again as a normal permission request (approval prompt and push
notification), and get_pipeline_status reports it under pendingApproval
(requestId, stage, item, toolName, subject for the command or path, and
reason). The blocked call is the request the worker named when it gave up,
and only if that request belongs to a worker of this stage attempt; otherwise
the stage parks on a plain “continue the stage” question and nothing is
pre-approved. When the user approves, the stage attempt runs again, and the
same call runs without a second prompt.
The allowance is stored on the pipeline (metadata.approvedCalls), so it
survives a server restart, and it ends when the run is no longer running or
awaiting approval. It is narrow on purpose. It applies only to the stage worker
of that pipeline, stage and attempt (not the root agent, another pipeline, or a
child the stage spawned), with the same hostServices setting, and only to the
same tool and action with the same arguments. A different cwd, env,
network or useShell value, or different file contents, asks again. Only the
CLI’s description label and timeout are ignored. A DENY rule always wins.
Every stage attempt has an id that is stored in its checkpoint. A resume after
an approval, a failure or a server restart continues the same attempt, so on a
resumable CLI (Claude Code, Codex) it reuses the attempt’s CLI resume key and
continues the conversation the worker stopped in. The next item, a QA bounce, a
contract retry, or a rewind with fromStage starts a new attempt. While an
attempt runs, its text so far is saved to its checkpoint every 15 seconds and
when it fails. A resumed attempt gets that text back, marked INCOMPLETE, and
get_pipeline_status shows it under incompleteAttempt. It is never reported
as the stage’s output, and it is cleared when the attempt finishes. A denial or
a stop leaves it stopped at that stage; resume_pipeline runs the stage again.
answer_pipeline_approval waits up to five minutes for an explicit user answer.
A timeout or unavailable answer returns decided: false and leaves the pipeline
approval pending; it does not manufacture a denial. Decisions include the
command, request id, deciding user and timestamp.
A plan approval or a stage approval also lists the pipeline’s gate commands,
which are the stages’ verifyCommand values resolved from the run’s parameters.
Approving the prompt lets each of those exact commands run without asking in
its own stage for the rest of the run, through either the Octipus shell tool or
the CLI’s own Bash, with the command as the only argument. A
pipeline without an approval prompt still asks the first time a gate command
runs: creating a pipeline is the model’s action, not the user’s, so it cannot
authorize anything. A gate command that depends on a stage’s output is not known
in advance and is not listed. A changed command, such as npm test 2>&1
instead of npm test, still asks. The coordinator can call
answer_pipeline_approval({id}) to ask the user in the conversation. The tool
forwards the question, so the user still makes the decision. After a server
restart, approving through this tool also resumes the paused stage.
resume_pipeline({id, skipItems: [itemId], evidence}) marks plan items that
earlier work already delivered as skipped, and records the commit and check
given in evidence. Every id is checked first; if one is unknown, nothing is
skipped. The item the run stopped in (failed, or still marked running after a
restart) can be skipped too. The resume then goes on from the plan loop with
the next pending item, and the skip and its evidence are kept.
Host services for gate commands
Section titled “Host services for gate commands”The process sandbox (security.shellSandbox) runs shell commands without the
network and without the Docker socket. A pipeline step can opt in with
"hostServices": true in its template step. When set, that stage’s
shell__run and shell__run_background commands and its verifyCommand share
the host network (for example, a local Postgres) and get
/var/run/docker.sock (for example, for docker compose build). It is off by
default. The Docker socket is root-equivalent on the host, so set it only on
stages whose commands you trust. It covers the stage’s own commands only:
agents the stage spawns run in the default sandbox. The setting has no effect
when the sandbox is off.
Authenticate with a vault credential
Section titled “Authenticate with a vault credential”Managed CLI agents receive these instructions automatically in their system prompt;
getRoleConfig also includes them for normal and lite role prompts. The shell tool’s
env parameter describes the same workflow.
Use the exact vault entry name as a placeholder in an Octipus tool call. For example,
with gh installed and a vault entry named github_token, call shell__run through
the octipus MCP server with:
{ "command": "gh api user", "env": { "GH_TOKEN": "{{secret:github_token}}" }, "network": true}Octipus checks the tool’s normal execution permissions and resolves the placeholder
using the current user’s vault access and the credential’s tool allowlist. The
subprocess receives GH_TOKEN; the agent does not need to fetch the plaintext token.
Use the environment variable supported by the actual program/API client. The token
is scoped to that invocation, not automatically installed into the vendor CLI’s
environment or a persistent login store.
A vendor-native terminal/Bash/exec call does not resolve these placeholders.
If MCP is unavailable, use the run’s provided bridge helper to call shell__run
with the same JSON arguments. Calling the target program directly through the
native terminal would bypass Octipus’s substitution.
Put credentials in env, not the command string (which can be logged), a URL,
a generated file, or a chat message. Exact injected values are masked in returned
Octipus tool output; do not print, encode, or save credentials to test this.
If the name is unknown, ask for the vault entry name, not the token. For missing
entries, denied access, or authentication failures, report the error and verify the
entry name/tool access rather than guessing names or trying repeated logins.
How tools and context work
Section titled “How tools and context work”Each managed CLI run receives a private loopback bridge, authenticated with a
random capability that ends with the run. The bridge exposes that worker’s
registered Octipus tools, including its role-specific tools, skill loaders,
connectors and applicable plan/delegation tools. Calls run through the native
ToolExecutor with the original agent, user, session and workspace. The bridge
never accepts an identity or session override from tool arguments and does not
use the standalone MCP server’s bootstrap admin token.
Claude Code (including the GLM/Kimi variants), Codex and Vibe receive a per-run MCP configuration. Antigravity can reach the same bridge through a supplied terminal helper. The helper supports listing tools and calling a named tool with JSON arguments; credentials are inherited privately through the environment.
A CLI agent rarely calls search_knowledge on its own: it has its own file
and web search. The knowledge base and notes closest to each message are
therefore injected into its instructions by the context hooks
(CONTEXT-HOOKS.md), and the Recall page shows, per model,
how often an agent used them or searched itself.
The working directory remains the session’s project or user workspace, except
for a coding child given its own git worktree (see
Optional git worktree per coding child). System
instructions are passed with the invocation. Octipus does not temporarily
rewrite AGENTS.md or GEMINI.md in a shared project.
These controls scope Octipus bridge calls. They do not turn the vendor CLI into an OS-isolated tenant: vendor-native tools, host files, account credentials, hooks and local configuration remain subject to the vendor’s runtime and sandbox. Use a trusted local execution account. Claude, Codex and Vibe isolate their configured MCP list for the run; Antigravity’s host integrations are not isolated by its terminal-helper connection.
Plans, feedback and delegation
Section titled “Plans, feedback and delegation”A CLI root receives get_work_plan and update_work_plan when the root tool set
includes them. Its durable plan appears in the same web/TUI view as a native
root’s plan. CLI children receive their own assigned tools; they do not acquire
root-only plan editing privileges merely by being CLI agents.
get_cli_run_context returns the plan, complete feedback history and queued user
guidance. Octipus tool responses also include fresh context when its repository is available.
A failed context refresh does not turn a successful tool operation into an error. The CLI is
instructed to check before further affected work and before its final answer.
User steering targets the session’s running root. It interrupts a pending
collect_children wait (including automatic collection) without cancelling or
automatically steering any child. The root decides whether to use steer_child
on one of its own pending children or handle the change after collecting.
Claude-based adapters also submit guidance through their stream-json stdin while
native CLI work is running. --replay-user-messages acknowledges receipt; a pipe
write emits steering_submitted, and replay emits steering_delivered. Inputs
not acknowledged before process exit are retained for a follow-up. Acknowledged
input does not prove the requested change was applied. See the
Claude CLI reference.
Codex and other adapters without live stdin receive guidance through Octipus tool
responses or follow-up invocations. A running command is allowed to finish;
steering does not terminate commands or restart children.
Claude/Codex can make up to two follow-up invocations when guidance or unhandled
root-plan feedback remains, within the remaining run budget. Each steering
message grants one extra iteration, matching direct-provider workers. When no follow-up
is possible (buffered adapters, an exhausted turn budget, or the follow-up
limit), the run still completes with its result: Octipus appends a visible note
naming how many guidance items were not applied and emits a guidance_pending
event. Plan feedback stays pending in the durable record and steering text is
already in the session history, so the next user message carries both. Feedback
marked needs_clarification remains visible for the user to answer.
Bridge calls are serialized per worker so the native executor sees one call at
a time. get_cli_run_context and get_work_plan bypass that queue, so a CLI can
still read guidance while an awaited delegation holds it.
Delegation uses the actual native spawn_child handler and its role, depth and
budget constraints, including detach mode: a CLI can fan out several children,
keep working, and pick their results up with collect_children. Every Octipus
tool response lists still-pending children in the run context. A CLI that
finishes without collecting gets one bounded follow-up turn to merge the
results (or, for adapters that report only at completion, the results appended
to its answer); children still pending after that are cancelled. Cancellation is
propagated through the worker’s abort signal. Vendor-native subagents outside
Octipus’s bridge are not members of Octipus’s managed child tree.
Permissions and limits
Section titled “Permissions and limits”| Adapter | Octipus tools | Vendor planning mode | Activity | Vendor approval relay |
|---|---|---|---|---|
| Claude Code / GLM / Kimi | Scoped MCP | plan | Structured stream | Claude permission control requests enter Octipus approvals |
| Codex CLI | Scoped MCP | read-only sandbox | Structured stream | Vendor’s noninteractive sandbox policy; no interactive relay |
| Mistral Vibe | Scoped MCP | plan agent | Buffered | Vendor agent mode; no interactive relay |
| Antigravity | Terminal bridge | plan mode | Buffered text | Vendor mode; no interactive relay |
All Octipus tool calls use Octipus permissions. Attended sessions can approve
through their existing UI/TUI/channel; unattended ASK is blocked. Claude’s own
tool approvals are relayed as toolId cli-native:<Tool> (for example
cli-native:Bash). The default rule set allows the read-only tools (Read, Glob,
Grep, LS, NotebookRead, WebFetch, WebSearch, TodoWrite); everything else uses the
ASK default. Operators who set their own permissions.rules replace that list
and can add per-tool entries such as cli-native:Read(*). Rules can match
arguments, e.g. cli-native:Bash(git:*). Existing cli-native policies and rules
remain effective: a denial under either identity wins, and legacy scoped grants
keep their conditions, expiry and usage limits. A new per-tool policy takes
precedence over a non-denying legacy policy; legacy ASK rules still take
precedence over the new read-tool defaults. Vendor-native
permissions are separate and vary by adapter. A vendor bypass/auto-approve mode
still bypasses that vendor’s prompts; it does not authorize an Octipus tool that
Octipus denied. Prompt instructions tell the CLI not to work around denials, but
are not an enforcement boundary for arbitrary vendor-native shell actions.
Claude’s AskUserQuestion is an interactive questionnaire, not a yes/no tool
approval. In the web UI, every question and option description is shown in the
answer form, with single/multiple selection and custom text. Submit answers
returns them to Claude; Cancel returns a denial. A stored ALLOW still requires
actual answers, while DENY and unattended-session restrictions remain effective.
These native tools are not entries in the built-in tool settings; native policy
uses the cli-native:<Tool> rules described above. Clients that only send a bare
Allow cannot answer a questionnaire; use the web form.
The action_recovery · retry prompt reviews earlier tool actions whose effects
are uncertain. It does not indicate that the global retry counter was exceeded.
Check whether those actions already took effect before allowing further changes.
The litellm.maxRetries setting instead controls automatic LiteLLM request
retries; swarm.contractRetries limits retries after a child fails its scorer
gate. Approving recovery changes neither limit.
Command scorers use the child’s captured project/worktree and the shell sandbox.
Select the repository as the session project, then prefer pnpm test over an
absolute pnpm -C /host/repo test: a host path may not be mounted, and an
isolated child may be working in a different worktree. Explicit directory
switches for pnpm, npm, yarn, git and make are checked before dispatch when they
point outside the child’s tree, and again before verification. An unavailable
directory is an environment failure, not a request to rewrite working code.
A required non-retryable gate stops contract retries even when another gate has
a fixable failure. A viable any_of alternative still allows verification.
The flow guard runs on top of these rules. If a session has
read credentials, or has mixed private data with untrusted content, it turns an
allowed egress call (for example cli-native:WebFetch, or a network command in
cli-native:Bash) into an approval. It watches Claude’s and Codex’s tool-use
streams, so reads that were auto-allowed still count.
When a Claude Code model row sets no permission mode, managed runs use
workspace (acceptEdits): edits inside the workspace proceed, and every other
native tool raises a permission request that the stdio relay routes through
Octipus’ ALLOW/ASK/DENY rules. The previous default, bypassPermissions, never
emitted those requests, so the relay was inert unless an operator chose a mode.
Set permissionMode: full on the model row to restore bypass.
Adapters that report no token usage (Mistral Vibe, Antigravity) are accounted
with a character-based estimate (about four characters per token) flagged as
estimated on the cost row, so token budgets and pipeline pools no longer treat
those runs as free. The prompt handed to every CLI (and every native agent run)
is written to ~/.octipus/prompts as an owner-only file swept after seven days;
turn the agent.promptDumps setting off to write nothing.
Plan mode overrides the configured vendor permission mode for the invocation.
Native plan mode itself is not a complete shell sandbox; neither is this mapping.
Managed runs reject extra arguments that could override permission, MCP, input,
output, directory or budget settings, both when the model is saved and at spawn. The supported additive flags are Claude’s
--no-session-persistence, --no-chrome, --disable-slash-commands and
--effort; Codex’s --strict-config and --color; and Antigravity’s
--disable-slash-commands and --effort. Vibe has no additive flags currently.
Use the dedicated model settings for model choice and permissions.
The run’s active-work timeout excludes Octipus approval and delegation waits. Claude, Codex and Vibe’s bridge tool requests have a separate two-hour vendor wall-clock timeout, including those waits; exceptionally long calls can still exceed that transport limit. Reported usage is accumulated, with duplicate Claude message fragments deduped. Claude/Vibe receive turn caps, and Vibe receives its own token cap. Exact token accounting remains unavailable for adapters that do not report usage; Antigravity has a timeout backstop, not a measured token-limit guarantee. Observed bridge file changes and permission denials contribute to execution evidence, without counting the outer MCP envelope a second time. Native vendor telemetry can be incomplete, so counts are observed evidence rather than a complete audit of the host filesystem.
In shared spaces
Section titled “In shared spaces”An install CLI model is the operator’s personal subscription: it serves agents
in a shared space only when its model row sets
metadata.cliAgent.sharedUse: true. In a space each adapter runs in its
declared space mode (CLI_SPACE_MODES in src/core/cli-adapters.ts), where
its native tools cannot act outside Octipus’s decision path: Claude-binary
tools (Claude Code, GLM, Kimi) use --permission-mode default with the stdio
permission tool, drop pre-approved allowedTools, and read no user, project
or local settings file: --setting-sources= with a locked --settings file
(no allow rules, bypass mode disabled, no project MCP servers, the shell guard
as the only hook), so neither the host’s ~/.claude/settings.json nor a
.claude/settings.json in the space’s files can pre-approve a tool or add a
hook. (A user-level apiKeyHelper or env in those files does not apply in a
space either; log the CLI in, or set its environment on the model.) Octipus’s
file tools and native writes never write a coding agent’s configuration
(.claude/, .codex/, .gemini/, .agents/, .vibe/, .mcp.json) in a
space. Codex runs in the
read-only sandbox; Antigravity in --mode plan. Mistral Vibe declares no
such mode and is refused in spaces. A commenter’s turn uses API models only.
See SPACES.md.
Authentication and fallback
Section titled “Authentication and fallback”By default the child uses the vendor CLI’s stored login/configuration. Octipus
does not automatically inject the backend’s API-key environment variables.
Operators who intentionally want that behavior can set
metadata.cliAgent.inheritApiKeys: true on the model. Claude’s explicit OAuth
token environment variable remains supported. The GLM/Kimi Claude adapters
continue to use their explicitly configured vendor credentials/endpoints.
The CLI’s own configuration may itself select metered API authentication; Octipus cannot infer the final bill from a login. There is no implicit retry from a failed CLI to the default/API model. Explicit topic backup bindings are still an operator-configured fallback and can have different billing.
Build the bridge with npm run build --prefix mcp-server along with the backend.
Missing compiled bridge files fail at startup of the run. Capability details are
shown in the model settings and /work-plan for a CLI root.
Session reuse
Section titled “Session reuse”Octipus hands the vendor CLI its own session id back on the next turn (for adapters that support it), so the vendor continues the conversation it already holds instead of Octipus re-sending the whole conversation as a new prompt each time.
Only Claude Code and Codex CLI support this. Claude’s id is minted by
Octipus itself and passed on the first run (--session-id), so there is
never a run with no id to resume later; Codex assigns its own id, which
Octipus captures from thread.started. Antigravity and Mistral Vibe do not
participate, for different reasons:
- Antigravity: the installed build’s print mode emits no conversation id at all. Handing it a stale id does not error — it silently starts a brand new conversation, which is worse than not reusing, so Octipus never tries.
- Mistral Vibe: is not installed in this environment, and its resume
support depends on the vendor’s own
log_interactionsconfig flag staying enabled in the user’s local configuration — something Octipus does not control and cannot verify.
What invalidates a stored session
Section titled “What invalidates a stored session”Vendor reuse belongs to the root conversation. Children have isolated vendor
conversations and bridge namespaces, and start cold unless they are resumed per
role and task (see
Resuming child sessions per role and task). A fingerprint covers model, configured
provider identity, permissions, plan mode, workspace, stable instructions and
tool schemas. A changed fingerprint starts a new persistent vendor conversation.
Each record also tracks a clear generation and an acknowledged transcript cursor.
/clear invalidates old state even if an old turn finishes afterward.
What resume actually saves
Section titled “What resume actually saves”The next launch sends current guidance and messages the vendor has not yet seen. This includes intervening answers or corrections from another provider. The vendor may still process its stored history; actual cached-token usage and cost, not stdin size, determine savings. Codex’s Octipus MCP namespace stays stable across root turns while bridge endpoints and credentials remain per-run.
Compaction
Section titled “Compaction”Octipus summarizes a contiguous prefix, retains recent turns, and publishes a checkpoint with exact message coverage. Both Claude and Codex vendor records rotate only after successful checkpoint publication. The next launch carries that checkpoint plus its suffix. There is no separate credential-bearing CLI maintenance subprocess. A failed, incomplete or ineffective automatic summary does not rotate the vendor conversation. Turn execution and maintenance are serialized within one server process. Only root vendor sessions rotate; child task sessions are kept, because they never held the root transcript.
Cold fallback
Section titled “Cold fallback”A resumed vendor session can go missing between turns (the id expired, or the vendor’s state was lost). Octipus detects this from the vendor’s own error output, drops the stale stored id, and retries that turn exactly once, cold, with the full prompt — so a turn is never simply lost because the vendor forgot its session. The replacement ID is persisted, so the following turn resumes normally.
Resuming child sessions per role and task
Section titled “Resuming child sessions per role and task”By default a child agent starts a fresh vendor conversation every time it is spawned. A child on Claude Code or Codex CLI can instead continue the vendor session an earlier child left on the same task, when the parent asks for it.
The parent (the agent calling spawn_child, native or CLI) sets it by passing
an optional resumeKey: a task id of 1 to 200 letters, digits, ., _, : or
-. Passing the same key with the same role continues that child’s session;
omitting it gives a fresh child, exactly as before. The key is ignored for
native children and for CLI adapters that cannot resume.
The spawner scopes the key by the parent, so unrelated parents in one session
cannot pick up each other’s task sessions. The stored key is
<parent scope>><child role>:<task id>, where the parent scope is:
- a root parent’s role. Not an agent id, because the root is a new worker every turn and must land on the same key when it re-spawns the task in a later turn;
- a keyed parent’s own resume key, so grandchildren carry the full lineage;
- nothing for any other parent. A non-root parent spawned without a key has no stable identity, so its children start cold (and the server logs that the key was ignored).
The vendor session is stored in the same per-session record as the root’s,
under <adapter>::<key>. A keyed child resumes only when all of these hold:
- the stored record’s fingerprint matches. For a keyed child it covers the model, permission and plan mode, working directory, role, tool ids (without the lazy discovery tools) and all stable instructions (role prompt, critical rules, delegation, vault and bridge guidance). Changing any of them starts cold. A different brief, or different brief- or session-selected skills, does not: those follow the prompt’s volatile marker and are re-sent on each resumed run, as a root’s per-turn blocks are;
- the session has not been cleared since the record was saved (
/cleardrops every child key along with the root’s); - no other live agent holds the same key. Two concurrent children with one key never share a vendor conversation: the second starts cold. This claim is kept in memory, so it only guards agents in one server process. A stopped child keeps the key until its vendor process exits.
When the run ends the session id is saved for the next child on the task. A
child stopped by a cancel, a timeout or a turn limit (including Claude’s
error_max_turns) still saves it, but only if the vendor confirmed the session
during that run and no retry has taken the key since. A child stopped for
exceeding its token budget drops the key instead, so the next run starts cold
rather than inherit the spent context. At most 50 child task sessions are kept
per Octipus session; the least recently used are removed first. Compaction
keeps them (see Compaction).
Root CLI fingerprints include the full tool schemas, so any change to a root’s
tools starts its vendor session cold once. Adding resumeKey to spawn_child
was such a change: existing root Claude Code and Codex sessions started cold
once after the upgrade that introduced it.
Optional git worktree per coding child
Section titled “Optional git worktree per coding child”Parallel coding children normally share one working tree, and a Claude Code or
Codex child’s edits do not pass through Octipus’s own write serialization. An
operator can give each coding child its own git worktree instead. It is off by
default; turn it on with swarm.worktreeIsolation (env
SWARM_WORKTREE_ISOLATION), described in
CONFIGURATION.md.
A child gets a worktree only when all of these hold; otherwise it runs on the shared tree as before:
- its role is
codingand its model is a CLI adapter; - the session is a dev-mode project (never the per-user sandbox) whose directory is itself a git repository root, not a folder inside a larger repository;
- the project has no uncommitted tracked changes at spawn time. A worktree
starts at
HEADand would not see them; - the spawning agent is not already working in a worktree. Descendants of a worktree child reuse its worktree rather than creating a nested one.
If the worktree cannot be created, the child also runs on the shared tree.
Where. Worktrees are created under ~/.octipus/worktrees/<id> (the
absolute path in the OCTIPUS_WORKTREES_DIR environment variable relocates
this directory), on a
new branch octipus/<id> at the project’s current HEAD. The CLI child uses
the worktree as its working directory. Crash, backup and contract retries of
the same child reuse it; a backup attempt on a native model runs on the shared
tree, and its result is never merged from the worktree. Agents the child spawns work there too, and their
changes are captured by its commit and merge.
Cleanup. When the child finishes, the server commits any uncommitted work
on its branch and reports the branch, head, diff stat, files changed and merge
outcome on the child’s receipt. It merges the branch into the project’s
current branch only when the child finished ok from an attempt that ran in
the worktree and the project is still safe to merge into; the exact
conditions and skip reasons are listed in CONFIGURATION.md. A conflict is
aborted and the branch kept. The worktree directory is then removed, and the
branch is deleted with git branch -d only if it was merged (or had no
changes). When it was not merged, the child’s result notes name the branch
holding the work. A worktree is never removed while it has uncommitted changes
or while its HEAD commit would become unreachable; if removal is refused, it
is left in place and the notes say where. While the setting is on, the orphan
reaper sweeps worktrees whose child is gone (for example after a crash): it
removes one only if it is clean and merged into the project, reports the rest,
and skips any whose owning process is still alive.
node_modules. A fresh worktree has no dependencies installed. When the
repository root has node_modules, the worktree gets a single symlink
node_modules pointing to it, and /node_modules is added once, with a
comment, to the repository’s .git/info/exclude. That file is shared with the
main tree, where the line only affects an untracked root node_modules.
swarm.worktreeLinkNodeModules (default true) turns the symlink off.
Limitations.
node_modulesis shared, not isolated: an install or delete inside a worktree changes the project’s real dependencies.- Only coding-role CLI children are isolated. Native agents write through Octipus’s tools to the shared tree.
- Uncommitted edits in the project are not carried into a worktree; a dirty project disables isolation for that spawn.
- Unmerged work stays on its
octipus/<id>branch (or anoctipus/<id>-detachedref) for a person to review; Octipus never deletes an unmerged branch. - Server-side git calls run with repository hooks and fsmonitor disabled, so project hooks do not run on the server’s commits and merges.
Validation scope
Section titled “Validation scope”Deterministic tests cover private capabilities, cross-run isolation, invalid
configuration, compiled stdio transport, tool errors, context identity, plan
updates, feedback, permission denial, cancellation, follow-up turns, late
guidance reporting, unqueued context reads, Codex discovery and adapter
arguments. The vendor flags themselves (--permission-prompt-tool stdio,
--input-format stream-json, Codex -c mcp_servers=…, agy --mode plan) are
only exercised by the opt-in live test and can drift with vendor releases. An
opt-in test (OCTIPUS_LIVE_CLI=claude-code, codex, vibe or antigravity with Vitest’s
src/core/cli-agent-bridge.test.ts) exercises a configured real CLI login against
isolated sample tools and an in-memory plan repository. It does not contact
personal connectors or modify project files, but consumes vendor usage.
Approximately 90% coverage of useful everyday workflows is the implementation target, not a measured compatibility percentage or a promise of feature parity.
Lazy discovery
Section titled “Lazy discovery”CLI workers advertise the same core tool schemas as direct workers when lazy
mode is enabled. list_tools/describe_tool discover long-tail tools, and
call_discovered_tool dispatches through the original exact-membership and
permission checks. MCP discovery returns bounded summaries; exact tool schemas,
resources, URI templates and prompts are retrieved on demand. Catalog change
notifications refresh metadata without preloading resource contents.
Approval waits in managed CLI runs
Section titled “Approval waits in managed CLI runs”An unanswered approval pauses the active-work budget, but a managed CLI run waits at most five minutes for it. Expiry fails the run and cancels its pending requests; it never grants permission or executes the waiting command. A tool caller disconnecting during approval also fails the run immediately. Queued calls whose callers disconnected are discarded before execution. Completion while an approval is still pending is recorded as failure, not success.
Octipus shell tools use a separate /tmp. Keep test virtual environments in
the project/workspace so both the CLI and sandboxed verification can use them;
a missing host /tmp executable is not a reason to request elevated access.
Chat image attachments
Section titled “Chat image attachments”CLI chat turns skip auxiliary OCR. Claude receives original image bytes as
base64 image blocks in its stream-JSON user message; Codex receives original
workspace file paths through --image. Both transports include newly attached
images on resumed turns. Other adapters receive explicit original file references
and instructions to inspect them with native image-reading tools.
Image paths are resolved against the session workspace before launch. Missing, oversized, or unsupported CLI images are reported rather than converted to text. Raw image bytes are omitted from prompt dumps. API-worker auxiliary vision summaries are labeled as fallible supplemental descriptions, never authoritative replacements for the original image.