Pair programming with AI agents. A captain steers, a mate builds.
Ship coordinates two AI coding agents—captain and mate—working together on a shared codebase. The captain reviews, directs, and assigns tasks. The mate writes code, runs tests, and implements. The human watches, steers the captain, and approves sensitive actions.
Each session runs in an isolated git worktree on its own branch. Sessions are persistent across restarts.
Browser (React)
|
| roam WebSocket (generated TypeScript types)
|
v
Backend (Rust)
|
|--- ACP ---> Claude Code (captain)
|--- ACP ---> Claude Code / Codex (mate)
|--- git ---> worktree per session
|--- fs ---> task persistence
ACP (Agent Client Protocol) gives the backend structured access to agent state: prompts, notifications, stop reasons, plan steps, content blocks, and permission requests. No terminal scraping, no polling.
The captain gets a restricted MCP toolset — no shell access, read-only file access:
captain_assign— assign a task to the mate (title + description)captain_steer— send direction to the mate mid-taskcaptain_accept— accept the mate's work and close the taskcaptain_cancel— cancel the current taskcaptain_notify_human— block until the human respondscaptain_read_file,captain_search_files,captain_list_files— read-only codebase access
The mate gets full implementation tools:
run_command— shell command in the worktreeread_file,write_file— file access (Rust files are auto-formatted)edit_prepare/edit_confirm— two-step search-and-replace with diff previewsearch_files,list_files— ripgrep and fdcargo_check,cargo_clippy,cargo_test— Rust toolchainpnpm_install— frontend dependency managementset_plan— declare a work plan (required before any file changes)plan_step_complete— mark a step done and commit its changesmate_send_update— non-blocking progress update to the captainmate_ask_captain— blocking question to the captainmate_submit— submit work for captain review
assign → Working → [mate produces updates] → steer
↓
accept / cancel
- Human or captain sends
assignwith a task title and description - Backend prompts the mate via ACP
- Mate works—plan steps, tool calls, and text stream back in real time
- When the mate submits, the captain reviews and accepts, steers, or cancels
- Accepted tasks move to history; the session is ready for the next task
- Session list — all sessions with agent states, current task, branch name, auto-generated title
- Session view — unified feed of captain and mate output side by side
- Unified feed — interleaved
BlockAppend/BlockPatchevents from both agents; content blocks include text, tool calls (with diffs and terminal output), plan updates, and permission requests - Composer — steers the captain by default; prefix with
@mateto steer the mate directly;@also triggers file mention autocomplete from the worktree; images can be attached by paste or drag-and-drop - Permission approval — inline approve/deny UI when an agent requests a sensitive action; options include allow-once, allow-always, reject-once, reject-always
- Agent state — Working (with optional plan and current activity), Idle, AwaitingPermission, ContextExhausted, Error
- Plan steps — displayed with status (Pending / InProgress / Completed / Failed) and priority
- Context warning — chips appear when context falls below 20%
- Auto-title — session title generated in the background after the first message
- Scroll-to-bottom button — appears when scrolled up in a live feed
- Agent discovery — detects whether Claude Code and Codex are installed on the host
- Backend: Rust, async Tokio.
ship-serverserves the roam WebSocket endpoint and hosts the captain and mate MCP servers.ship-coremanages session lifecycle, ACP connections, and git worktrees.ship-typesdefines all shared types (Facet-derived, no serde). - Frontend: React + Radix Themes + Vanilla Extract. TypeScript bindings are generated from the Rust service trait via
cargo xtask codegen. - RPC: roam — Rust traits as the schema, bidirectional streaming over WebSocket.
- Worktrees: Created under
.ship/@{id}/on aship-{id}branch when a session starts.
Default server mode serves the built frontend from frontend/dist:
cd frontend
pnpm build
cd ..
just run
# or: cargo run --bin ship -- serveDev mode starts Vite behind the backend and preserves the proxy + HMR workflow:
cargo run --bin ship -- serve --devTypecheck:
pnpm exec tsgo --noEmitLint / format:
pnpm exec oxlint frontend/src/
pnpm exec oxfmt --write frontend/src/Regenerate TypeScript bindings after changing ship-service:
cargo xtask codegen