Skip to content

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.

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.

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.

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.

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.

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.

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.

AdapterOctipus toolsVendor planning modeActivityVendor approval relay
Claude Code / GLM / KimiScoped MCPplanStructured streamClaude permission control requests enter Octipus approvals
Codex CLIScoped MCPread-only sandboxStructured streamVendor’s noninteractive sandbox policy; no interactive relay
Mistral VibeScoped MCPplan agentBufferedVendor agent mode; no interactive relay
AntigravityTerminal bridgeplan modeBuffered textVendor 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.

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.

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.

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_interactions config flag staying enabled in the user’s local configuration — something Octipus does not control and cannot verify.

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.

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.

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.

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.

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 (/clear drops 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.

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 coding and 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 HEAD and 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_modules is 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 an octipus/<id>-detached ref) 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.

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.

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.

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.

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.


Edit this page in the repository.