COSAW Under the Hood: frao-cosaw-mcp and the Phase-Orchestrator

COSAW Under the Hood: frao-cosaw-mcp and the Phase-Orchestrator

Reference companion to "Everything Is a Prompt: Context Engineering with COSAW". That article argues for the workflow. This one specifies the machinery — every mode, every tool, every step of self-injection.

Two tools, one division of labor

The COSAW runtime is two programs that never overlap.

frao-cosaw-mcp is the control plane. It is a Go server compiled to a single static binary. It knows how to create workflows, register phases, launch task agents, watch them, close them, and route messages between them. It does not own long-term state on its own terms. It writes workflow state to the .cosaw/ directory inside each repo and reports up to the phase-orchestrator.

phase-orchestrator is the system of record. It is a Rust service built on axum and tokio. It owns briefs, tasks, and resource metrics in SQLite. It exposes a REST API and streams live metrics over Server-Sent Events to a dashboard. It drives cosaw-mcp through an interface called CosawBackend that has two implementations — one shells to the cosaw-mcp CLI, the other speaks to the cosaw-mcp MCP process.

The division is deliberate. The orchestrator persists and displays. The MCP executes. Neither edits application code. Neither builds images. They orchestrate and record.

The two modes of frao-cosaw-mcp

The binary runs in two roles. Both share the same Go engine and the same store, so the behavior is identical whichever transport you use.

cosaw-mcp mcp — main-session mode. This runs inside the main session’s Claude, registered through the repo’s .mcp.json. The main session’s Claude sees the cosaw_* tools. This instance holds the launch tools. It can create workflows, add phases, launch tasks, read task status, send bus messages, monitor context, and migrate the main session.

cosaw-mcp agent — task-agent mode. This runs inside each task agent’s Claude, wired through the worktree’s .mcp.json. This instance holds the report tools. It reports status and completion back to the main session over the shared bus. It cannot launch anything. It cannot see other agents.

The same engine functions back both the MCP tools and the control-plane CLI. One implementation, two transports. This is why the phase-orchestrator’s REST proxy can call the CLI while the main session uses the MCP tools and both behave identically.

Self-injection, exactly

This is the mechanism that delivers a brief into a task agent’s model context. Read it in order.

The two parties

  • The main session runs cosaw-mcp mcp. It holds cosaw_task_launch and the tmux client.
  • The task agent runs cosaw-mcp agent. It is a fresh Claude in its own worktree. It owns its composer and its Enter key.
  • Neither can call the other’s model. There is no API from the main session into the agent’s Claude. The only path in is the agent’s own interface.

