Skip to main content

CLI Adapters

Overview

Crewship runs each agent as a subprocess of a vendor coding CLI inside the crew container. The agent record’s cli_adapter column picks which binary and which command line; the orchestrator’s BuildCLICommand (internal/orchestrator/exec.go) is the one place that knows the per-vendor flag conventions. Everything else in the orchestrator — prompt assembly, memory injection, journal writing, tool routing — is adapter-agnostic; the adapter layer is only responsible for translating Crewship’s uniform internal representation into whatever flags / files / stdio shape a given vendor CLI expects. The adapter pattern exists because no two coding-CLI vendors agree on anything. Claude Code reads its system prompt from a --system-prompt flag; every other adapter has no equivalent flag, so Crewship prepends the preamble inline with [SYSTEM]/[USER] delimiters on turn 1 and lets the CLI’s own file-discovery path (AGENTS.md, CLAUDE.md, GEMINI.md, .cursor/rules/, .factory/AGENTS.md) carry the same content into turn 2+. Factory Droid additionally expresses autonomy as a tier (--auto low|medium|high) instead of a tool list. Rather than picking a winner, Crewship supports all of them — six adapters as of 2026-04 — and lets the operator choose per agent. The selection lives on the agent row, so a backend crew can run Claude Code while a data crew uses Gemini, with no orchestrator-wide config change. Adding another vendor is intentionally small: one new case in BuildCLICommand for the command-line translation, plus a normalisation rule in agents_create.go for the default tool profile and any vendor-specific defaults. The adapter_hint field on the CLI pairing request is telemetry only — the backend never routes on it, and the frontend list of adapters lives in lib/cli-adapters.ts rather than in Go, so a new adapter is a TypeScript file plus a Go switch arm, not a schema migration.

Supported adapters

AdapterBinaryTurn-1 system promptTurn-2+ persistenceOutputNotes
CLAUDE_CODEclaude --print --output-format stream-json --include-partial-messages --dangerously-skip-permissions --verbose--system-prompt flagAGENTS.md + canonical memory file set written to CWDstream-JSON on stdoutSupports --model, --mcp-config <path>. --tools is always passed with the per-profile built-in allowlist (see below). Default.
CODEX_CLIcodex exec --json --sandbox <mode>[SYSTEM]/[USER] prepend in the prompt body (Codex has no system-prompt flag)AGENTS.md written into CWD before execNDJSON--sandbox is always passed: read-only for tool_profile=MINIMAL, workspace-write otherwise.
GEMINI_CLIgemini -p <message> --output-format stream-json[SYSTEM]/[USER] prepend (no documented --system-instruction flag in headless mode)GEMINI.md + canonical memory file setNDJSONUses -p for the user message. tool_profile=MINIMAL adds --approval-mode plan (read-only); --allowed-tools is deprecated upstream.
OPENCODEopencode run --format json[SYSTEM]/[USER] prependAGENTS.md written into CWD before execsingle JSON object (no streaming)sst.dev’s BYOK CLI; works with any provider.
CURSOR_CLIcursor-agent -p --output-format stream-json[SYSTEM]/[USER] prepend (no --system-prompt flag in headless mode).cursor/rules/, AGENTS.md, CLAUDE.md written into CWDstream-JSON on stdout (same shape as Claude Code)Added 2026-04. -m <model> for model override.
FACTORY_DROIDdroid exec --auto {low|medium|high} -o stream-json[SYSTEM]/[USER] prependAGENTS.md + .factory/AGENTS.md written into CWDstream-JSON (NDJSON)Added 2026-04. Tiered autonomy — see below.
The default fallback when cli_adapter is empty or unknown is claude --print <message> — minimal flags, no streaming, lowest-risk degradation.

Tool profiles & the built-in tool allowlist

An agent’s tool_profile (FULL | CODING | MINIMAL, default CODING) curates which of the CLI’s built-in tools the agent may use. This is an allowlist, not a denylist: tools not on the list are removed from the model’s context entirely. Why an allowlist matters: a headless Claude Code agent otherwise inherits the CLI’s entire default tool catalog — including harness-internal tools (TaskCreate/TaskUpdate/TaskList/TaskGet/TaskStop, TodoWrite, ToolSearch, Agent, Workflow, Cron*, ScheduleWakeup, RemoteTrigger, EnterPlanMode, AskUserQuestion, …) that have no Crewship backing. An agent that calls one (e.g. TaskCreate to “create a task”) writes to ephemeral in-process CLI state that persists nowhere the user can see, then can’t explain where the data went. A denylist would let any newly-added builtin leak back in on the next CLI upgrade; the allowlist is closed by default. The per-profile built-in sets (single source of truth: builtinToolAllowlist in internal/orchestrator/tool_profiles.go):
ProfileBuilt-in toolsPosture
MINIMALRead, Glob, Grep, ToolSearchRead-only — inspection, review, grading.
CODING (default)+ Write, Edit, Bash, WebFetch, WebSearchWrite + execute + network. The workhorse.
FULL+ NotebookEditEverything useful.
ToolSearch is in every profile on purpose. Claude Code defers MCP tools by default (tool search enabled) and the model discovers them via the built-in ToolSearch tool — drop it and the agent can no longer see or call any MCP tool (crewship-memory, Composio/YouTube, everything). Verified: with ToolSearch allowed, MCP tools load on demand while a builtin that’s not in the allowlist (e.g. TaskCreate) stays unreachable even via ToolSearch. Keeping deferral also scales to large MCP catalogs (e.g. GitHub’s ~846 tools) without bloating context. This governs only built-in tools. MCP tools (crewship-memory, Composio apps, …) come from the agent’s .mcp.json via --mcp-config and are unaffected by --tools, so they always resolve (discovered through ToolSearch). The real Crewship capabilities (missions, issues, pipelines, keeper, peer messaging) are sidecar HTTP endpoints advertised in the system prompt — not CLI tools — and are gated by the agent’s role capabilities. Per-adapter enforcement (each CLI exposes a different lever):
AdapterMechanism
CLAUDE_CODE--tools <allowlist> (named built-ins).
CODEX_CLI--sandbox read-only (MINIMAL) vs workspace-write — capability gate, no named-tool list.
GEMINI_CLI--approval-mode plan (MINIMAL, read-only); --allowed-tools is deprecated upstream.
FACTORY_DROID--auto low|medium|high autonomy tier.
OPENCODE / CURSOR_CLIProfile-based built-in curation not yet wired (these CLIs lack the harness-internal tool surface that motivated the allowlist).

