diff --git a/configuration/context-layers.mdx b/configuration/context-layers.mdx
index 2496371d94b..af1afb883c9 100644
--- a/configuration/context-layers.mdx
+++ b/configuration/context-layers.mdx
@@ -83,6 +83,7 @@ harness; spawn-time plumbing delivers it:
| **Claude Code** | managed region of `~/.claude/CLAUDE.md` | Claude Code natively reads `~/.claude/CLAUDE.md` at session start |
| **Codex** | `~/.overdeck/context/codex-global.md` | At spawn, Overdeck seeds the agent's `$CODEX_HOME/AGENTS.md` from this file — Codex natively reads `AGENTS.md`, never the render artifact itself |
| **Pi** | `~/.overdeck/context/pi-global.md` | Injected into the session's system-prompt context at spawn. Pi does natively support a global `~/.pi/agent/AGENTS.md`; Overdeck deliberately does not write there — the global layer is delivered per-spawn instead, leaving `~/.pi/agent/` entirely yours |
+| **Prime Agent** | `~/.overdeck/context/prime-agent-global.md` | Injected into the root RPC session's supplemental system prompt at spawn; Overdeck never writes `~/.prime/agent` |
The `pi-global.md` / `codex-global.md` files are **Overdeck-owned render
artifacts** — the harnesses do not know about them. They exist so the global
@@ -133,7 +134,8 @@ Write completion markers via `/pan-done` when ready for review.
{{/harness:pi}}
```
-Blocks may be stacked to target several harnesses; `{{#harness:codex}}` is
+Use `{{#harness:prime-agent}}...{{/harness:prime-agent}}` for Prime-only
+content. Blocks may be stacked to target several harnesses; `{{#harness:codex}}` is
also recognized.
## CLI
diff --git a/configuration/harnesses.mdx b/configuration/harnesses.mdx
index ae636054363..379ad43e16f 100644
--- a/configuration/harnesses.mdx
+++ b/configuration/harnesses.mdx
@@ -12,7 +12,7 @@ that runs in tmux. The **model** is what the binary calls. They are picked
independently per spawn.
-**Supported today:** `claude-code` (default), `ohmypi`, `codex`, `acp`, and `kimi-code`. For the wider
+**Supported today:** `claude-code` (default), `ohmypi`, `codex`, `acp`, `kimi-code`, and `prime-agent`. For the wider
field of coding-agent harnesses Overdeck could adopt next — and how each one handles
skills, MCP, and AGENTS.md — see the [Harness Landscape](/reference/harness-landscape).
@@ -24,6 +24,7 @@ skills, MCP, and AGENTS.md — see the [Harness Landscape](/reference/harness-la
| `codex` | [OpenAI Codex CLI](https://github.com/openai/codex) (`codex`) | OpenAI-model work | Uses persistent `codex app-server` sessions by default; requires Codex CLI `0.144.0` or newer. |
| `acp` | [Kimi Code CLI](https://www.kimi.com/code/docs/en/kimi-code-cli/guides/getting-started.html) (`kimi`) | Opt-in Kimi work | Uses the open Agent Client Protocol over a persistent authenticated host; Kimi is the first wired ACP agent. |
| `kimi-code` | [Kimi Code CLI](https://www.kimi.com/code/docs/en/kimi-code-cli/guides/getting-started.html) (`kimi`) | Kimi models (built-in default) | Drives Kimi's own native terminal UI directly — no ACP host, no Anthropic-compatibility endpoint. Kimi models only. Set `providerHarnesses.kimi` to `acp` or `claude-code` to opt back out. |
+| `prime-agent` | Prime Agent (`prime-agent`) | Opt-in multi-provider RPC work and conversations | Overdeck owns one persistent root RPC process per session; supported versions are `>=0.1.0 <1.0.0`. |
## Composer commands across harnesses
@@ -60,6 +61,39 @@ Managed Codex sessions use an isolated `CODEX_HOME`. On initialization and resum
receive neither override, so Claude Code's native context behavior remains in
control.
+### Prime Agent RPC (alternative)
+
+Install a compatible Prime Agent release with the upstream installer, then verify it:
+
+```bash
+curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
+prime-agent --version # must be >=0.1.0 and <1.0.0
+prime-agent --help # must list --mode rpc
+pan doctor
+```
+
+If the binary is outside `PATH`, set `primeAgent.binaryPath` in
+`~/.overdeck/config.yaml`. Configure the provider's API credentials through
+the provider's normal environment variables or Overdeck provider settings.
+Overdeck maps Anthropic models to `anthropic`, OpenAI models to `openai`, and
+Google models to `google`; an unknown provider/model pair fails instead of
+silently switching harnesses.
+
+Overdeck owns the persistent root RPC process, session directory, resume,
+delivery, abort, and termination. A detached session resumes only when its
+recorded model, provider, and workspace match. Otherwise Overdeck starts a
+fresh session or reports a recovery error. Prime schedules, heartbeats,
+observation, autonomous continuation, and cross-agent messaging are disabled
+because Cloister owns that lifecycle. RLM children remain internal tool work
+attributed to the root session and cannot claim Overdeck tasks.
+
+Prime's worker/kernel separation is an execution boundary, not a security
+sandbox. Use the same workspace and credential isolation you use for other
+coding harnesses. Overdeck writes managed context only under
+`~/.overdeck/context`; it never changes `~/.prime/agent`. To uninstall, stop
+Prime sessions, remove `primeAgent` from the Overdeck config, and remove the
+upstream `prime-agent` package with the package manager that installed it.
+
### oh-my-pi / ohmypi (alternative)
- Adds RPC mode (`omp --mode rpc`) so Overdeck can write structured commands to
diff --git a/docs/HARNESSES.md b/docs/HARNESSES.md
index 743316de226..2fb27c89f71 100644
--- a/docs/HARNESSES.md
+++ b/docs/HARNESSES.md
@@ -6,6 +6,6 @@
| Topic | Where it lives now |
| --- | --- |
-| Operational guide — supported harnesses (`claude-code`, `pi`), install, picker locations, ToS rules, troubleshooting | [`configuration/harnesses.mdx`](../configuration/harnesses.mdx) |
+| Operational guide — supported harnesses, including managed Prime Agent RPC sessions, install, picker locations, security rules, and troubleshooting | [`configuration/harnesses.mdx`](../configuration/harnesses.mdx) |
| Planning survey — 13 coding-agent harnesses, extensibility mechanisms, adoption implications | [`reference/harness-landscape.mdx`](../reference/harness-landscape.mdx) |
| Proposal — loading curated skill bundles into a single conversation | [`reference/template-conversations.mdx`](../reference/template-conversations.mdx) |
diff --git a/docs/MODEL_ROUTING.md b/docs/MODEL_ROUTING.md
index 6e867424e04..47046a93f84 100644
--- a/docs/MODEL_ROUTING.md
+++ b/docs/MODEL_ROUTING.md
@@ -29,6 +29,15 @@ When Overdeck needs to make an LLM call, it resolves the model through this chai
Overdeck supports seven LLM providers:
+### Prime Agent provider mapping
+
+The `prime-agent` harness receives an explicit provider and model on every
+launch. Anthropic routes to `anthropic`, OpenAI routes to `openai`, and Google
+routes to `google`. Unsupported mappings fail loudly; Overdeck does not pick a
+different model or harness. Provider authentication remains provider-owned.
+Using Claude Pro or Max subscription credentials through Prime Agent bills as
+extra API usage and does not consume the Claude plan quota.
+
| Provider | Auth Method | Notes |
|----------|-------------|-------|
| **Anthropic** | Claude Code subscription login (default, always enabled) | Opus 4.6, Sonnet 4.6, Haiku 4.5 |
diff --git a/docs/TELEMETRY.md b/docs/TELEMETRY.md
index 83c36bd3bfc..7914f359d38 100644
--- a/docs/TELEMETRY.md
+++ b/docs/TELEMETRY.md
@@ -79,7 +79,7 @@ the public contract and lists every allowed value.
| `duration_bucket` | `under_100ms`, `100ms-999ms`, `1s-9s`, `10s+` |
| `forge` | `github`, `gitlab` |
| `fork_kind` | `summary`, `handoff`, `plain` |
-| `harness` | `claude-code`, `ohmypi`, `codex`, `acp`, `kimi-code` |
+| `harness` | `claude-code`, `ohmypi`, `codex`, `acp`, `kimi-code`, `prime-agent` |
| `merge_kind` | `pipeline` |
| `model_family` | `claude`, `gpt`, `gemini`, `kimi`, `minimax`, `glm`, `mimo`, `other` |
| `pipeline_stage` | `work_done`, `review_passed`, `verification_passed`, `merged`, `closed_out` |
diff --git a/docs/evidence/pan-3668/prime-agent-runtime-picker.png b/docs/evidence/pan-3668/prime-agent-runtime-picker.png
new file mode 100644
index 00000000000..d9b0ee91312
Binary files /dev/null and b/docs/evidence/pan-3668/prime-agent-runtime-picker.png differ
diff --git a/docs/prime-agent-no-loss-audit.md b/docs/prime-agent-no-loss-audit.md
new file mode 100644
index 00000000000..9eafb43ae35
--- /dev/null
+++ b/docs/prime-agent-no-loss-audit.md
@@ -0,0 +1,20 @@
+# Prime Agent No-Loss Audit
+
+Every existing harness remains present while `prime-agent` gains an explicit home. The mechanical test reads each canonical surface below.
+
+| # | Surface | Canonical location | Prime disposition | Existing harnesses preserved | Verified |
+|---|---|---|---|---|---|
+| S1 | Harness union and known set | `packages/contracts/src/types.ts` | explicit literal | yes | [x] |
+| S2 | Behavior map | `packages/contracts/src/harness-behavior.ts` | `PRIME_AGENT_BEHAVIOR` | yes | [x] |
+| S3 | Config harness schema | `src/lib/config-yaml/schema.ts` | accepted role/provider harness | yes | [x] |
+| S4 | CLI help and composer manifest | `src/cli/index.ts`, `packages/contracts/src/composer-commands.generated.ts` | listed | yes | [x] |
+| S5 | Provider resolution | `src/lib/prime-agent/provider-map.ts` | strict table, no fallback | unchanged | [x] |
+| S6 | Runtime registry | `src/lib/runtimes/index.ts` | registered adapter | yes | [x] |
+| S7 | Artifact, telemetry, flywheel, context preview | `packages/contracts/src/{artifacts,telemetry,flywheel,context-layers}.ts` | explicit literal | yes | [x] |
+| S8 | Transcript registry | `src/lib/conversations/transcript-adapter.ts` | Prime adapter | yes | [x] |
+| S9 | Cost source | `src/lib/overdeck/cost.ts` | `prime_agent` source | yes | [x] |
+| S10 | Context renderer | `src/lib/context-layers/harness.ts` | Prime marker and artifact | yes | [x] |
+| S11 | Work/conversation lifecycle | `src/lib/agents/runtime-command.ts`, `src/lib/overdeck/conversation-runtime.ts` | persistent RPC | yes | [x] |
+| S12 | Delivery and recovery | `src/lib/prime-agent/{session-controller,session-resume}.ts` | correlated RPC and strict resume | unchanged | [x] |
+| S13 | Dashboard API and frontend | `src/dashboard/` | identity, activity, usage, picker, badges, filters | yes | [x] |
+| S14 | Operator documentation | `configuration/harnesses.mdx`, `configuration/context-layers.mdx` | setup and boundary documented | yes | [x] |
diff --git a/docs/prime-agent-verification.md b/docs/prime-agent-verification.md
new file mode 100644
index 00000000000..c60f2c0bcdd
--- /dev/null
+++ b/docs/prime-agent-verification.md
@@ -0,0 +1,35 @@
+# Prime Agent verification
+
+Verified on 2026-08-12 for PAN-3668 with Prime Agent 0.7.2 and Node.js 22.22.0.
+
+## Automated gates
+
+| Gate | Result |
+| --- | --- |
+| `npm run build` | Passed, including contracts, CLI, dashboard server, dashboard frontend, and dist-externals checks. |
+| `npm run typecheck` | Passed. The dashboard ratchet fell from 30 known errors to 28 after the Prime runtime types were propagated. |
+| `npm run lint` | Passed through the focused rerun after removing the new Prime transcript-adapter cycle. |
+| `npm test` | 13,664 tests passed and 51 skipped. Two unrelated 5-second setup hooks timed out under full-suite load; both files passed immediately in a focused rerun (5/5 tests). |
+| Prime focused and no-loss tests | Passed, including the live smoke, runtime registry, parser, policy, picker, transcript, artifact, telemetry, and canonical-harness matrices. |
+
+The opt-in live test is `tests/integration/prime-agent-smoke.test.ts`. Run it with `OVERDECK_PRIME_AGENT_LIVE=1 npx vitest run tests/integration/prime-agent-smoke.test.ts`. Provider and model overrides are available through `OVERDECK_PRIME_AGENT_PROVIDER` and `OVERDECK_PRIME_AGENT_MODEL`.
+
+## Live Prime Agent smoke
+
+The smoke used a disposable directory and the installed `prime-agent` binary. It verified:
+
+1. RPC launch with an explicit provider, model, working directory, and session directory.
+2. Correlated `get_state`, `prompt`, `steer`, `get_session_stats`, `abort`, `get_messages`, and resumed-session requests.
+3. A real Bash tool execution that wrote the expected marker inside the disposable directory.
+4. Non-zero token accounting, non-negative cost accounting, graceful stop, and resume from the saved session file.
+5. The freshly built doctor diagnostic: `Prime Agent: v0.7.2 (RPC mode available)`.
+
+## Dashboard boot and UI
+
+The production dashboard bundle booted under Node.js 22 on isolated port 33668 with an isolated `OVERDECK_HOME`, `OVERDECK_DISABLE_DEACON=1`, and `OVERDECK_NO_RESUME=1`. `/api/health` returned `status: ok`, `mode: peer`, and the feature branch build metadata. The log order confirmed the HTTP listener before boot-cache completion, and Cloister stayed disabled.
+
+Playwright opened the isolated dashboard, enabled the throwaway harness-permutation setting, opened the runtime picker, and confirmed that `Prime Agent` was visible with the description `Managed persistent Prime RPC session`. The inspected screenshot is [the Prime Agent runtime picker](evidence/pan-3668/prime-agent-runtime-picker.png).
+
+## Release decision
+
+The Prime Agent path is verified for managed launch, RPC delivery, tool use, transcript persistence, cost reporting, stop/resume, diagnostics, and operator-facing selection. The only full-suite exceptions were two unrelated setup-hook timeouts that passed outside the saturated run.
diff --git a/packages/contracts/src/artifacts.ts b/packages/contracts/src/artifacts.ts
index 97ca3804b4a..10c3733d337 100644
--- a/packages/contracts/src/artifacts.ts
+++ b/packages/contracts/src/artifacts.ts
@@ -12,7 +12,7 @@ export type ArtifactHash = typeof ArtifactHash.Type
export const ArtifactAgentRole = Schema.Literals(["plan", "work", "review", "test", "ship", "flywheel", "user"])
export type ArtifactAgentRole = typeof ArtifactAgentRole.Type
-export const ArtifactAgentHarness = Schema.Literals(["claude-code", "pi", "ohmypi", "codex", "acp", "kimi-code", "user"])
+export const ArtifactAgentHarness = Schema.Literals(["claude-code", "pi", "ohmypi", "codex", "acp", "kimi-code", "prime-agent", "user"])
export type ArtifactAgentHarness = typeof ArtifactAgentHarness.Type
export const ArtifactLifecycleState = Schema.Literals([
diff --git a/packages/contracts/src/composer-commands.generated.ts b/packages/contracts/src/composer-commands.generated.ts
index 48f6a47931e..dedd7240131 100644
--- a/packages/contracts/src/composer-commands.generated.ts
+++ b/packages/contracts/src/composer-commands.generated.ts
@@ -33,7 +33,7 @@ export const COMPOSER_COMMAND_MANIFEST: ComposerCommandManifestEntry[] = [
{"id":"pan-backlog","path":["backlog"],"display":"/pan backlog","description":"Backlog sequencer management","args":[],"options":[],"aliases":[],"category":"Lifecycle"},
{"id":"pan-backlog-write-sequence","path":["backlog","write-sequence"],"display":"/pan backlog write-sequence","description":"Validate a SequenceDoc JSON file and write it to .pan/backlog/sequence.md (triggers auto-commit)","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--project-root ","description":"Project root (default: cwd)","required":true,"valueHint":"path"}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-backlog-forecast","path":["backlog","forecast"],"display":"/pan backlog forecast","description":"Pickup forecast (waves / needs-planning / cohort / stats) from sequence.md + SQLite — no HTTP (sandbox-safe)","args":[],"options":[{"flags":"-n, --n ","description":"Wave size (default 5)","required":true,"valueHint":"n"}],"aliases":[],"category":"Lifecycle"},
- {"id":"pan-plan","path":["plan"],"display":"/pan plan","description":"Planning lifecycle commands","args":[{"name":"id","required":false,"variadic":false}],"options":[{"flags":"--auto","description":"Run non-interactive planning; inferred choices are recorded in plan.autoDecisions[]","required":false,"valueHint":null},{"flags":"--auto-start","description":"[deprecated: use pan start ] After planning completes, automatically start the work agent — used by autonomous orchestrators","required":false,"valueHint":null},{"flags":"--probe","description":"Add an adversarial pre-finalize probe pass to the planning prompt","required":false,"valueHint":null},{"flags":"--model ","description":"Model to use for the planning role","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Planning effort: low | medium | high","required":true,"valueHint":"level"},{"flags":"--remote","description":"Use remote planning workspace (Fly.io)","required":false,"valueHint":null},{"flags":"--local","description":"Use local planning workspace","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
+ {"id":"pan-plan","path":["plan"],"display":"/pan plan","description":"Planning lifecycle commands","args":[{"name":"id","required":false,"variadic":false}],"options":[{"flags":"--auto","description":"Run non-interactive planning; inferred choices are recorded in plan.autoDecisions[]","required":false,"valueHint":null},{"flags":"--auto-start","description":"[deprecated: use pan start ] After planning completes, automatically start the work agent — used by autonomous orchestrators","required":false,"valueHint":null},{"flags":"--probe","description":"Add an adversarial pre-finalize probe pass to the planning prompt","required":false,"valueHint":null},{"flags":"--model ","description":"Model to use for the planning role","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Planning effort: low | medium | high","required":true,"valueHint":"level"},{"flags":"--remote","description":"Use remote planning workspace (Fly.io)","required":false,"valueHint":null},{"flags":"--local","description":"Use local planning workspace","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-plan-finalize","path":["plan","finalize"],"display":"/pan plan finalize","description":"Mark the workspace plan as proposed and promote it to canonical state","args":[],"options":[{"flags":"-w, --workspace ","description":"Workspace path (defaults to cwd, walks up to find .pan/)","required":true,"valueHint":"path"},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null},{"flags":"--no-promote","description":"Skip auto-promotion to main; leave spec at status=proposed for manual Done","required":false,"valueHint":null},{"flags":"--no-quality-lint","description":"Emergency bypass for xBRIEF quality lint during finalize","required":false,"valueHint":null},{"flags":"--no-prd","description":"Bypass the PRD-first gate for a genuinely trivial issue (loud; prefer writing the PRD)","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-plan-done","path":["plan","done"],"display":"/pan plan done","description":"Complete planning — promote the xBRIEF and transition the issue to Planned","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--no-prd","description":"Bypass the PRD-first gate for a genuinely trivial issue (loud; prefer writing the PRD)","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-tell","path":["tell"],"display":"/pan tell","description":"Send message to running agent","args":[{"name":"id","required":true,"variadic":false},{"name":"message","required":true,"variadic":false}],"options":[],"aliases":[],"category":"Lifecycle"},
@@ -59,8 +59,8 @@ export const COMPOSER_COMMAND_MANIFEST: ComposerCommandManifestEntry[] = [
{"id":"pan-wipe","path":["wipe"],"display":"/pan wipe","description":"Destructive: removes workspace files, kills processes, deletes branches, clears review state, and resets tracker status","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--force","description":"Skip confirmation","required":false,"valueHint":null},{"flags":"-y, --yes","description":"Skip confirmation","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-destroy","path":["destroy"],"display":"/pan destroy","description":"Alias for workspace destroy: remove the issue workspace worktree and branch","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--force","description":"Force removal even with uncommitted changes","required":false,"valueHint":null},{"flags":"--project ","description":"Explicit project path (overrides registry)","required":true,"valueHint":"path"}],"aliases":[],"category":"Workspace"},
{"id":"pan-close","path":["close"],"display":"/pan close","description":"Verify, clean up, and close issue on tracker","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--force","description":"Skip confirmation prompt","required":false,"valueHint":null},{"flags":"--json","description":"Output as JSON","required":false,"valueHint":null},{"flags":"--accept-review","description":"Accept a missed DoD row 1 (Review passed) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-tests","description":"Accept a missed DoD row 2 (Tests passed) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-verification","description":"Accept a missed DoD row 3 (Verification green on the branch) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-merged","description":"Accept a missed DoD row 4 (Merged to main) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-post-merge","description":"Accept a missed DoD row 5 (Post-merge lifecycle ran) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-main-verify","description":"Accept a missed DoD row 6 (Verified on main) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-ship","description":"Accept a missed DoD row 7 (Version strings propagated) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--accept-deploy","description":"Accept a missed DoD row 8 (Deployed) — recorded as an explicit override in the close-out record","required":false,"valueHint":null},{"flags":"--abandon ","description":"Record an abandoned disposition (PAN-3211): skip the DoD gate and close without landing evidence. Operator-conversation only; cannot be combined with --accept-* flags","required":true,"valueHint":"reason"},{"flags":"--residue ","description":"Record a residue disposition (PAN-3396): close stale convention PRs/MRs, skip the DoD gate with verified evidence, and mark terminal without merge claim. Operator-conversation only; cannot be combined with --abandon or --accept-* flags","required":true,"valueHint":"reason"}],"aliases":[],"category":"Lifecycle"},
- {"id":"pan-start","path":["start"],"display":"/pan start","description":"Create workspace and spawn agent for an issue","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--model ","description":"Work model to use and persist for later respawns (defaults to Cloister config)","required":true,"valueHint":"model"},{"flags":"--swarm ","description":"Per-issue swarm policy: off | auto | always","required":true,"valueHint":"mode"},{"flags":"--review-mode ","description":"Per-issue review mode: quick | full | none","required":true,"valueHint":"mode"},{"flags":"--review-model ","description":"Per-issue review model override","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Claude Code effort: low | medium | high | xhigh | max (defaults to roles.work.effort)","required":true,"valueHint":"level"},{"flags":"--tier ","description":"Remote workspace resiliency tier: ephemeral | durable (defaults to remote.resiliency_tier)","required":true,"valueHint":"tier"},{"flags":"--dry-run","description":"Show what would be created","required":false,"valueHint":null},{"flags":"--shadow","description":"Enable shadow mode","required":false,"valueHint":null},{"flags":"--no-shadow","description":"Disable shadow mode","required":false,"valueHint":null},{"flags":"--remote","description":"Use remote workspace (Fly.io)","required":false,"valueHint":null},{"flags":"--local","description":"Use local workspace (explicit override)","required":false,"valueHint":null},{"flags":"--plan ","description":"Planning depth when no plan exists yet: interactive | auto | skip (default: config planning.default_mode, shipped default auto)","required":true,"valueHint":"mode"},{"flags":"--plan-model ","description":"Planning model override when this start auto-plans (defaults to roles.plan.model); the work model stays --model","required":true,"valueHint":"model"},{"flags":"--auto","description":"[deprecated: use --plan skip] Skip planning agent by synthesizing a minimal xBRIEF from the issue title/body","required":false,"valueHint":null},{"flags":"--force","description":"Clear paused and pending-operator-decision gates and start anyway","required":false,"valueHint":null},{"flags":"--fresh","description":"Drop the saved Claude session (non-destructive) and start a new one — replaces a live session too, so it recovers an inert agent without a separate pan kill","required":false,"valueHint":null},{"flags":"--host","description":"Bypass workspace docker stack-health gate and spawn on the host","required":false,"valueHint":null},{"flags":"--yes","description":"Confirm --host in non-interactive contexts","required":false,"valueHint":null},{"flags":"--off-book","description":"Allow one work-agent dispatch outside the active order book and log the override","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
- {"id":"pan-strike","path":["strike"],"display":"/pan strike","description":"Spawn strike agent(s) — implement and push a strike branch for Deacon to land through the verified merge door. Bypasses plan/review/test/ship.","args":[{"name":"ids","required":true,"variadic":true}],"options":[{"flags":"--model ","description":"Model override (defaults to roles.strike.model from config)","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Strike effort: low | medium | high | xhigh | max (default high)","required":true,"valueHint":"level"},{"flags":"--dry-run","description":"Print what would happen without spawning","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
+ {"id":"pan-start","path":["start"],"display":"/pan start","description":"Create workspace and spawn agent for an issue","args":[{"name":"id","required":true,"variadic":false}],"options":[{"flags":"--model ","description":"Work model to use and persist for later respawns (defaults to Cloister config)","required":true,"valueHint":"model"},{"flags":"--swarm ","description":"Per-issue swarm policy: off | auto | always","required":true,"valueHint":"mode"},{"flags":"--review-mode ","description":"Per-issue review mode: quick | full | none","required":true,"valueHint":"mode"},{"flags":"--review-model ","description":"Per-issue review model override","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Claude Code effort: low | medium | high | xhigh | max (defaults to roles.work.effort)","required":true,"valueHint":"level"},{"flags":"--tier ","description":"Remote workspace resiliency tier: ephemeral | durable (defaults to remote.resiliency_tier)","required":true,"valueHint":"tier"},{"flags":"--dry-run","description":"Show what would be created","required":false,"valueHint":null},{"flags":"--shadow","description":"Enable shadow mode","required":false,"valueHint":null},{"flags":"--no-shadow","description":"Disable shadow mode","required":false,"valueHint":null},{"flags":"--remote","description":"Use remote workspace (Fly.io)","required":false,"valueHint":null},{"flags":"--local","description":"Use local workspace (explicit override)","required":false,"valueHint":null},{"flags":"--plan ","description":"Planning depth when no plan exists yet: interactive | auto | skip (default: config planning.default_mode, shipped default auto)","required":true,"valueHint":"mode"},{"flags":"--plan-model ","description":"Planning model override when this start auto-plans (defaults to roles.plan.model); the work model stays --model","required":true,"valueHint":"model"},{"flags":"--auto","description":"[deprecated: use --plan skip] Skip planning agent by synthesizing a minimal xBRIEF from the issue title/body","required":false,"valueHint":null},{"flags":"--force","description":"Clear paused and pending-operator-decision gates and start anyway","required":false,"valueHint":null},{"flags":"--fresh","description":"Drop the saved Claude session (non-destructive) and start a new one — replaces a live session too, so it recovers an inert agent without a separate pan kill","required":false,"valueHint":null},{"flags":"--host","description":"Bypass workspace docker stack-health gate and spawn on the host","required":false,"valueHint":null},{"flags":"--yes","description":"Confirm --host in non-interactive contexts","required":false,"valueHint":null},{"flags":"--off-book","description":"Allow one work-agent dispatch outside the active order book and log the override","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
+ {"id":"pan-strike","path":["strike"],"display":"/pan strike","description":"Spawn strike agent(s) — implement and push a strike branch for Deacon to land through the verified merge door. Bypasses plan/review/test/ship.","args":[{"name":"ids","required":true,"variadic":true}],"options":[{"flags":"--model ","description":"Model override (defaults to roles.strike.model from config)","required":true,"valueHint":"model"},{"flags":"--harness ","description":"Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)","required":true,"valueHint":"harness"},{"flags":"--effort ","description":"Strike effort: low | medium | high | xhigh | max (default high)","required":true,"valueHint":"level"},{"flags":"--dry-run","description":"Print what would happen without spawning","required":false,"valueHint":null}],"aliases":[],"category":"Lifecycle"},
{"id":"pan-strike-ready","path":["strike-ready"],"display":"/pan strike-ready","description":"Persist readiness for a pushed strike branch so Deacon can land it","args":[{"name":"id","required":true,"variadic":false}],"options":[],"aliases":[],"category":"Lifecycle"},
{"id":"pan-knowledge","path":["knowledge"],"display":"/pan knowledge","description":"Maintain or open the project OKF bundle","args":[{"name":"id","required":false,"variadic":false}],"options":[{"flags":"--focus ","description":"Run /okf study for a focused topic before syncing","required":true,"valueHint":"topic"},{"flags":"--retro","description":"Run /okf retro before syncing","required":false,"valueHint":null},{"flags":"--model ","description":"Model override (defaults to roles.knowledge.model from config)","required":true,"valueHint":"model"},{"flags":"--effort ","description":"Knowledge effort: low | medium | high | xhigh | max","required":true,"valueHint":"level"}],"aliases":[],"category":"Data"},
{"id":"pan-knowledge-open","path":["knowledge","open"],"display":"/pan knowledge open","description":"Open the project OKF bundle in the local knowledge viewer","args":[],"options":[{"flags":"--no-install","description":"Do not install @inkeep/open-knowledge when the ok binary is missing","required":false,"valueHint":null},{"flags":"--no-browser","description":"Start or reuse the viewer without opening a browser","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
@@ -266,7 +266,7 @@ export const COMPOSER_COMMAND_MANIFEST: ComposerCommandManifestEntry[] = [
{"id":"pan-merge","path":["merge"],"display":"/pan merge","description":"Merge workflow helpers","args":[],"options":[],"aliases":[],"category":"Review"},
{"id":"pan-merge-cancel","path":["merge","cancel"],"display":"/pan merge cancel","description":"Cancel a pending Flywheel auto-merge during its cooldown window","args":[{"name":"id","required":true,"variadic":false}],"options":[],"aliases":[],"category":"Review"},
{"id":"pan-artifacts","path":["artifacts"],"display":"/pan artifacts","description":"Create, publish, validate, inspect, and open shared HTML artifacts","args":[],"options":[],"aliases":[],"category":"Data"},
- {"id":"pan-artifacts-create","path":["artifacts","create"],"display":"/pan artifacts create","description":"Validate and publish an HTML artifact","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--issue ","description":"Issue ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--workspace ","description":"Workspace ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--agent-role ","description":"Agent role: plan, work, review, test, ship, flywheel, or user","required":true,"valueHint":"role"},{"flags":"--agent-harness ","description":"Agent harness: claude-code, pi, ohmypi, codex, acp, kimi-code, or user","required":true,"valueHint":"harness"},{"flags":"--run-id ","description":"Run ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--session-id ","description":"Session ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--title ","description":"Artifact title","required":true,"valueHint":"title"},{"flags":"--description ","description":"Artifact description","required":true,"valueHint":"text"},{"flags":"--strict","description":"Enable strict validation warnings","required":false,"valueHint":null},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
+ {"id":"pan-artifacts-create","path":["artifacts","create"],"display":"/pan artifacts create","description":"Validate and publish an HTML artifact","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--issue ","description":"Issue ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--workspace ","description":"Workspace ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--agent-role ","description":"Agent role: plan, work, review, test, ship, flywheel, or user","required":true,"valueHint":"role"},{"flags":"--agent-harness ","description":"Agent harness: claude-code, pi, ohmypi, codex, acp, kimi-code, prime-agent, or user","required":true,"valueHint":"harness"},{"flags":"--run-id ","description":"Run ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--session-id ","description":"Session ID for artifact provenance","required":true,"valueHint":"id"},{"flags":"--title ","description":"Artifact title","required":true,"valueHint":"title"},{"flags":"--description ","description":"Artifact description","required":true,"valueHint":"text"},{"flags":"--strict","description":"Enable strict validation warnings","required":false,"valueHint":null},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
{"id":"pan-artifacts-publish","path":["artifacts","publish"],"display":"/pan artifacts publish","description":"Revalidate and republish an existing artifact when changes are pending","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--strict","description":"Enable strict validation warnings","required":false,"valueHint":null},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
{"id":"pan-artifacts-unshare","path":["artifacts","unshare"],"display":"/pan artifacts unshare","description":"Disable an artifact URL without deleting source, snapshots, or metadata","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--yes","description":"Confirm unsharing without an interactive prompt","required":false,"valueHint":null},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
{"id":"pan-artifacts-share","path":["artifacts","share"],"display":"/pan artifacts share","description":"Share an artifact through an external tunnel","args":[{"name":"file","required":true,"variadic":false}],"options":[{"flags":"--tunnel","description":"Request tunnel-backed sharing","required":false,"valueHint":null},{"flags":"--json","description":"Emit JSON result","required":false,"valueHint":null}],"aliases":[],"category":"Data"},
diff --git a/packages/contracts/src/context-layers.test.ts b/packages/contracts/src/context-layers.test.ts
index 71fff7bb155..c5644f1080f 100644
--- a/packages/contracts/src/context-layers.test.ts
+++ b/packages/contracts/src/context-layers.test.ts
@@ -96,7 +96,7 @@ describe("context dashboard contracts", () => {
it("names harness previews with shared harness values and fullPrompt", () => {
const harnesses: readonly Harness[] = CONTEXT_PREVIEW_HARNESSES
- expect(harnesses).toEqual(["claude-code", "ohmypi", "codex", "acp", "kimi-code"])
+ expect(harnesses).toEqual(["claude-code", "ohmypi", "codex", "acp", "kimi-code", "prime-agent"])
const parsed = decodePreviewResponse({
operation: "preview",
@@ -106,6 +106,7 @@ describe("context dashboard contracts", () => {
codex: "Codex rendered context",
acp: "ACP rendered context",
"kimi-code": "Kimi Code rendered context",
+ "prime-agent": "Prime Agent rendered context",
fullPrompt: "Overdeck injected prompt audit",
},
diagnostics: [],
@@ -115,6 +116,7 @@ describe("context dashboard contracts", () => {
expect(parsed.previews.ohmypi).toBe("oh-my-pi rendered context")
expect(parsed.previews.acp).toBe("ACP rendered context")
expect(parsed.previews["kimi-code"]).toBe("Kimi Code rendered context")
+ expect(parsed.previews["prime-agent"]).toBe("Prime Agent rendered context")
expect(parsed.previews.fullPrompt).toBe("Overdeck injected prompt audit")
})
diff --git a/packages/contracts/src/context-layers.ts b/packages/contracts/src/context-layers.ts
index c1787c5bed5..fd54cbcb8c1 100644
--- a/packages/contracts/src/context-layers.ts
+++ b/packages/contracts/src/context-layers.ts
@@ -2,7 +2,7 @@ import { Schema } from "effect"
import type { Harness } from "./types"
export const CONTEXT_LAYER_KINDS = ["global", "project", "workspace"] as const
-export const CONTEXT_PREVIEW_HARNESSES = ["claude-code", "ohmypi", "codex", "acp", "kimi-code"] as const satisfies readonly Harness[]
+export const CONTEXT_PREVIEW_HARNESSES = ["claude-code", "ohmypi", "codex", "acp", "kimi-code", "prime-agent"] as const satisfies readonly Harness[]
export const ContextLayerKind = Schema.Literals(CONTEXT_LAYER_KINDS)
export type ContextLayerKind = typeof ContextLayerKind.Type
@@ -141,6 +141,7 @@ export const ContextHarnessPreviews = Schema.Struct({
codex: Schema.String,
acp: Schema.String,
"kimi-code": Schema.String,
+ "prime-agent": Schema.String,
fullPrompt: Schema.String,
})
export type ContextHarnessPreviews = typeof ContextHarnessPreviews.Type
diff --git a/packages/contracts/src/flywheel.ts b/packages/contracts/src/flywheel.ts
index 61c1cad56d2..9fa9d29f5a4 100644
--- a/packages/contracts/src/flywheel.ts
+++ b/packages/contracts/src/flywheel.ts
@@ -6,7 +6,7 @@ export type FlywheelRunId = typeof FlywheelRunId.Type
export const FlywheelHttpUrl = Schema.String.check(Schema.isPattern(/^https?:\/\/\S+$/i))
export type FlywheelHttpUrl = typeof FlywheelHttpUrl.Type
-export const FlywheelHarness = Schema.Literals(["claude-code", "pi", "ohmypi", "codex", "acp", "kimi-code"])
+export const FlywheelHarness = Schema.Literals(["claude-code", "pi", "ohmypi", "codex", "acp", "kimi-code", "prime-agent"])
export interface FlywheelOrchestrator {
harness: typeof FlywheelHarness.Type
model: string
diff --git a/packages/contracts/src/harness-behavior.test.ts b/packages/contracts/src/harness-behavior.test.ts
new file mode 100644
index 00000000000..df9e8dbb776
--- /dev/null
+++ b/packages/contracts/src/harness-behavior.test.ts
@@ -0,0 +1,33 @@
+import { describe, expect, it } from "vitest"
+import { getHarnessBehavior, getRuntimeBehavior, PRIME_AGENT_BEHAVIOR } from "./harness-behavior"
+
+describe("Prime Agent harness behavior", () => {
+ it("uses Prime-specific protocol discriminators", () => {
+ expect(PRIME_AGENT_BEHAVIOR).toMatchObject({
+ displayName: "Prime Agent",
+ executableName: "prime-agent",
+ processNames: ["prime-agent"],
+ launchCommandKind: "prime-agent-rpc",
+ deliveryKind: "prime-agent-rpc",
+ readinessKind: "prime-agent-ready",
+ transcriptKind: "prime-agent-jsonl",
+ sessionIdSource: "prime-agent-session-id",
+ contextLayerKind: "prime-agent",
+ feedKind: "prime_agent",
+ workAgentMode: "prime-agent-rpc",
+ supportsPtySupervisor: false,
+ supportsChannelsBridge: false,
+ supportsConversationStreaming: true,
+ supportsPatchProjection: false,
+ usesRpcFifo: false,
+ usesCodexHome: false,
+ injectsPromptTimeMemory: true,
+ readyTimeoutSeconds: 120,
+ })
+ })
+
+ it("resolves without falling back to Claude Code", () => {
+ expect(getHarnessBehavior("prime-agent")).toBe(PRIME_AGENT_BEHAVIOR)
+ expect(getRuntimeBehavior("prime-agent")).toBe(PRIME_AGENT_BEHAVIOR)
+ })
+})
diff --git a/packages/contracts/src/harness-behavior.ts b/packages/contracts/src/harness-behavior.ts
index 50ca2f10f36..1bb56f2ab47 100644
--- a/packages/contracts/src/harness-behavior.ts
+++ b/packages/contracts/src/harness-behavior.ts
@@ -3,7 +3,7 @@ import type { Harness } from "./types"
export type RuntimeName = Harness
export type HarnessName = RuntimeName | "pi"
-export type HarnessLaunchCommandKind = "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" | "acp-host" | "kimi-code-tui"
+export type HarnessLaunchCommandKind = "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" | "acp-host" | "kimi-code-tui" | "prime-agent-rpc"
export type HarnessDeliveryKind =
| "pty-supervisor"
| "rpc-fifo"
@@ -11,6 +11,7 @@ export type HarnessDeliveryKind =
| "codex-app-server-rpc"
| "acp-host-rpc"
| "tmux-paste"
+ | "prime-agent-rpc"
export type HarnessReadinessKind =
| "claude-session-signal"
| "ohmypi-ready-file"
@@ -18,10 +19,11 @@ export type HarnessReadinessKind =
| "codex-app-server-ready"
| "acp-host-ready"
| "kimi-session-signal"
-export type HarnessTranscriptKind = "claude-jsonl" | "ohmypi-jsonl" | "codex-rollout-jsonl" | "acp-jsonl" | "kimi-wire-jsonl"
-export type HarnessSessionIdSource = "launcher-session-id" | "transcript-jsonl" | "codex-thread-id" | "acp-session-id" | "kimi-session-newest"
-export type HarnessContextLayerKind = "claude" | "pi" | "codex" | "acp" | "kimi-code"
-export type HarnessFeedKind = "claude_code" | "pi" | "codex" | "acp" | "kimi_code"
+ | "prime-agent-ready"
+export type HarnessTranscriptKind = "claude-jsonl" | "ohmypi-jsonl" | "codex-rollout-jsonl" | "acp-jsonl" | "kimi-wire-jsonl" | "prime-agent-jsonl"
+export type HarnessSessionIdSource = "launcher-session-id" | "transcript-jsonl" | "codex-thread-id" | "acp-session-id" | "kimi-session-newest" | "prime-agent-session-id"
+export type HarnessContextLayerKind = "claude" | "pi" | "codex" | "acp" | "kimi-code" | "prime-agent"
+export type HarnessFeedKind = "claude_code" | "pi" | "codex" | "acp" | "kimi_code" | "prime_agent"
export interface HarnessNativeCommand {
readonly name: string
@@ -48,7 +50,7 @@ export interface HarnessBehavior {
readonly usesRpcFifo: boolean
readonly usesCodexHome: boolean
readonly injectsPromptTimeMemory: boolean
- readonly workAgentMode: "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" | "acp-host" | "kimi-code-tui"
+ readonly workAgentMode: "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" | "acp-host" | "kimi-code-tui" | "prime-agent-rpc"
readonly readyTimeoutSeconds: number
}
@@ -176,12 +178,36 @@ export const KIMI_CODE_BEHAVIOR: HarnessBehavior = {
readyTimeoutSeconds: 60,
}
+export const PRIME_AGENT_BEHAVIOR: HarnessBehavior = {
+ displayName: "Prime Agent",
+ nativeCommands: [],
+ executableName: "prime-agent",
+ processNames: ["prime-agent"],
+ launchCommandKind: "prime-agent-rpc",
+ deliveryKind: "prime-agent-rpc",
+ readinessKind: "prime-agent-ready",
+ transcriptKind: "prime-agent-jsonl",
+ sessionIdSource: "prime-agent-session-id",
+ contextLayerKind: "prime-agent",
+ feedKind: "prime_agent",
+ supportsPtySupervisor: false,
+ supportsChannelsBridge: false,
+ supportsConversationStreaming: true,
+ supportsPatchProjection: false,
+ usesRpcFifo: false,
+ usesCodexHome: false,
+ injectsPromptTimeMemory: true,
+ workAgentMode: "prime-agent-rpc",
+ readyTimeoutSeconds: 120,
+}
+
const BEHAVIORS: Record = {
"claude-code": CLAUDE_CODE_BEHAVIOR,
ohmypi: OHMYPI_BEHAVIOR,
codex: CODEX_BEHAVIOR,
acp: ACP_BEHAVIOR,
"kimi-code": KIMI_CODE_BEHAVIOR,
+ "prime-agent": PRIME_AGENT_BEHAVIOR,
}
export function getHarnessBehavior(harness: HarnessName | undefined | null): HarnessBehavior {
@@ -189,6 +215,7 @@ export function getHarnessBehavior(harness: HarnessName | undefined | null): Har
if (harness === "codex") return CODEX_BEHAVIOR
if (harness === "acp") return ACP_BEHAVIOR
if (harness === "kimi-code") return KIMI_CODE_BEHAVIOR
+ if (harness === "prime-agent") return PRIME_AGENT_BEHAVIOR
return CLAUDE_CODE_BEHAVIOR
}
diff --git a/packages/contracts/src/telemetry.ts b/packages/contracts/src/telemetry.ts
index a6feb83d59e..017a7a5f4b5 100644
--- a/packages/contracts/src/telemetry.ts
+++ b/packages/contracts/src/telemetry.ts
@@ -82,7 +82,7 @@ export const TELEMETRY_CLI_VERBS = [
"other",
] as const
-const TELEMETRY_HARNESSES = ["claude-code", "ohmypi", "codex", "acp", "kimi-code"] as const satisfies readonly Harness[]
+const TELEMETRY_HARNESSES = ["claude-code", "ohmypi", "codex", "acp", "kimi-code", "prime-agent"] as const satisfies readonly Harness[]
export const TELEMETRY_PROPERTY_DOMAINS = {
agent_spawn_mode: ["spawn-and-send", "spawn-work-and-send"],
diff --git a/packages/contracts/src/types.test.ts b/packages/contracts/src/types.test.ts
index 84cb5284adf..f1d39569206 100644
--- a/packages/contracts/src/types.test.ts
+++ b/packages/contracts/src/types.test.ts
@@ -15,6 +15,10 @@ describe("getHarness", () => {
expect(getHarness({ runtime: "kimi-code" })).toBe("kimi-code")
})
+ it("preserves the canonical prime-agent runtime literal", () => {
+ expect(getHarness({ runtime: "prime-agent" })).toBe("prime-agent")
+ })
+
it("returns the existing target for every legacy harness string", () => {
expect(getHarness({ runtime: "claude-code" })).toBe("claude-code")
expect(getHarness({ runtime: "ohmypi" })).toBe("ohmypi")
diff --git a/packages/contracts/src/types.ts b/packages/contracts/src/types.ts
index 37d3f0c59fc..c4d17bc4924 100644
--- a/packages/contracts/src/types.ts
+++ b/packages/contracts/src/types.ts
@@ -84,9 +84,9 @@ export type VerificationStatusValue = typeof VerificationStatusValue.Type
// PAN-1989 retired 'pi'; legacy DB rows read as 'ohmypi' via getHarness().
// All new write sites use canonical harness literals.
-export type Harness = 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code'
+export type Harness = 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code' | 'prime-agent'
-export const KNOWN_HARNESSES: ReadonlySet = new Set(['claude-code', 'ohmypi', 'codex', 'acp', 'kimi-code'])
+export const KNOWN_HARNESSES: ReadonlySet = new Set(['claude-code', 'ohmypi', 'codex', 'acp', 'kimi-code', 'prime-agent'])
/**
* Normalize a snapshot's runtime field to a known Harness value.
diff --git a/scripts/dashboard-types-baseline.txt b/scripts/dashboard-types-baseline.txt
index 64bb6b746dc..9902f17848a 100644
--- a/scripts/dashboard-types-baseline.txt
+++ b/scripts/dashboard-types-baseline.txt
@@ -1 +1 @@
-30
+28
diff --git a/scripts/file-size-allowlist.txt b/scripts/file-size-allowlist.txt
index 96b7b27b774..3f636d8f0d6 100644
--- a/scripts/file-size-allowlist.txt
+++ b/scripts/file-size-allowlist.txt
@@ -14,7 +14,8 @@
1259 src/lib/lifecycle/workflows.ts # PAN-3727
1049 src/lib/config-yaml/schema.ts # PAN-3410
1068 src/lib/config-yaml/schema.ts # PAN-3658
-1074 src/lib/config-yaml/schema.ts # PAN-3754
+1080 src/lib/config-yaml/schema.ts # PAN-3668
+1086 src/lib/config-yaml/schema.ts # PAN-3754
900 src/lib/launcher-generator.ts # PAN-1837
1186 src/lib/sync.ts # PAN-3450
1060 src/lib/agents/spawn.ts # PAN-1837
diff --git a/scripts/guard-agent-dir-removal.sh b/scripts/guard-agent-dir-removal.sh
index d636bee78f9..dcfb680b6de 100755
--- a/scripts/guard-agent-dir-removal.sh
+++ b/scripts/guard-agent-dir-removal.sh
@@ -74,6 +74,10 @@ is_allowlisted() {
src/lib/overdeck/conversation-runtime.ts)
[[ "$content" == *'rm(sessionIdPath'* ]]
;;
+ src/lib/runtimes/prime-agent.ts)
+ [[ "$content" == *"'prime-agent-session-id'"* ||
+ "$content" == *"'prime-agent-launch-error'"* ]]
+ ;;
*)
return 1
;;
diff --git a/src/cli/commands/artifacts.ts b/src/cli/commands/artifacts.ts
index 1a4b7765828..f677903a3e1 100644
--- a/src/cli/commands/artifacts.ts
+++ b/src/cli/commands/artifacts.ts
@@ -281,7 +281,7 @@ export function registerArtifactCommands(program: Command): void {
.option('--issue ', 'Issue ID for artifact provenance')
.option('--workspace ', 'Workspace ID for artifact provenance')
.option('--agent-role ', 'Agent role: plan, work, review, test, ship, flywheel, or user')
- .option('--agent-harness ', 'Agent harness: claude-code, pi, ohmypi, codex, acp, kimi-code, or user')
+ .option('--agent-harness ', 'Agent harness: claude-code, pi, ohmypi, codex, acp, kimi-code, prime-agent, or user')
.option('--run-id ', 'Run ID for artifact provenance')
.option('--session-id ', 'Session ID for artifact provenance')
.option('--title ', 'Artifact title')
@@ -387,7 +387,7 @@ function parseAgentRole(value: string | undefined): ArtifactAgentRole | undefine
export function parseAgentHarness(value: string | undefined): ArtifactAgentHarness | undefined {
if (value === undefined) return undefined;
- if (value === 'claude-code' || value === 'pi' || value === 'ohmypi' || value === 'codex' || value === 'acp' || value === 'kimi-code' || value === 'user') return value;
+ if (value === 'claude-code' || value === 'pi' || value === 'ohmypi' || value === 'codex' || value === 'acp' || value === 'kimi-code' || value === 'prime-agent' || value === 'user') return value;
throw new Error(`Invalid --agent-harness: ${value}`);
}
diff --git a/src/cli/commands/doctor-prime-agent.ts b/src/cli/commands/doctor-prime-agent.ts
new file mode 100644
index 00000000000..ecc567dcc10
--- /dev/null
+++ b/src/cli/commands/doctor-prime-agent.ts
@@ -0,0 +1,79 @@
+import { resolveHarnessBinary } from '../../lib/harness-binary.js';
+import { execFile } from 'node:child_process';
+import { promisify } from 'node:util';
+import { Effect } from 'effect';
+import { getClaudeAuthStatus } from '../../lib/claude-auth.js';
+import { getOpenAIAuthStatus } from '../../lib/openai-auth.js';
+import { loadConfigSync } from '../../lib/config-yaml.js';
+import {
+ checkSystemPrerequisite,
+ type PrerequisiteProbe,
+ type PrerequisiteResolver,
+} from '../../lib/system-prerequisites.js';
+
+export const SUPPORTED_PRIME_AGENT_VERSION_MIN = '0.1.0';
+export const SUPPORTED_PRIME_AGENT_VERSION_MAX_EXCLUSIVE = '1.0.0';
+
+export interface PrimeAgentDoctorResult {
+ name: string;
+ status: 'ok' | 'warn' | 'error';
+ message: string;
+ fix?: string;
+}
+
+function compareSemver(a: string, b: string): number {
+ const left = a.split('.').map(Number);
+ const right = b.split('.').map(Number);
+ for (let index = 0; index < 3; index += 1) {
+ if ((left[index] ?? 0) !== (right[index] ?? 0)) return (left[index] ?? 0) - (right[index] ?? 0);
+ }
+ return 0;
+}
+
+export async function checkPrimeAgent(
+ probe?: PrerequisiteProbe,
+ resolver?: PrerequisiteResolver,
+ credentialCheck?: () => Promise,
+): Promise {
+ const productionProbe: PrerequisiteProbe = async (path, args) => (await promisify(execFile)(path, args, { encoding: 'utf8' })).stdout;
+ const activeProbe = probe ?? productionProbe;
+ const prime = await checkSystemPrerequisite('prime-agent', activeProbe, resolver);
+ if (!prime.found) return [{ name: prime.name, status: 'warn', message: 'Not installed (prime-agent harness unavailable)', fix: `Install: ${prime.install.linux}` }];
+
+ const version = prime.version?.match(/(\d+\.\d+\.\d+)/)?.[1] ?? null;
+ if (!version) return [{ name: prime.name, status: 'warn', message: 'Detected but `prime-agent --version` did not return a semantic version', fix: `Install a supported release >= ${SUPPORTED_PRIME_AGENT_VERSION_MIN} and < ${SUPPORTED_PRIME_AGENT_VERSION_MAX_EXCLUSIVE}` }];
+ if (compareSemver(version, SUPPORTED_PRIME_AGENT_VERSION_MIN) < 0 || compareSemver(version, SUPPORTED_PRIME_AGENT_VERSION_MAX_EXCLUSIVE) >= 0) {
+ return [{ name: prime.name, status: 'warn', message: `v${version} is unsupported; requires >= ${SUPPORTED_PRIME_AGENT_VERSION_MIN} and < ${SUPPORTED_PRIME_AGENT_VERSION_MAX_EXCLUSIVE}`, fix: 'Install a compatible Prime Agent release; Overdeck will not fall back to another harness.' }];
+ }
+
+ const executable = await (resolver
+ ? resolver('prime-agent', { primeAgentHarness: true })
+ : resolveHarnessBinary('prime-agent'));
+ try {
+ const help = executable ? await activeProbe(executable, ['--help']) : '';
+ if (!/(?:--mode\b[^\n]*\brpc\b|--mode\s+<[^>]+>)/i.test(help)) {
+ return [{ name: prime.name, status: 'warn', message: `v${version} does not advertise the required --mode rpc capability`, fix: 'Install a Prime Agent build with RPC mode support.' }];
+ }
+ } catch {
+ return [{ name: prime.name, status: 'warn', message: `v${version}; RPC capability probe failed`, fix: 'Run `prime-agent --help` and verify --mode rpc is available.' }];
+ }
+ const results: PrimeAgentDoctorResult[] = [{ name: prime.name, status: 'ok', message: `v${version} (RPC mode available)` }];
+ if (!probe || credentialCheck) {
+ let hasCredential: boolean;
+ if (credentialCheck) {
+ hasCredential = await credentialCheck();
+ } else {
+ const { config } = loadConfigSync();
+ const [claude, openai] = await Promise.all([
+ Effect.runPromise(getClaudeAuthStatus()),
+ Effect.runPromise(getOpenAIAuthStatus()),
+ ]);
+ hasCredential = claude.loggedIn || claude.hasAnthropicApiKey || openai.loggedIn
+ || Object.values(config.apiKeys).some(Boolean);
+ }
+ results.push(hasCredential
+ ? { name: 'Prime Agent provider credentials', status: 'ok', message: 'At least one configured provider credential is available' }
+ : { name: 'Prime Agent provider credentials', status: 'warn', message: 'No provider credential is configured', fix: 'Configure credentials for the provider used by the selected Prime Agent model.' });
+ }
+ return results;
+}
diff --git a/src/cli/commands/doctor.ts b/src/cli/commands/doctor.ts
index 95ba3ec7a6c..7e6d09ba6d3 100644
--- a/src/cli/commands/doctor.ts
+++ b/src/cli/commands/doctor.ts
@@ -36,6 +36,7 @@ import { checkInotify } from './doctor-inotify.js';
import { checkStateDivergence } from './doctor-state-divergence.js';
import { checkStateWorktrees } from './doctor-state-worktree.js';
import { checkDuplicateComposeStacks } from './doctor-duplicate-stacks.js';
+import { checkPrimeAgent } from './doctor-prime-agent.js';
import {
assessBridgePoolPressure,
bridgePoolLimitFromPools,
@@ -745,10 +746,9 @@ export async function doctorCommand(options: DoctorOptions = {}): Promise
// Codex CLI (alternative harness — PAN-1574). Optional: missing → warn.
for (const c of checkCodex()) checks.push(c);
-
// Kimi Code CLI (ACP harness). Resolve the same configured executable used at launch.
for (const c of await checkKimi()) checks.push(c);
-
+ for (const c of await checkPrimeAgent()) checks.push(c);
// Check Overdeck directories
const directories = [
{ path: OVERDECK_HOME, name: 'Overdeck Home', fix: 'Run: pan init' },
diff --git a/src/cli/commands/flywheel.ts b/src/cli/commands/flywheel.ts
index d77dc3b7456..b59989c7c52 100644
--- a/src/cli/commands/flywheel.ts
+++ b/src/cli/commands/flywheel.ts
@@ -95,7 +95,7 @@ interface FlywheelGateSnapshot {
}
interface ResolvedFlywheelRoleConfig {
- harness: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code';
+ harness: RuntimeName;
model: string;
effort: RoleEffort;
minAgents: number;
diff --git a/src/cli/commands/strike.ts b/src/cli/commands/strike.ts
index a62265085d8..4ea94e87659 100644
--- a/src/cli/commands/strike.ts
+++ b/src/cli/commands/strike.ts
@@ -14,12 +14,13 @@ import { ACTIVITY_STALLED_MS } from '../../lib/agents/health.js';
import { resolveProjectFromIssueSync } from '../../lib/projects.js';
import { isHarnessProcessAlive, sessionExists } from '../../lib/tmux.js';
import type { RoleEffort } from '../../lib/config-yaml.js';
+import type { RuntimeName } from '../../lib/runtimes/types.js';
const execAsync = promisify(exec);
export interface StrikeOptions {
model?: string;
- harness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code';
+ harness?: RuntimeName;
effort?: RoleEffort;
dryRun?: boolean;
}
diff --git a/src/cli/index.ts b/src/cli/index.ts
index 4655c46af35..fe622991ba5 100644
--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -366,7 +366,7 @@ const planCmd = program
.option('--auto-start', '[deprecated: use pan start ] After planning completes, automatically start the work agent — used by autonomous orchestrators')
.option('--probe', 'Add an adversarial pre-finalize probe pass to the planning prompt')
.option('--model ', 'Model to use for the planning role')
- .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)')
+ .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)')
.option('--effort ', 'Planning effort: low | medium | high')
.option('--remote', 'Use remote planning workspace (Fly.io)')
.option('--local', 'Use local planning workspace')
@@ -510,7 +510,7 @@ program
.command('start ')
.description('Create workspace and spawn agent for an issue')
.option('--model ', 'Work model to use and persist for later respawns (defaults to Cloister config)').option('--swarm ', 'Per-issue swarm policy: off | auto | always').option('--review-mode ', 'Per-issue review mode: quick | full | none').option('--review-model ', 'Per-issue review model override')
- .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)')
+ .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)')
.option('--effort ', 'Claude Code effort: low | medium | high | xhigh | max (defaults to roles.work.effort)')
.option('--tier ', 'Remote workspace resiliency tier: ephemeral | durable (defaults to remote.resiliency_tier)')
.option('--dry-run', 'Show what would be created')
@@ -531,7 +531,7 @@ program
.command('strike ')
.description('Spawn strike agent(s) — implement and push a strike branch for Deacon to land through the verified merge door. Bypasses plan/review/test/ship.')
.option('--model ', 'Model override (defaults to roles.strike.model from config)')
- .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code (defaults to role/provider settings)')
+ .option('--harness ', 'Coding-agent harness: claude-code | pi | codex | acp | kimi-code | prime-agent (defaults to role/provider settings)')
.option('--effort ', 'Strike effort: low | medium | high | xhigh | max (default high)')
.option('--dry-run', 'Print what would happen without spawning')
.action((ids: string[], options: { model?: string; harness?: RuntimeName; effort?: RoleEffort; dryRun?: boolean }) => strikeCommand(ids, options));
diff --git a/src/dashboard/frontend/src/components/CommandDeck/ConversationList.tsx b/src/dashboard/frontend/src/components/CommandDeck/ConversationList.tsx
index 2cc2fdf1700..9c09cf20942 100644
--- a/src/dashboard/frontend/src/components/CommandDeck/ConversationList.tsx
+++ b/src/dashboard/frontend/src/components/CommandDeck/ConversationList.tsx
@@ -46,7 +46,7 @@ export interface Conversation {
/** Model used for this conversation. Null until backfilled from session file. */
model?: string | null;
/** Harness used to spawn this conversation. */
- harness?: 'claude-code' | 'pi' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code' | null;
+ harness?: 'claude-code' | 'pi' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code' | 'prime-agent' | null;
/** Effort level used when spawning this conversation. */
effort?: string | null;
/** Async fork provisioning status. Null = not a fork or completed. */
diff --git a/src/dashboard/frontend/src/components/CommandDeck/ConversationRow.tsx b/src/dashboard/frontend/src/components/CommandDeck/ConversationRow.tsx
index 644bcf9a212..04109f6e038 100644
--- a/src/dashboard/frontend/src/components/CommandDeck/ConversationRow.tsx
+++ b/src/dashboard/frontend/src/components/CommandDeck/ConversationRow.tsx
@@ -37,6 +37,7 @@ function shortHarness(harness: NonNullable): string {
if (harness === 'codex') return 'Codex';
if (harness === 'acp') return 'ACP';
if (harness === 'kimi-code') return 'Kimi Code';
+ if (harness === 'prime-agent') return 'Prime Agent';
// Unknown future harness — show the raw id rather than a wrong label.
return harness;
}
diff --git a/src/dashboard/frontend/src/components/CommandDeck/useConversationMutations.ts b/src/dashboard/frontend/src/components/CommandDeck/useConversationMutations.ts
index 2ff63c01a79..1c9d8462d31 100644
--- a/src/dashboard/frontend/src/components/CommandDeck/useConversationMutations.ts
+++ b/src/dashboard/frontend/src/components/CommandDeck/useConversationMutations.ts
@@ -4,6 +4,7 @@ import { toast } from 'sonner';
import type { Conversation } from './ConversationList';
import { updateConversationTitle } from './ConversationList';
import { closeConversationPanes } from '../../lib/panesStore';
+import type { Harness } from '../Settings/types';
async function archiveConversation(name: string): Promise {
const res = await fetch(`/api/conversations/${encodeURIComponent(name)}/archive`, { method: 'POST' });
@@ -47,7 +48,7 @@ type ApiForkMode = 'summary' | 'plain' | 'handoff';
type ForkModeOption = ApiForkMode | 'fast-summary';
type HandoffAuthor = 'source' | 'external';
-async function summaryForkConversation(opts: { conv: Conversation; model: string; summaryModel: string; harness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code'; summaryHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code'; forkMode?: ApiForkMode; focus?: string; localSummaryOnly?: boolean; includeThinkingInSummary?: boolean; title?: string; handoffAuthor?: HandoffAuthor; handoffAuthorModel?: string; handoffAuthorHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code'; projectKey?: string }): Promise {
+async function summaryForkConversation(opts: { conv: Conversation; model: string; summaryModel: string; harness?: Harness; summaryHarness?: Harness; forkMode?: ApiForkMode; focus?: string; localSummaryOnly?: boolean; includeThinkingInSummary?: boolean; title?: string; handoffAuthor?: HandoffAuthor; handoffAuthorModel?: string; handoffAuthorHarness?: Harness; projectKey?: string }): Promise {
const res = await fetch(`/api/conversations/${encodeURIComponent(opts.conv.name)}/summary-fork`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -83,7 +84,7 @@ export interface ConversationMutations {
toggleFavorite: (opts: { name: string; favorited: boolean }) => void;
move: (opts: { name: string; projectKey: string; projectName: string }) => void;
openForkModal: (conv: Conversation, options?: { mode?: ForkModeOption; focus?: string }) => void;
- submitFork: (conv: Conversation, launchModel: string, summaryModel: string, forkMode: ApiForkMode, localSummaryOnly: boolean, includeThinkingInSummary: boolean, title?: string, launchHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', summaryHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', focus?: string, handoffAuthor?: HandoffAuthor, handoffAuthorModel?: string, handoffAuthorHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', projectKey?: string) => void;
+ submitFork: (conv: Conversation, launchModel: string, summaryModel: string, forkMode: ApiForkMode, localSummaryOnly: boolean, includeThinkingInSummary: boolean, title?: string, launchHarness?: Harness, summaryHarness?: Harness, focus?: string, handoffAuthor?: HandoffAuthor, handoffAuthorModel?: string, handoffAuthorHarness?: Harness, projectKey?: string) => void;
forkTarget: Conversation | null;
forkTargetMode: ForkModeOption | undefined;
forkTargetFocus: string | undefined;
@@ -233,7 +234,7 @@ export function useConversationMutations(
}
}, [summaryForkMutation.isPending]);
- const submitFork = useCallback((conv: Conversation, launchModel: string, summaryModel: string, forkMode: ApiForkMode, localSummaryOnly: boolean, includeThinkingInSummary: boolean, title?: string, launchHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', summaryHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', focus?: string, handoffAuthor?: HandoffAuthor, handoffAuthorModel?: string, handoffAuthorHarness?: 'claude-code' | 'ohmypi' | 'codex' | 'acp' | 'kimi-code', projectKey?: string) => {
+ const submitFork = useCallback((conv: Conversation, launchModel: string, summaryModel: string, forkMode: ApiForkMode, localSummaryOnly: boolean, includeThinkingInSummary: boolean, title?: string, launchHarness?: Harness, summaryHarness?: Harness, focus?: string, handoffAuthor?: HandoffAuthor, handoffAuthorModel?: string, handoffAuthorHarness?: Harness, projectKey?: string) => {
summaryForkMutation.mutate({
conv,
model: launchModel,
diff --git a/src/dashboard/frontend/src/components/Settings/sections/CrewRow.tsx b/src/dashboard/frontend/src/components/Settings/sections/CrewRow.tsx
index 419a153f75f..bbb6358cdee 100644
--- a/src/dashboard/frontend/src/components/Settings/sections/CrewRow.tsx
+++ b/src/dashboard/frontend/src/components/Settings/sections/CrewRow.tsx
@@ -8,7 +8,7 @@ import {
type CrewEntry,
} from './tiered-crews';
-const HARNESSES: Harness[] = ['claude-code', 'ohmypi', 'codex', 'acp', 'kimi-code'];
+const HARNESSES: Harness[] = ['claude-code', 'ohmypi', 'codex', 'acp', 'kimi-code', 'prime-agent'];
function ModelSelect({ value, onChange, label = 'Model' }: { value: string; onChange: (value: string) => void; label?: string }) {
return