The launch sequence

  1. cosaw_task_launch creates the worktree, the branch, and the tmux session. The session is named phase-<phase>-<slug>.
  2. tmux starts claude --dangerously-skip-permissions inside that session. The agent boots with its own empty context and its own .mcp.json exposing cosaw-mcp agent.
  3. The main session writes the prompt plus the brief to a temp file. The file is plain text. Nothing is pre-processed.
  4. tmux load-buffer reads the file as raw bytes into the tmux buffer.
  5. tmux paste-buffer writes those bytes into the agent’s pane. The brief lands in the agent’s composer. This is placement. The main session has put the instruction into the agent’s environment. It has not touched the model.
  6. The main session sends tmux send-keys Enter as a separate invocation. This is the FM13 rule. Text and Enter are two distinct events. A compound send-keys "text" Enter is interpreted by the TUI as Shift+Enter, which inserts a newline instead of submitting. The separate Enter is what submits.
  7. The agent’s own TUI processes that Enter. The text in its composer becomes a submitted message. The instruction enters the model’s context through the agent’s own action. This is self-injection. The orchestrator placed the text. The agent injected it into itself.
  8. The main session polls. capture-pane reads the agent’s screen. It looks for processing indicators first: Thought, Reading, Forging, Skill, Unravelling, or ● Bash(. A bare prompt character is not proof of work. The brief may have landed without being processed.
  9. If no processing appears within a few seconds the main session resends Enter. If that fails it re-pastes the buffer. The escalation is explicit. No magic sleeps.
  10. Progress is read the same way. capture-pane grabs the live screen. The main session watches without ever attaching.
  11. Completion is a contract. The agent stages its work with git add -A and prints ===PHASE_COMPLETE===. Before closing a session the main session confirms the agent is actually idle. It checks process state. It samples CPU twice to confirm ticks are flat. It looks for work appearing after the signal. A session that printed the signal and then started new work is treated as running.
  12. Closing is graceful. /quit first, then kill-session. Force-killing is a last resort.

The distinction in one sentence

The main session injects instructions into the agent’s environment. The agent injects them into its own context. Both run the same binary in different roles. Neither ever calls the other’s model.

The message bus

The bus is a shared filesystem structure under .cosaw/<workflow>/bus/. It contains a registry of sessions, an append-only audit log, and per-recipient inboxes. Every write is flock-protected. Every read skips partial or corrupt lines rather than failing.

The design choice is deliberate: no open ports. The bus works identically on the host and inside the orchestrator container’s mounted workspace. It is how otherwise-isolated agents exchange messages — reports back to the main session, messages between sessions — without ever sharing a model context.

Context monitoring

cosaw-mcp tracks the context size of every session — task agents and the main session alike. It reads the statusline JSON Claude Code pipes to the statusline script, falling back to a tmux scrape when needed.

  • It records growth deltas per session.
  • It warns at soft thresholds (default 300000,350000).
  • At the hard threshold (default 500000) it runs a ladder: compact once, then migrate or flag.
  • The whole monitor is idle-gated. Running work is never disturbed.
  • Thresholds are parameterizable live through cosaw_context_thresholds_set or the context-thresholds set CLI.

The .cosaw/ store

Every workflow leaves a durable record at .cosaw/<workflow>/ inside the repo:

Artifact Content
workflow.json workflow id, repo, purpose, status, main session, phases, events
STATUS.md lifecycle log: planning → greenlit → in-flight → merged → archived
briefs/ canonical phase briefs, one file per phase
MIGRATION.md the continuation prompt that started the workflow
bus/ the message bus: registry, audit, inboxes

The .cosaw/ directory is committed to git. It is the durable, portable record — a workflow resumes from it on any host that has the repo.

Security model

  • Identifier allowlist. Workflow, phase, slug, and session identifiers are validated against ^[A-Za-z0-9_-]+$ before any filesystem use. Path traversal is rejected and tested.
  • Argument arrays. Commands execute as exec.Command argument arrays, never shell strings.
  • Buffer paste. Briefs are delivered via tmux buffer paste with zero shell interpretation.
  • Private tmux socket. The recommended deployment runs a private tmux server per orchestrator (COSAW_TMUX_SOCKET with a unique label) rather than mounting a shared host socket.
  • Volume type. flock() requires a filesystem that supports it — use a local or hostPath volume, not NFS.

Caveman mode

Task agents can operate in a terse, token-efficient style that cuts roughly 75% of output tokens. The caveman skill is embedded in the binary and deployed into each task agent’s worktree at launch. task_launch appends a ## Caveman Mode block to the brief. It is on by default for task agents; pass caveman:false to disable.

The control-plane CLI

The phase-orchestrator’s REST proxy drives cosaw-mcp through a CLI instead of JSON-RPC. This is a frozen contract — the subcommand names and JSON output shapes are stable. JSON goes to stdout. Errors go to stderr as plain text with exit code 1.

cosaw-mcp workflow-create <purpose>
cosaw-mcp workflow-list
cosaw-mcp workflow-get <workflow_id>
cosaw-mcp phase-add <workflow_id> <phase> <slug> <brief>
cosaw-mcp phase-list <workflow_id>
cosaw-mcp phase-brief <workflow_id> <phase>
cosaw-mcp task-launch <repo> <phase> <slug> <brief> [branch_base] [caveman]
cosaw-mcp task-status <session>
cosaw-mcp task-close <session>
cosaw-mcp context-status <session>
cosaw-mcp context-history <session> [--limit N]
cosaw-mcp context-thresholds [set --soft=.. --compact=.. --migrate=..]

Every subcommand reuses the exact store and engine functions the MCP tools call. No orchestration logic is duplicated.

Environment variables

Variable Mode Purpose
COSAW_REPO mcp repo root where .cosaw/ lives
COSAW_WORKFLOW_ID mcp bound workflow, default for tools
COSAW_WORKFLOW_DIR agent absolute path to .cosaw/<workflow>/
COSAW_SESSION both this instance’s session name (main reserved)
COSAW_ROLE both main or agent
COSAW_ORCHESTRATOR mcp phase-orchestrator base URL for reporting
COSAW_AGENT_CMD mcp command typed into task sessions
COSAW_MAIN_TMUX_SESSION mcp tmux session the main Claude runs in
COSAW_TMUX_SOCKET both tmux socket label
COSAW_CAVEMAN agent 1 enables Caveman Mode
COSAW_CTX_* mcp context monitor thresholds, poll interval, snapshot dir

The phase-orchestrator

A Rust service (axum 0.8, tokio, SQLite via sqlx) on port 9763. It is the system of record for the COSAW runtime: briefs, tasks, and resource metrics. It exposes a dashboard and a REST API.

Launch modes

Mode Behavior
direct Spawns the command as a child process; tracks the PID via /proc
detached Same as direct but disowned via setsid; survives service restarts
tmux-attach Opens a new window in an existing tmux session
tmux-dedicated Creates a shared phase-orchestrator tmux session; one window per task
tmux-per-task One brand new session per task, with companion windows for htop/btop and live process detail

Resource monitoring

CPU sampled every second, memory every 3 seconds, I/O every 5 seconds via /proc. The dashboard streams it over SSE, and per-task history holds the last 300 data points.

The CosawBackend trait

The orchestrator drives cosaw-mcp through an interface with two implementations — the control-plane CLI and the MCP process. The app-engine talks to one REST surface regardless of which backend is live. cosaw-mcp reports workflow state back through cosaw_workflow_report, keeping the dashboard and the .cosaw/ store in sync.

The app-engine integration

Frao App Engine is an agent-driven PaaS. When a user submits a prompt, the app-engine launches a COSAW workflow through the phase-orchestrator over REST. The flow:

  1. The app-engine POSTs a workflow with the app’s workspace path and the prompt.
  2. The phase-orchestrator maps host paths to container paths and asks cosaw-mcp to launch the main session.
  3. The main session decomposes the requirement and launches task agents in worktrees under /workspace.
  4. Task agents implement and signal ===PHASE_COMPLETE===.
  5. The workflow reaches merged. The app builds, deploys, and goes live.
  6. On build or health failure the workflow relaunches with feedback, bounded to a few iterations.

Host and container paths are translated. Host orchestrator sessions live on the default tmux socket; container orchestrator sessions live on a private cosaw-orch socket. The two are never mixed.

Docker wiring

The orchestrator image bakes in the orchestrator binary, cosaw-mcp, Claude, tmux, and fish. It runs as UID 1337. The entrypoint installs cosaw-mcp from the mounted workspace binary if present, or builds it from source.

Mount Container path Purpose
~/prhyme/projects /workspace repos, worktrees, cosaw-mcp source
/tmp/tmux-1337 /tmp/tmux-1337 host tmux socket (hybrid ops)
/var/run/docker.sock /var/run/docker.sock spawn task containers
~/.claude /mnt/host-claude read-only Claude config

The whole stack is one image. It moves to any Docker host as a unit.

Full MCP tool reference

Group Tools
Workflow & phases cosaw_workflow_create/get/list, set_status, log, report, write_doc; cosaw_phase_add/list/brief
Task lifecycle cosaw_task_launch, wait, status, read, tail, close, interrupt
Bus cosaw_bus_send, broadcast, read, who
Context monitor cosaw_context_status/history/probe/thresholds/thresholds_set
Migration cosaw_migrate_main, cosaw_main_session_get/set
Session & plugins cosaw_session_command, cosaw_plugin_enable

Ports and databases

Service Port Store
Phase-orchestrator 9763 SQLite phase-orchestrator.db
Advisor dashboard 9753 SQLite advisor.db
cosaw-mcp .cosaw/<workflow>/ + bus

The start of this architecture is a single observation: a model reasons only over the context you give it. Everything upstream of that observation — the phases, the isolation, the tmux plumbing, the containers — exists to make that context excellent on purpose.