Status: complete — all phases landed, Phase 6 decided (keep the process boundary) · Last verified: 2026-07-14 · Contract: v1
This guide explains the merge of coven-code into the unified coven CLI
("the Coven CLI unification"), what users need to do to move over seamlessly,
and how the merge is implemented comprehensively in the Cave
(OpenCoven/coven-cave, the desktop
control room).
Authoritative sources this guide summarizes:
- Plan:
docs/superpowers/plans/2026-07-12-coven-cli-unification.md - Phase 3 sub-plan:
docs/superpowers/plans/2026-07-12-phase3-engine-harness.md - Compatibility contract: coven/docs/ENGINE-CONTRACT.md (
contract_version: 1) - Engine-side pointer:
COVEN.md"Engine contract"
coven becomes the one CLI users install and type for everything —
interactive agent work, headless runs, session management, auth, models.
coven-code is absorbed as the Coven engine: a version-pinned,
checksum-verified binary that coven installs, upgrades, health-checks, and
drives. The name "coven-code" disappears from install instructions and
user-facing copy; the binary keeps its name internally and lives under
~/.coven/engine/<version>/.
This is a product merge, not a source merge. All integration crosses a
process boundary (exec/spawn, stream-json over stdio, HTTP over a Unix
socket). No Cargo dependency from coven on any claurst-* crate, ever.
Why:
- License.
covenis MIT;coven-codeis GPL-3.0, inherited from upstream Claurst whose copyright OpenCoven does not wholly own. Statically linking would make the combined binary a GPL derivative and forcecovento GPL-3.0. A process boundary keeps each work under its own license (the ripgrep-in-VS-Code / containerd-under-Docker pattern). - Upstream merges. This repo deliberately preserves
claurst-*crate names sogit merge upstream/mainstays low-friction. Moving the source into the coven workspace would end that. - Runtime split. coven-cli is deliberately synchronous; the engine is tokio-based, ~148k LoC, with different ratatui/rusqlite majors. Unifying buys nothing users can see.
"Singular" means: one package to install, one command to type, one doc site, one config root, one session ledger. The engine binary is an implementation detail, like a bundled shared library.
Coven drives the engine only through the surfaces listed in
ENGINE-CONTRACT.md:
--version, --print, the stream-json loop
(--print --input-format stream-json --output-format stream-json),
--session-id/--resume, --model/--append-system-prompt/--cwd,
--permission-mode, auth status --json, and acp. Breaking any of them
requires a contract version bump plus a coordinated engine.lock update in
coven (see AGENTS.md). Coven's contract tests run in this
repo's CI against every engine build (the coven-contract job in
rust-ci.yml, still non-blocking while the job proves stable; the coven-side
suite landed with coven #353, so flipping it to a hard gate is the remaining
step), so a contract break is caught before release, not after.
coven → engine interactive TUI (auto-installs engine on first run)
coven "fix the login bug" → cast free-text task (defaults to the engine harness)
coven run <harness> [...] → daemon-ledgered session: codex | claude | coven-code | ...
coven sessions|attach|kill|… → session ledger (includes engine sessions)
coven auth | models | acp → engine passthroughs
coven engine status|install|which → managed-engine admin
coven code [anything…] → raw passthrough escape hatch to the engine CLI
coven doctor → daemon + harnesses + engine + auth
Collision policy: coven-owned names always win (run, sessions, daemon,
…). New engine subcommands are reachable through coven code <sub> on day
one, without waiting for a coven release.
| Phase | Deliverable | Status |
|---|---|---|
| 0 | ADRs + compatibility contract in both repos | Landed — coven-code #150 merged; contract doc landed with coven #346 |
| 1 | Engine resolver, coven engine admin, auto-install, passthroughs, doctor |
Landed — coven #346 merged |
| 2 | engine.lock pin, cross-repo contract-test CI, one install story |
Landed — engine side coven-code #151 merged (reverse contract-CI job + install-story notices); coven side #353 merged |
| 3A | Engine as first-class harness; bare prompts prefer the engine | Landed — coven #354 merged, zero engine-side changes |
| 3B | Externally-owned sessions in the daemon ledger | Landed — coven #355 (daemon endpoints) + coven-code #152 (TUI notifier, opt-in daemonLedger setting) merged |
| 4 | State/config/auth unification under ~/.coven |
Landed — coven-code #153 merged (engine home ~/.coven/code/ with in-place migration + shared settings layer) |
| 5 | Brand/UX sweep, npm deprecation of the engine package | Landed — coven-code #154 (user-facing rebrand + direct-invocation notice), #158 (LLM-facing tool descriptions), #160 (ACP display name); @opencoven/coven-code deprecated on npm (notice points at @opencoven/cli); engine npm-publish made opt-in per release, GitHub Releases stay the artifact source (#162) |
| 6 | Decision gate: full source merge | Decided — NO-GO, keep the process boundary (#155, ADR: 2026-07-14-phase6-source-merge-decision.md) |
The coven-side phases landed in order (#346, #353, #354, #355; the original stacked PRs #347–#349 were superseded by these re-landed equivalents after their base branches were cleaned up). coven-code #152 is independently functional: the notifier is fire-and-forget and silently no-ops against an older daemon.
What already works today, on main of this repo: every contract surface
(the engine has shipped them since 0.6.1), the reverse contract-CI job, and
the coven-runtimes adapter manifest
(spec/runtime-manifest/coven-code.json)
that lets the coven daemon drive coven-code as a manifest-declared adapter
even on coven builds that predate the built-in harness (#354).
Short version: nothing is required. Every step of the merge is additive and the legacy paths keep working. The steps below are what to do to be on the unified path, per audience.
npm install -g @opencoven/cli
covenFirst interactive run offers to install the engine (~15 MB, one keypress),
then opens the TUI. There is no separate coven-code install step. To
pre-fetch instead of lazily installing: coven engine install.
Upgrade @opencoven/cli when Phase 1–2 releases, then:
coven engine install # installs the pinned, checksum-verified engine
coven doctor # shows the Engine section: source, version, pin, authNothing else changes: coven run codex|claude behave exactly as before.
After Phase 3A, bare-prompt casts (coven "do X") prefer the engine when it
is available — use coven run codex|claude -- "…" to pick a specific
harness explicitly (bare prompts intentionally accept no flags).
Everything keeps working, in this order of resolution when coven looks for the engine:
COVEN_ENGINE_BIN(explicit override — dev builds, custom paths)- Managed:
~/.coven/engine/<current>/coven-code coven-codeon PATH- Legacy:
~/.coven-code/bin/coven-code
Notes:
- A managed engine wins over a PATH copy once you run
coven engine install— that command is authoritative.coven engine whichprints which binary won andcoven doctorprints why. - Direct
coven-code …invocation remains fully supported. Since the Phase 5 sweep (#154) it prints one dim notice line when run interactively outside coven (COVEN_PARENTunset); scripted/delegated runs stay silent. - The
@opencoven/coven-codenpm package is deprecated (the notice points at@opencoven/cli). Installed copies keep working, but new engine versions are no longer published to npm by default — publishing is an opt-in switch per release since #162. GitHub release archives remain the artifact source permanently (they are whatcoven engine installdownloads). Note the barecoven-codenpm name is an unrelated, already deprecated package — only the scoped name is real. - Version drift: if your PATH engine is older than coven's pin you get a
one-line warning; below
MIN_ENGINE_VERSION(0.6.1) coven refuses with an actionable message.coven engine installfixes both.
No changes required. coven-code --print …, the stream-json loop, exit codes
(0 success / 1 error), and auth status --json are contract-frozen surfaces.
Two useful additions:
COVEN_NO_AUTO_INSTALL=1forces the no-engine error instead of an interactive prompt (the prompt is already TTY-gated, so CI never blocks).- Prefer
coven code <args…>in new scripts — it resolves the engine for you;coven-codeon PATH keeps working for old ones.
- Phase 4 landed (#153): the engine home migrates in place to
~/.coven/code/on first launch, with a compatibility symlink at~/.coven-codefor one deprecation cycle — automatic, no user action, and reversible (remove symlink, restore dir). Pre-migration installs keep reading~/.coven-code/until they upgrade. - Credentials are never shared or imported across CLIs.
coven auth loginis a passthrough into the engine's own auth; the engine never replays OAuth tokens from Claude Code or any other CLI (hard rule, seeAGENTS.mdProviders).
With coven #355 and coven-code #152 landed, add to the engine settings
(~/.coven/code/settings.json; the legacy ~/.coven-code/settings.json
path still works through the compatibility symlink):
{ "daemonLedger": true }Interactive TUI sessions then register themselves in the Coven daemon ledger
(best-effort; a dead or absent daemon never affects the TUI; Unix only).
They appear in coven sessions as external sessions: coven attach
replays the recorded ledger (with an explicit "not the live terminal"
notice) and coven kill refuses them (422 external_session_not_killable)
because the daemon does not own the process. The setting ships opt-in
(default off) and is planned to default on in a later release now that the
Phase 4 config layering is in place.
Everything above applies except the ledger notifier (the daemon client is
Unix-socket-only; the notifier is a documented no-op on Windows). The
managed-engine layout uses a plain-text current pointer file, not
symlinks, so coven engine install works identically. Passthroughs use
spawn-and-wait rather than exec.
| Variable | Set by | Meaning |
|---|---|---|
COVEN_ENGINE_BIN |
user | Absolute path override for the engine binary; beats all resolution |
COVEN_NO_AUTO_INSTALL |
user/CI | 1 disables the first-run install prompt |
COVEN_PARENT=coven |
coven | Present on every delegated invocation; the engine uses it to know it is being driven |
COVEN_HOME |
user | Coven state root (~/.coven default); forwarded to the engine when set |
COVEN_DAEMON_SOCKET |
daemon env | Daemon UDS path; used by the session notifier |
COVEN_CODE_* |
user | Engine-owned namespace; coven never overrides it |
The Cave (coven-cave) is the desktop control room. It already builds on the
coven CLI and daemon, which is exactly the seam the merge formalizes — so
the Cave's work is mostly simplification: collapse its two-tool story into
the one-CLI story and adopt the new engine/ledger surfaces as they land.
| Surface | Cave code | Behavior today |
|---|---|---|
| Binary resolution | src/lib/coven-bin.ts |
Probes well-known install dirs for coven (nvm/fnm, npm/pnpm/bun globals, homebrew, ~/.cargo/bin last), falls back to login-shell PATH, caches; covenSpawnEnv() scrubs forbidden keys |
| Daemon lifecycle | src/lib/daemon-start.ts |
Health-checks /api/v1/health, else spawns coven daemon start |
| Session launch | src/app/api/sessions/route.ts → daemon POST /api/v1/sessions |
Sends harness id; allow-list (src/lib/server/session-security.ts) is derived from COMPATIBILITY_ADAPTERS |
| Runtime catalog | src/lib/harness-adapters.ts + src/lib/runtime-registry.gen.ts |
Curated seed (codex, claude, copilot, hermes, openclaw) merged with the generated coven-runtimes registry, which includes coven-code; regenerate with pnpm sync:runtimes |
| Streaming | src/lib/copilot-stream.ts |
Runtimes that declare stream_args (coven-code included) use the long-lived stdin-frame stream-json loop |
| Tool install/onboarding | src/lib/opencoven-tools-status.ts, opencoven-tools-install.ts, onboarding-gate.ts, install queue |
Treats two npm packages as peers: @opencoven/cli and @opencoven/coven-code; detects the engine by finding coven-code on PATH; npm i -g both |
| Version probes | src/lib/harness-version.ts, coven-version.ts |
Runs <binary> --version for each adapter binary found on PATH |
The structural insight: the Cave never talks to the engine directly for sessions — it talks to the coven daemon, and the daemon spawns harnesses. That means most merge phases reach the Cave "for free" through a coven upgrade, and the Cave-side changes concentrate in detection, onboarding copy, and session-list semantics.
Each stage is gated on a release landing; nothing here needs to move early. Stages are independent, shippable, and ordered by dependency.
Stage 1 — engine-aware detection and one-package onboarding (gate: coven release containing #346 + #353)
opencoven-tools-status.ts: stop equating "engine installed" with "coven-codeon PATH". The managed engine lives at~/.coven/engine/<current>/coven-codeand is deliberately not on PATH. Detect viacoven engine status --json(it reports resolved path, source, version, and pin state) with the current PATH probe kept as a fallback for engine-only installs and older coven builds.coven engine whichis the script-friendly variant.opencoven-tools-install.ts+ the onboarding install queue: collapse the two-package lane (npm i -g @opencoven/cli @opencoven/coven-code) intonpm i -g @opencoven/clifollowed bycoven engine install. Keep the queue's serialize-behind-the-npm-lane behavior; the engine step is acovensubprocess, not an npm install. Keep a "direct engine install" escape hatch for users who intentionally run PATH engines.onboarding-gate.ts(skip-coven-codekey): the gate's question changes from "is the coven-code package installed" to "doescovenresolve an engine". Preserve the skip semantics.- Compat/version checks (
harness-version.ts): for thecoven-codeadapter, source the version fromcoven engine status --json(orcoven code --version) so a managed-only install doesn't read as "unknown version". - Copy sweep: onboarding and settings surfaces should say "Coven engine" and stop instructing a separate coven-code npm install (matches the Phase 2 install-story change already landed here in #151).
Stage 2 — sessions on the built-in engine harness (gate: coven release containing #354)
- No payload change: the Cave keeps posting
POST /api/v1/sessions {harness: "coven-code", …}. What changes is that the daemon now hascoven-codeas a built-in harness with engine-aware availability and spawn — a managed engine that is not on PATH becomes launchable, and the adapter-manifest scaffold under$COVEN_HOME/adapters/coven-code.jsonstops being a prerequisite. - Registry copy: the coven-runtimes entry's install hint
(
npm install -g @opencoven/coven-code …) should be updated upstream to the engine story (coven engine install), then regenerated into the Cave viapnpm sync:runtimes. Registry versions are immutable — this is a manifest version bump inspec/runtime-manifest/coven-code.json, submitted through coven-runtimes acceptance. - Expectation change worth surfacing in the UI: bare-prompt casts made
through coven now default to the engine harness; the Cave's own launches
are explicit about
harness, so nothing changes for it — but "new session" affordances may want to present Coven Code first for parity.
Stage 3 — external (TUI-owned) sessions in the session list
(gate: coven release containing #355; richer once coven-code #152 is in the
pinned engine and users opt into daemonLedger)
External sessions are registered, not launched: the daemon has no PTY for them. The Cave must treat them as a distinct kind:
- Session list/status (
session-status.ts,session-list-merge.ts, rail components): render external records (surface theexternalflag from the daemon API) with an "interactive TUI session" affordance instead of the usual live-attach affordance. - Kill affordances: the daemon answers
422 external_session_not_killable— hide or disable kill for external sessions rather than surfacing a failed action. - Attach/inspect: attach-equivalent views should show the recorded ledger with the same "shows the ledger, not the live terminal" framing the CLI uses; do not attempt stdin forwarding.
- Sweeps and health logic (e.g.
stuck-created-sweep.ts): exclude external sessions — the daemon's own orphan recovery already skips them (external = 0filter) and the Cave must not re-reap what the daemon deliberately leaves alone. Completion arrives via the engine's/completecall (exit code 0 → completed, nonzero → failed). - Optional Cave setting: a toggle that writes
daemonLedger: trueinto the engine settings, so users can opt into ledgered TUI sessions from the Cave UI. Unix only; hide on Windows.
Stage 4 — state unification prep (gate: Phase 4 engine release)
The engine home moves to ~/.coven/code/ with a one-cycle symlink at
~/.coven-code. Any Cave code that touches engine paths directly (transcript
readers, coven-memory-path.ts-style helpers) must resolve through the
engine's reported paths rather than hardcoding ~/.coven-code. The Cave
already has a home-migration pattern (server/cave-home-migration.ts) to
model the detection on. Audit for hardcoded .coven-code strings before the
Phase 4 release, not after.
Stage 5 — naming sweep (gate: Phase 5)
The coven-cave bin alias shipped by the engine npm package gets a sunset
notice and is later removed — the Cave must not spawn or document it
(desktop app code paths use coven/coven-code names already; this is a
copy-and-docs check). "coven-code" disappears from user-facing Cave copy in
favor of "the Coven engine", matching both repos.
Per stage, in coven-cave:
pnpm typecheck
pnpm test:app # unit/component lanes
pnpm test:api # server/API lanesManual smokes that map to the merge's exit criteria:
- Stage 1: fresh machine profile → onboarding installs one npm package,
engine present via
coven engine status; tools panel shows both ready. - Stage 2: create a session with harness
coven-codefrom the Cave with the engine only managed (removed from PATH) — it must launch and stream. - Stage 3: start an interactive
coven-codeTUI withdaemonLedgeron → session appears in the Cave list as external; kill affordance absent; closing the TUI flips it to completed. - Stage 4: populated
~/.coven-code→ upgrade → Cave still finds transcripts/settings through the moved home.
- Process boundary: the Cave talks to
coven(CLI + daemon HTTP). It must never link engine code or bypass coven to manage engine internals — the escape hatch for engine-specific calls iscoven code …. - No credential bridging: the Cave never copies or injects tokens
between CLIs; auth flows go through
coven auth …passthrough or the engine's own login. Presentation may unify; tokens may not. - Additive adoption: every stage must degrade gracefully against an
older coven (missing
enginesubcommand → fall back to PATH detection; missing/sessions/externalsemantics → no external rows to render). The daemon and CLI are updated by users on their own schedule.
- Every phase is independently revertible; the legacy resolution order
(PATH +
~/.coven-code/bin) stays in the resolver permanently. - The engine's stream-json output stays Claude-Code-compatible — coven and the Cave extend their parsers rather than forking the format.
- Supply chain: engine installs are SHA-256-pinned via coven's
engine.lockand fail closed on mismatch; the pin is regenerated per engine release by an automated bump PR gated on the cross-repo contract tests. - Contract drift is caught in both directions: coven CI tests the pinned
engine; this repo's CI runs coven's contract tests against every engine
change (the
coven-contractjob).
- Coven runtime contract — how the daemon drives
coven-codeas a registered runtime today - Headless contract —
--print/ stream-json surfaces - Coven Familiars — daemon familiars as agent personas
- Installation — current install paths for this repo's CLI