Cursor (CURSOR_CLI)

cursor-agent -p is Cursor’s headless mode. -p (print) prevents the interactive TUI from spawning; --output-format stream-json aligns the JSONL stream with the format the chat-bridge already parses for Claude Code, so the same reader handles both. Cursor reads its system instructions from files in the working directory: .cursor/rules/, AGENTS.md, and CLAUDE.md. SetupSystemPrompt writes those before exec, so there is no --system-prompt flag on the command line. The very first turn, however, has no file-discovery yet, so the orchestrator also prepends the preamble inline with [SYSTEM]/[USER] delimiters — turn-2+ then falls back to file-discovery so persona edits land without burning a re-prepend. --mode=plan and --mode=ask are valid Cursor flags (added 2026-01-16) but are not exposed today; the default agent mode is what BuildCLICommand emits. If a tool profile needs read-only browsing, extend the CURSOR_CLI case to map tool_profile=CONSULTATIVE to --mode=ask.

Factory Droid (FACTORY_DROID)

droid exec is Factory’s headless single-shot mode. The --auto flag picks the autonomy tier:
--auto valueBehaviourWhen
lowRead-only — agent inspects but does not mutate files.tool_profile is MINIMAL.
mediumCan edit files within scope.Default — tool_profile is CODING or unset.
highFully autonomous.tool_profile is FULL. Factory’s docs warn it can perform destructive operations without further confirmation.
The mapping lives in internal/orchestrator/adapter_droid.go (BuildCommand): MINIMAL → low, FULL → high, and everything else (CODING or an unset profile) → medium. The default is medium because the API normalises an empty ToolProfile to CODING (see internal/api/agents_create.go), and CODING agents are expected to write code. The previous “default = low” choice was theoretical; in production almost every agent would be told to write code anyway. The current inversion (default-medium, explicit-low) is honest about production behaviour.
--auto high is reachable by setting an agent’s tool_profile to FULL. Factory’s docs warn this tier can perform destructive operations without further confirmation, so gate FULL agents behind a Harbormaster approval rather than making it the default.

Adapters that are deliberately NOT supported

BuildCLICommand only ships adapters for CLI agents that meet four criteria. CLI agents that don’t meet all four are skipped today. Adding a new adapter is a one-line change once the upstream surface stabilises; open a GitHub issue to discuss before opening a PR.
  • Stable headless / --exec surface — single-shot invocation with deterministic stdout, no interactive REPL requirement
  • Settled flag set — no recent breaking changes to the command-line API
  • Self-contained binary — no IDE extension or browser harness required at runtime
  • Documented function-calling or MCP support — necessary for the memory + skill primitives to wire in

How the adapter signals the sidecar

The orchestrator stamps two environment variables on the container env (visible to every process running inside that crew container — both the agent CLI and the sidecar process). The sidecar reads them once at startup and tags every POST /api/v1/internal/cost/record it sends to the server:
  • CREWSHIP_BILLING_MODEmetered for API-key credentials, flat_rate for subscription credentials (Claude Max, ChatGPT Plus, Cursor, Copilot).
  • CREWSHIP_SUBSCRIPTION_PLAN — display label, currently "Anthropic Max" for the OAuth Claude Code path. Only set when CREWSHIP_BILLING_MODE=flat_rate.
The agent CLI process technically inherits the same env (containers don’t isolate env vars between sibling exec sessions), but the agent has no reason to read them — the variables are interpreted by the sidecar, which is the one code path that knows what to do with billing mode. See Environment Variables for the operator-facing reference and Paymaster billing modes for the full rationale.

Picking an adapter for an agent

Set cli_adapter on the agent (UI: agent canvas → “Runtime” panel; CLI: crewship agent update <slug> --cli-adapter CURSOR_CLI; API: PUT /api/v1/agents/{id} with cli_adapter in the body). The string must match one of the constants above exactly. Pick by what the credential supports:
  • Anthropic API key or Claude Max OAuthCLAUDE_CODE.
  • OpenAI API key or ChatGPT PlusCODEX_CLI.
  • Google AI key or Pro/UltraGEMINI_CLI.
  • Cursor subscriptionCURSOR_CLI.
  • Factory accountFACTORY_DROID.
  • Anything else (BYOK)OPENCODE.
Switching adapters is hot — the next exec uses the new value. The container does not need to be rebuilt unless the new adapter’s binary is missing from the image (verify with crewship runtimes info <runtime>).