How Crewship invokes vendor coding CLIs (Claude Code, Codex, Gemini, OpenCode, Cursor, Factory Droid) and the reasoning behind which ones are supported.
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.
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):
Profile
Built-in tools
Posture
MINIMAL
Read, Glob, Grep, ToolSearch
Read-only — inspection, review, grading.
CODING (default)
+ Write, Edit, Bash, WebFetch, WebSearch
Write + execute + network. The workhorse.
FULL
+ NotebookEdit
Everything 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):
Adapter
Mechanism
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_CLI
Profile-based built-in curation not yet wired (these CLIs lack the harness-internal tool surface that motivated the allowlist).
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.
droid exec is Factory’s headless single-shot mode. The --auto flag picks the autonomy tier:
--auto value
Behaviour
When
low
Read-only — agent inspects but does not mutate files.
tool_profile is MINIMAL.
medium
Can edit files within scope.
Default — tool_profile is CODING or unset.
high
Fully 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.
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.
The four inclusion criteria
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
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_MODE — metered 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.
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 OAuth → CLAUDE_CODE.
OpenAI API key or ChatGPT Plus → CODEX_CLI.
Google AI key or Pro/Ultra → GEMINI_CLI.
Cursor subscription → CURSOR_CLI.
Factory account → FACTORY_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>).