All notable changes to Squadrant (formerly claude-cockpit) are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- False "First turn not delivered" warning + duplicate first-turn paste on cold Claude Code boot (#745).
pollFirstTurnConfirmedAt's fixed 100s window was anchored at spawn start, but the hook can only stamp confirmation after the scrape submits — and the scrape itself gets up to ~106s of readiness/submit retries, so the poll could time out ~9s before a delivery that had, in fact, landed. The poll now runs through the scrape's unsettled window plus a 15s post-settle grace (180s absolute cap) and cancels once a verdict is reached. Separately, the "paste never rendered → re-paste once" rule fired onsawDraft=falseeven when a cold-boot paste had already been submitted before the draft was observed, submitting the whole turn twice; the re-paste (and itsconfirmedSendToPanefallback) is now gated on the pane still being frozen at the pre-send image. heal daemonfalse-FAIL on the plist-drift race, and a same-version-bump misclassified as a hijack (#751, #752).forceKickstartAndVerifycaptured its "before" pid afterreconcilePlistAndService's bootout+bootstrap had already started the new instance, so akickstart -kracing that fresh process could never observe a pid change and reported FAILED on an already-healthy daemon;reregisterDaemonnow snapshots the pid before reconcile runs. Separately, the #670 foreign-install guard compared full paths, so a routine same-manager version bump (e.g. pnpm's.pnpm/squadrant@0.19.2/→.pnpm/squadrant@0.19.3/) was misread as a hijack and printed the refusal banner on every CLI call; same-manager/different-version is now classified as an upgrade and reconciled with a one-line notice, while a genuine different-manager hijack still refuses. Read-only crew subcommands (list/read/tasks) now skipensureDaemonentirely.- Gap-session handoff facts lost everything but the last exchange (#753).
extractTranscriptTailkept only the final user/assistant message per gap-session transcript, so an incidental last message (e.g. a daemon notice) could erase all record of what the session actually did. It now walks the full tail and builds a per-transcript digest — ordered user prompts, assistant final text per turn, tool-call counts, files touched, and PR/issue/commit references — capped at ~8KB, oldest entries truncated first. - Boot-gap "daemon was down" alert read as a live outage when delivered late (#744). The alert text carried only a bare minute count, so a copy stuck in an unreachable captain's mailbox for hours read as a current outage once finally delivered.
formatDownAlertTextnow bakes the actual outage window (local time + tz offset) into the message, and the delivery loop prefixes a[stale — generated N ago]tag when a daemon-sourced message is delivered more than 1h after it was generated.
- Socket directory created world-readable, blocking captain/crew launch on Claude Code ≥2.1.259 (#749).
ensureSocksDir()now creates/tmp/cc-sockswith mode0700; Claude Code 2.1.259 refuses to operate against a755socket directory.
- Event architecture Phase 0-1: fact pipeline + opencode cutover (#739, #740, #743). Introduces
@squadrant/core's own event-tracking module — anAgentFactvocabulary, per-agentFactAdapterseam, aLifecycleSourcefacade wiring adapters to the pipeline, a flight-recorder ring buffer for post-hoc debugging, and a pure fact-to-ControlEventmapping layer with an adapter conformance test suite. Ships invariant checks I1–I3 (depth-based tool-call pairing) and I4–I6 (trust, unknown-rate, and liveness-disagreement).opencodeis moved onto the fact pipeline via aFactAdapterreading SSE frames directly, retiring the oldOpencodeControlSource;claudestays on its existing path (shadow only) per the approved design. A generatedControlEventproducer/consumer table plus a--checkgate now catches producer/consumer drift at build time.
- False "First turn not delivered" warning + duplicate first-turn re-send (#745).
crew spawn's own screen-scrape confirmation could time out even when the first turn genuinely landed, because the crew'sUserPromptSubmithook confirms delivery to the daemon independently of — and can outrace — that scrape. Hook confirmation is now raced against the scrape for the scrape's own full timeout window and treated as primary, not a post-failure fallback: a scrape failure alone no longer concludes non-delivery when the hook already confirmed. As defense in depth,crew sendnow refuses to re-send the exact original task text once delivery is confirmed, closing the double-run scenario where following the (previously false) warning's guidance re-ran the task a second time. - Heal daemon false-FAIL on the plist-drift race (#741, #742).
squadrant heal daemoncould report a failed restart even when the daemon had actually come back up cleanly, racing its own plist-drift detection against the just-completed restart. Fixed at the source of the race rather than papering over it with a retry. - Opencode fact-pipeline perf regression and event-index cache-miss fallback (#743). A per-frame performance regression in the new opencode adapter (introduced during Phase 0-1 development) is fixed before shipping; chatty opencode frames are filtered,
resolve()is TTL-cached, and dead snapshot state is removed. TheeventsTaskIndexnow falls back to a live store lookup on a cache miss instead of losing the event.
- Phase 2 (per-crew
FactLogcleanup,claudeshadow-mode cutover, lifecycle refinement) is deliberately deferred — not part of this release. - First opencode crew after upgrade should be watched for event-pipeline health (I5 invariant floods, subagent-orchestrating crews' known ~2 CREW IDLE per task).
- Per-role thinking level →
--effort. Roles could already pin a model (defaults.roles.<role>.model); they can now also pin a thinking level via a newthinkingfield, emitted as claude's--effort <level>flag (plussquadrant launch --thinking/crew spawn --thinkingCLI overrides). Deliberately distinct from the existingdefaults.efforttokenomics dial (squadrant effort) — same word, different concept, kept apart on purpose. Claude-only; codex/opencode/gemini reject the flag. - Crew answer (#592).
squadrant crew answer <project> <name> <option>deliberately answers a crew's openAskUserQuestion/permission modal — reads the rendered option list, requires an explicit index/text match (never an implicit default), and drives the selection via Down/Up + Enter.crew replynow delivers through the same path ascrew sendbefore transitioning task state, instead of silently dropping the message body on a no-op "success".
- False CREW STALLED from a stale pendingTool (#542). Root cause: the daemon's cmux events-bridge had no case for
agent.hook.PostToolUse, so pendingTool never closed for a crew whose tool call had genuinely finished. The #492 stall veto then vetoed every real turn-completion forever, and the watchdog eventually fired a false CREW STALLED against an idle crew. Fixed at the transition — events-bridge now closes the window on PostToolUse, the state machine recognizes the cmux bridge's own PostToolUse spelling, and the watchdog gained a defense-in-depth check that recovers toawaiting-inputinstead of stalling when idle evidence already exists. - Pane-error false terminalization (#704). The interactive-probe's error detector matched its error vocabulary against any pane line — including content a crew merely displayed or quoted — and unconditionally terminalized the crew. It now skips visibly quoted/prefixed lines (box borders, blockquotes,
cat -nechoes) and cross-checks the crew's surface-liveness probe before terminalizing; an "alive"/"unknown" verdict downgrades to a new non-terminaltask.warnControlEvent, surfaced to the captain pane as "CREW WARN" instead of being silently dropped. - Heal daemon restart verification (#729).
squadrant heal daemonpreviously deferred to a cautious plainkickstart(no-k), a documented no-op on an already-healthy daemon, so a plist-matching heal run could report "restart complete" without touching the running process. It now forceskickstart -k, polls the pid until it actually changes, and retries thekickstart -kcall itself (default 5×300ms) to avoid racing a preceding bootout's still-unloading exit handler. - Unclean daemon death detection and delivery jam isolation (#589, #590). Persists a JSON exit marker on every shutdown path (including crash handlers) plus a running heartbeat marker touched every ~60s; a running marker that survives to the next boot with no exit marker to explain it is now logged and alerted as an UNCLEAN death (SIGKILL/OOM/power-loss), instead of reading identically to a first boot. The delivery loop also wraps each project's per-tick pass in its own try/catch so one project's exception can't starve delivery for every project after it, and backs off exponentially (capped at 60s) once a project crosses its max-defer threshold instead of polling forever.
- Task records no longer terminalize while the crew surface is alive (#595).
reapOrphanedCrews, the wall-clock task-timeout sweep, andstore.put()could all previously terminalize or silently overwrite a task record without checking the crew's own pane was actually gone. All three are now gated on the surface-liveness probe (alive/unknown fail safe),awaiting-inputis added to the existing #629 blocked/review ceiling exemption, andcrew send's reopen is now reported back to the caller instead of being invisible. - Oversized first-turn text corruption (#730). A multi-KB first-turn paste into a claude crew's pane could look "settled" before fully landing, letting Enter submit a truncated draft. First-turn text above 1200 bytes is now spilled to a temp file with a short "cat it and follow it exactly" pointer instead of being pasted verbatim; the daemon dispatch still receives the full task text.
- Captain memory write gate (#556). Crews previously had unrestricted write access to their captain's long-term memory directory, which is loaded into every future session. The existing unmatched PreToolUse hook now denies a matching Write/Edit/MultiEdit/NotebookEdit, or a Bash command whose text references a memory path, when issued inside a crew session — with an exact deny reason logged to stderr. Crews report, captains decide what is durable.
- Named peer identity for daemon-sent messages (#711).
squadrantd's receipt socket now registers a~/.claude/sessions/<pid>.jsonentry keyed to the socket it bound, so CREW DONE/BLOCKED/IDLE/TAKEOVER messages render as a named peer instead of the anonymous "Another Claude session" framing. Registrykindcorrected to"daemon"(verified against the Claude Code 2.1.241 binary'sinteractive|bg|daemon|daemon-workerallowlist), and stale entries are cleaned up on both pid reuse and process exit. - Delivery probe failures no longer read as "no-box" (#713, #714). A failed cmux
read-screeninvocation was previously conflated with a genuinely unconfirmed input box. It is now classified as a distinctprobe-faileddefer reason with its own DELIVERY STUCK wording, and the delivery loop re-resolves the captain surface once per attempt on a probe-failed defer (no-box/modal/draft never re-resolve, since the surface is known alive). - Heal status delivery-queue visibility (#715).
heal statusnow surfaces delivery-queue health via a structuralstuckboolean onComponentHealth/HealComponent(no string parsing) — only a genuinely stuck queue fails heal status; a merely deferring queue is advisory. - Liveness-tick captain log spam (#732).
runLivenessTicklogged a captain's derived state on every ~6s tick regardless of change, burying real events for stopped-but-still-listed projects. It now logs only on an actual state transition (or first observation).
- Docs. Added the AFK auto-continue safe-option-only clause to captain/crew templates (#616); made the #711 kind-allowlist evidence reproducible in-comment; dropped the stale "unreadable screen" phrasing from the no-box doc (#714); corrected opencode channel state, the captain socket path, and shadow-rollout wording across the agent-control-channel and architecture diagrams (#667).
- Agent Control Channel & Captain Channel (#667). Replaces screen-scraped liveness and pane-typing delivery inference with native agent control channels and socket messaging.
- Implemented the
ControlChannelport with a five-branchDeliveryOutcome(delivered,held,denied,unreachable,accepted), plus nativeLifecycleSourceimplementations (ClaudePeerRegistrySource,OpencodeControlSource). - Added rollout configuration flags:
defaults.controlChannelfor crew delivery anddefaults.captainChannelfor captain delivery (off/shadow/on). - Claude Peer Channel (Slice 3): Routes crew messaging over Claude's native
--messaging-socket-pathwith NDJSON framing, T1-confirms-T0 delivery receipts, a background receipt listener for held/denied/delivered observability, and recordsaccepted-unconfirmedstatus. - Captain Channel (Slice 4): Routes captain-bound delivery through the peer control socket (
captainChannel), bypassing pane-defer machinery (ghosts, modals,no-box). Added Telegram delivery receipts for held and unreachable captain deliveries, andsquadrant pingnow reports honest delivery outcomes.
- Implemented the
- CLI Agent & Model Runtime Overrides (#627). Added
--agent <name>and--model <name>flags tosquadrant launch <project>so operators can override configured captain agent and model defaults at runtime. - Claude Instruction File Projection Target (#705). Brought
CLAUDE.mdinto the cross-agent projection system (packages/shared/src/agents/projection.ts), unifying instruction emission alongside Codex, Gemini, and opencode to eliminate instruction-file drift across formats. - Captain Session Self-Description & Identity Wiring (#708, #709).
- Passed Claude's
-n, --nameflag during launch and crew spawn so captains (squadrant-captain-<project>) and crews (squadrant-crew-<project>-<name>) are self-describing in session registries. - Resolved captain
sessionIdfrom launch-time socket paths and wired it into captain-bound messages, restoring pid-reuse protection.
- Passed Claude's
- Local Dev Daemon Opt-In (#694). Added the
SQUADRANT_DEV_DAEMONenvironment variable allowing developers to run a local daemon from a repo checkout while maintaining strict refusal for linked worktrees.
- Captain channel init retry loop (#712). Fixed an issue where transient socket bind errors (
EACCES) at daemon boot permanently latched the daemon into pane-only fallback mode for its entire lifetime. It now retries initialization with capped exponential backoff, unrefs the timer, and catches retry errors cleanly. - Captain socket collisions during batch launches (#706). Fixed
launch.tsusing an undefined command-level positional argument instead of the target project name, which caused batch (--all) and parallel launches to collide on/tmp/cc-socks/squadrant-captain-undefined.sock. - Receipt listener daemon crash on boot and CLI hang (#695, #696).
- Handled UDS error events during listener startup, scoped receipt sockets by PID (
squadrantd-<pid>.sock), and cleaned up stale socket files before binding to prevent crash-loops. - Unref'd the receipt server and cleaned up socket files on exit so short-lived CLI commands (like
squadrant ping) do not hang.
- Handled UDS error events during listener startup, scoped receipt sockets by PID (
- Captain and crew resolution on symlinked paths (#689, #696). Switched registry lookups from working directory paths (which broke on macOS symlinks like
/tmpvs/private/tmp) to launch-timemessagingSocketPath, preventing falsegoneoraccepted-unconfirmeddelivery verdicts. - Captain re-adoption with truncated cmux argv (#699). Fall back to reading live arguments from the OS process table (
ps) when cmux truncates stored argv at--messaging-socket-path, and addedsquadrant heal captain [project] --allto reconcile degraded registry entries. - Fast-turn confirmation race (#691). Used
statusUpdatedAttimestamps to reliably confirm turns on fast-responding agents. - Control channel exception safety in crew send (#686). Guarded control-channel exceptions in
runCrewSend(both shadow and on modes) to ensure errors log cleanly and fall back safely to pane delivery without breaking the turn loop. - Claude captain visibility in liveness registry (#697). Gated launch
--messaging-socket-pathinjection strictly oncaptainChannel !== 'off'and positioned it last in the command arguments to prevent cmux store truncation from breaking role classification.
- Refuse silent Anthropic fallback (#627). When an alternative agent (opencode/codex/gemini) is launched with no explicit model and defaults to an Anthropic model via global configuration,
squadrant launchnow refuses to boot rather than silently falling back to Anthropic. - Hardened daemon worktree guard (#682). Strengthened the daemon's refusal to bind production sockets from inside linked worktrees against caller context bypasses.
- Daemon socket process isolation (#687). Derived the daemon socket path from
SQUADRANT_CONFIG, enabling complete process isolation across test and dev environments.
- Complete removal of GitNexus integration (#703). Removed all remaining references, skills, and configuration for GitNexus across the codebase, architecture documentation, and agent instruction files following its deprecation.
- Restricted permissions on configuration files and directories (#668). Previously,
config.json(which holds sensitive data liketelegram.botToken) was world-readable (0644). Config files are now tightened to0600, the config and project directories to0700, and the daemon socket to0600after binding. A one-shot migration automatically secures existing paths on update. Additionally,DEFAULT_CONFIG_PATHandCONFIG_DIRnow honor theSQUADRANT_CONFIGenvironment variable.
- Daemon crash-loop due to duplicate global installs (#670). Fixed an issue where having Squadrant installed globally multiple times (e.g., via
npmandpnpmsimultaneously) hijacked thelaunchdplist and caused a daemon crash-loop.- The update banner now prints the upgrade command for the install's specific package manager.
ensureDaemonnow refuses to seize a plist owned by a different, still-installed Squadrant binary.squadrant doctorwarns operators of multiple installations.squadrantdrefuses to bind the production socket from a monorepo or worktree checkout.
- False 'healthy' status when daemon is offline (#671).
squadrant heal statuspreviously reported all components as healthy if the daemon was completely offline, treating an empty component list as vacuously healthy. It now proactively probes daemon liveness via a real socket connection before trusting component data.
squadrant crew closerefuses dirty worktrees. Previously, closing a crew would silently destroy its worktree even if it contained uncommitted files. It now REFUSES to close and requires an explicit--forceflag. Anyone automating crew close will see a new failure if dirty files exist. (#649)
- Operator Takeover Protocol (#649). When the operator works directly inside a crew's tab, they can now record that fact so the captain knows to keep hands off. The crew keeps running normally; what changes is that the captain will not send to it, close it, or act on its lifecycle signals until handback.
- Added CLI commands
squadrant crew takeoverandsquadrant crew handback. - Added in-terminal slash commands
/takeoverand/handback. - Task records now track
operatorHoldstatus. - Operator-held status surfaces in
list,tasks, andhandoff facts. - The watchdog nudges operators on long-running takeovers.
- The daemon suppresses captain pushes and refuses
send/closeoperations on held crews.
- Added CLI commands
- Crew worktrees base on the captain's branch (#661). Crew worktrees are now based on the captain's currently checked-out branch instead of the GitHub default branch, ensuring they branch off the correct contextual state.
- Sweep ceiling measures current working stretch (#664). The sweep ceiling now measures the current working stretch instead of the overall task age. Task records now track the start time of the current stretch on
task.startedandtask.reopened.
- Read fetchAgeMs post-fetch to resolve false staleness (#654). 'squadrant handoff facts --fetch' reported fetchAgeMs measured BEFORE the fetch, so a just-fetched repo could still trip the 24h stale warning. Age is now read after the fetch; test pins an exact post-fetch age.
- Human review gate embedded in templates and made signal-agnostic (#656). It now covers CREW REVIEW, CREW DONE and CREW IDLE. Previously only REVIEW was gated, so a crew signalling done or going idle could get its work merged without operator sign-off. Contradicting 'merge if appropriate' wording removed.
- Captain startup rules moved to templates (#653). Captain startup rules (fetch origin, check branchState flags, identify current task, read handoff) moved into templates/captain.claude.md and captain.generic.md. They previously lived only in plugin/skills/captain-ops/SKILL.md, which loads only on voluntary skill invocation and never for non-Claude captains. Dead status.md reference removed.
- Template and skill audit. Audit of all 9 templates and 16 skills: replaced 5 hardcoded /Applications/cmux.app cmux invocations with squadrant runtime equivalents, removed dead read-status.sh and two further dead status.md references.
squadrant handoff factsreplaces the old handoff authoring layer, which fabricated state instead of reporting it (#650, #651). The previous command copied fields straight from in-process state (currentState = s.completed), which produced emptyblockedItemswhenever the live picture didn't map cleanly onto that shape, and let a captain misreport "188 commits ahead" sourced from a stale local ref nobody had fetched. The new command makes no synthesis decisions — it gathers facts and groups them by provenance (liveRepo,claudeMem,checkpoint,gapSessions) so a reader can see which claims come from the live repo versus a stale archive versus a gap in session history, and judgment about what those facts mean is left to the captain reading them, not baked into the tool.handoff.jsonis now archived on read instead of deleted, so a broken reconstruction can be diagnosed after the fact instead of vanishing with the read. Branch-state verification (branchState) adds fetch-gated truth:upstreamStatus,dirtyWorkingTree,onUnexpectedBranch, andmergedIntoBaseflags, with network access opt-in via--fetchrather than silently hitting the remote on every read. Alongside this, the captain session registry (#651) writes one line per session start to{spokeVault}/captain-sessions.jsonlfrom theSessionStarthook, so transcript attribution has an authoritative record instead of guessing which transcript file belongs to the current captain by file mtime.squadrant tokens— attribute token spend from Claude transcripts (#626). Streams~/.claude/projects/<slug>/*.jsonlline-by-line (never slurps a whole file — transcripts here run up to 5.7MB) to report, per project and in total: captain vs crew, boot prefix vs accumulated conversation (boot = turn-1 total, confirmed via turn-2cache_read), and the four token classes separately (input/output/cache_read/cache_write) — no collapsed total, no baked-in dollar rates. Claude-only today; no speculative multi-reader interface added ahead of a second driver writing an equivalent transcript. A same-day follow-up made the command disclose its rolling ~30-day data window: Claude Code's own transcript retention (cleanupPeriodDays, default 30 days) prunes older sessions, so a shrinking file count read like a drop in spend rather than what it actually was — retention. The reader now tracks and prints the actual[earliest, latest]timestamp range each session covers, everywhere a count is reported.- Persisted work-item primitive —
squadrant work(#630 step B). Per-project JSON store under~/.config/squadrant/work(atomic write+rename, path-traversal guard, 30-day TTL GC on done/cancelled items, no daemon dependency) with a minimal CLI:start/list/done/cancel,--parentfor hierarchical waves, andwork list --tree. A same-day follow-up fixed the default view: hiding all terminal (done/cancelled) items orphaned any still-open child of a done parent, rendering it as a parentless root with no sign it belonged to a wave — the exact "wave finished out of order, what's left?" scenario--parentexists to surface. The default view now keeps a done/cancelled parent visible (dimmed) whenever any descendant is still open, andwork done <id>now warns (non-refusing) when it still has open children. Step A of the same effort removed the deadwrite-status.shinstructions and stopped readingstatus.mdfor task counts — nothing writes that file anymore, sostatus/standup/retrowere silently rendering stale or zero data; they now say plainly they have no task-count data source.
- A crew's shared production daemon could be bounced by any one of the 26 registered projects' crew processes, exposing every in-flight task across all of them to the restart window (#636).
ensureDaemon()'s implicit self-heal ran on every CLI invocation, including crew-spawned ones — which inherit a different PATH/build (worktree-resolved node/pnpm) than the captain, making their plist-drift check unreliable. Fixed in three passes, converging on fail-closed: the daemon's mutating path (plist write, bootout, bootstrap, kickstart) now requires a positively-identified captain invocation (SQUADRANT_ROLE=captain, set at exactly one choke point —launchOneWorkspace) rather than merely the absence of a crew marker, which failed open for codex crews that never receive that marker at all (same failure shape as #499 — marker-absence treated as a positive signal). A small explicit allowlist (isOperatorInitiatedCommand,{launch, init}) covers the two remaining legitimate self-heal cases the captain-only gate would otherwise miss: a fresh install (before any captain marker exists) and a captain launched before this code shipped, whose already-running process can't retroactively gain the env marker until relaunched.squadrant heal daemonis unaffected — it already calls the explicit reconcile path directly, for any role. gitnexus_impactwas mandated in CLAUDE.md as an absolute pre-edit safety gate despite returning confirmed false negatives — a crew followed the mandate exactly and broke 6 daemon-core files (#642 item 1). The tool reports 0 callers / LOW risk for symbols that do have real callers. CLAUDE.md's Always Do / Never Do wording is softened so a LOW/0 result now reads as "no information" (cross-check with grep + typecheck) rather than authoritative assurance; HIGH/CRITICAL results are unaffected and remain meaningful. Advisory pending a full replacement decision.- Crew boot prefix (61.2k tokens, measured via the new
squadrant tokens) was heavier than the captain's own boot (54.5k) and paid on every spawn with little amortization (#625). CrewCLAUDE.mdtemplates only ever referencesquadrant:karpathy-principles— the other 15 squadrant skills (captain-ops, telegram, wiki-ops, etc.) are captain/command-only, but crews were served the full plugin directory's skill listing anyway. A new "subset" plugin-dir mode mirrors~/.config/squadrant/plugin-crewas a strict allowlist (currently justkarpathy-principles) and points the claude driver's--plugin-dirat it for rolecrewonly; captain, command, and side roles are unaffected. Measured 33,436 → 32,079 first-turn tokens (a 1,357-token cut) in a controlled headless comparison.
- Three lifecycle bookkeeping regressions, root-caused from live production evidence before any fix was written (#594, #629). (a) #629 — a crew sitting in
revieworblockedpending a human decision could be killed bysweep()'s wall-clock task-timeout ceiling, which applied to any non-terminal state with no exemption for states that pause on a decision with no natural time bound; live evidence showed a task enterreviewand get cancelled by the ceiling ~9h later, permanently closing thecrew approvepath. Fixed by exemptingisStickyAttentionstates (blocked/review) from the ceiling, reusing the predicate from the #492→#605→#608 family. (b) #594a — a crew awaiting a registered backgroundMonitorwatch was misclassified idle: callingMonitorarms a background watch and returns almost immediately, but the crew's turn then genuinely ends (Stop hook) while the watch is still outstanding, and squadrant read that as "awaiting the captain" when the crew was actually about to self-resume; live evidence showedCREW IDLEfiring 3× for the same turn in a Stop→auto-resume loop. Fixed with newpendingMonitorbookkeeping (armed onPreToolUseforMonitor, not cleared by its ownPostToolUse) that vetoes theturn.completed → awaiting-inputtransition, bounded by a new stall budget so a crew that armedMonitoronce and then went genuinely silent still gets flagged idle eventually. (c) #594b — aCREW IDLEpush could arrive aftercrew closehad already terminalized the task, because the notify decision reads aTaskRecordsnapshot synchronously while the mailbox write is awaited I/O, leaving a gap a concurrent close could land in; fixed by re-checking the daemon's current record right before writing and dropping a notification the store has since superseded. 11 new tests across state-machine/watchdog/daemon/delivery-loop, all failing before the fix and passing after; full suite (2239 tests) green. Explicitly does not cover #542/#515 — a different root cause (aPostToolUsethat's dropped entirely, never clearingpendingTool), unaffected by this change. crew approve's PR title was the crew's raw task prompt — the captain's instruction, not a change summary (#611). Now prefers, in order: the branch's last commit subject, then the review note, then the task-prompt first line as a last resort.- Defer/stuck delivery logs gave no way to diagnose which project or why, and the stuck alert's wording actively misled when the real blocker was a modal (#617, #484). Threaded the existing defer classification (modal / no-box / draft, plus a byte-identical-hold "stable" signal) through to both the per-defer log line (project + reason, throttled to onset and every 30th tick) and the stuck-alert text — a modal now gets its own message instead of the generic draft/ghost wording.
installClaudeHooksnow unconditionally verifies and repairs the full squadrant hook set on every run (#615), logging a warning when a hook — most notably theAskUserQuestionmapping crew-blocked signalling depends on (#560) — is found missing from an existingsettings.json. Also adds an opt-indefaults.claudeEnvconfig field that deep-merges intosettings.json'senvblock (non-clobbering, a conflicting existing key is logged not overwritten); absent by default, so AFK auto-continue stays off unless a machine opts in explicitly.
squadrant diff— cmux-native crew code review (#596, #599, #604). Opens a crew's diff entirely inside cmux, no VSCode. Phase 1 (#596) addssquadrant diff <project> <crew>via a newRuntimeDriver.showDiff()capability, resolving the crew's worktree/branch through the daemon ledger. Phase A (#599) extends it with--staged/--unstaged/--workingto peek at a crew's uncommitted working tree mid-task, not just the branch-vs-base surface. Phase B (#599) adds a non-terminalreviewcheckpoint between crew work and PR creation:crew signal reviewfires a "CREW REVIEW" alert (parallel to the existing blocked/done machinery, no new parallel system),crew sendclears it back toworkingas feedback, and the newsquadrant crew approve <project> <crew>— only reachable fromreviewstate — pushes the branch, opens the PR viagh pr create, and terminalizes the task. #604 rounds it out with--pr <N>(wrapsgh pr diff),--base/--head/--against(merge-base diff), and a no-args list-and-pick mode over live crews.explainer-reelskill (#598). Wraps iart-ai's explainer-video-skills with a squadrant dark-neon style pack — design tokens, an SVG component library, and a Playwright→FFmpeg GIF pipeline — plus an interactive "swimlane band" HTML mode for click-through system-flow diagrams.
squadrant difffailed on every real invocation with "Error: --focus requires a value," hidden entirely by CI. cmux'sdiffsubcommand defines--focusas a value flag (<true|false>), not the bare toggleshowDiffpushed — invisible to tests because the args array is asserted against a mockedexecFile, never handed to real cmux. Fixed to push--focus true.crew approvewas unreachable — the new review gate didn't actually gate. Two stacked bugs. (a) Crew's own completion-protocoltask.doneunconditionally overrodereview → done, bypassing the gate because state already read'done'by the timeapproveran (#605):reduce()now vetoes anytask.donewhilestate === 'review'unless it carriessource: 'approve', the distinct terminal channel onlyrunCrewApprovestamps. (b) That guard only coveredtask.done— a crew's normal turn-end (task.turn.completed) still fell through toawaiting-inputand clobberedreviewthe same way, since a turn always ends right aftersignal reviewfires (#608). FactoredisStickyAttention(state)(blocked | review) and applied it at every liveness-absorb guard so the two states move in lock-step;task.turn.startedis untouched, so the reject/feedback path still resumesreview → working.- Telegram
/launchsilently opened nothing (#586). The curated command path execs/launch's argv from the daemon, which has noCMUX_WORKSPACE_IDand no terminal — without--headless,ensureCmuxReady()hit theisInsideCmux()gate, openedcmux.app, and exited before the workspace ever launched. #520 had already fixed this on the boot-if-down path; the curated command path was missed. Audited the rest ofcommands.ts— no other command carries a terminal/cmux gate.
- The captain-delivery probe could inject a real backspace keystroke into a draft the captain was actively typing, and force-deliver a queued message on top of it — this fired 4 times in production and stalled a P1 security task for 27 minutes (#579, #484). The
maxDefersbackstop escalated to a live backspace-invariance probe purely on how many times delivery had deferred, even while the pane's content was still changing every poll — a genuinely typing captain, or a Claude Code suggested-prompt ghost that didn't match the narrow "Press...to..." detection regex (#579), would eventually cross that count and get probed anyway. Probe escalation now requires content stability (byte-identical polls) and never fires on defer count alone, so a live, changing draft is never touched. Because deferring forever behind a genuinely stuck delivery was previously silent, a new "DELIVERY STUCK" alert now fires once per stall (and re-arms after recovery) through the notifier plugin and Telegram — routed out-of-band, not through the same captain mailbox the stall is blocking, so the operator can be reached while delivery is still stuck rather than only after it clears. squadrant statushid a live captain entirely if the project had nostatus.md, even thoughstatus.mdis documented as optional (#549). The default table usedstatus.mdas the source of truth for whether a project's row rendered at all, so a project that had never written one — or had a corrupted one — silently vanished from the table with "no status.md," even while the daemon's liveness registry showed the captain demonstrably alive. Captain liveness is now always rendered from daemon state, withstatus.mdonly supplying the optional crew/task/progress columns on top of it. A corruptstatus.mdis now shown distinctly ("status.md unreadable", in red) instead of being swallowed and rendered the same as a missing one, and a deliberately-stopped captain now gets its own magenta indicator instead of being lumped in with one that crashed.
- A crew's blocked
AskUserQuestionprompt could go completely unnoticed — noCREW BLOCKEDever fired (#560). A crew's own per-crew Claude hook set had noPreToolUsecoverage at all, so opening the question modal emitted nothing to the daemon; detection had accidentally been leaning on an unrelated hook subsystem that didn't reliably cover crews. Crews now get a dedicatedPreToolUse → AskUserQuestionhook that extracts the real question and options from the tool call and reports it as blocked-on-input, even falling back to a generic message if the tool payload is malformed — so a stalled crew is always surfaced instead of silently waiting forever. CREW DONEcould be lost because the send and close paths disagreed on which task record was "the" active one, and a signal on an already-finished task reported success while doing nothing (#574, #557). When a crew had duplicate task records under the same name, sending and closing picked different ones, so the two ends of a crew's lifecycle could silently drift onto different task ids. Task-record selection is now shared logic used everywhere, andcrew signal done|blocked|failednow fails loudly instead of quietly no-opping when the target task is already in a terminal state.effort set --projectsilently rewrote the global effort dial instead of the named project's override, and captains could be told about an effort change that didn't actually apply to them (#575, #576). The--projectflag was accepted but ignored, so a per-project effort request landed globally; notifications were also broadcast as one blanket message, so a project running its own override could be told a value that wasn't actually its effective effort.effort set --projectnow writes a scoped per-project override (and rejects an unknown project name outright instead of writing a stray file), and change notices are computed per recipient so only captains whose effective effort actually changed hear about it.
- The daemon never re-adopted a captain after a cmux restart, and silently killed its live crews (#565). When cmux crashes and reattaches a session, it degrades the session's
launchCommand— it comes back as a bare["claude"], stripped of--append-system-prompt-file. squadrant classifies a captain by its launchCommand, so the record readrole: "unknown", was filtered out of the liveness snapshot entirely, andrunLivenessTickthen marked the captain ended purely because it was absent from the snapshot. The captain readstoppedfor 8 hours while it was demonstrably alive and working; the orphan-reaper repeatedly cancelled its crews' task records while those crews were alive and actively committing code, soCREW DONEcould never fire and cross-project dispatch never settled. It was not even hand-fixable: purging the liveness registry and patching the correct pid was overwritten on the next daemon boot, because the snapshot still omitted the project. Fixed in two layers: (1) a session's identity (sessionId) now outranks a degradedlaunchCommandclassification; (2)markEndednow requires positive evidence that the pid is dead — absence from a snapshot is no longer a death sentence. The second layer is the important one: it blocks the whole class. Absence of evidence is not evidence of absence, and reaping is a destructive action that must never be taken on an inferred signal. runtime send/crew sendtyped a message into the input box, never committed it, and reported success anyway (#566). Two stacked bugs. (a)runCrewSendonly wrote a stderr warning whenconfirmedSendToPanereported the paste-settle-Enter loop had exhausted, then returned normally — so the CLI printed✔ Sentand exited 0 for a message still sitting unsent in the box. Two live agents ended up blocked on a question that was typed but never submitted, with nothing in the system reporting a problem. (b) A v0.16 regression: routingruntime sendthrough the mailbox (#529) converted an unconditional direct-to-pane send into one gated by the delivery loop — which is gated on captain liveness. With liveness wrong (#565), every message was silently swallowed. Fixed:appendCaptainMessagereturns itsseq, andwaitForCaptainDeliveryconfirms the delivery cursor acked it before the CLI reports success; otherwise it throws (non-zero exit, no✔).crew sendnow throws on unconfirmed submit. A send now either lands or fails loudly — there is no third outcome. The #529 draft-clobber protection is retained: the send is never pre-gated on liveness, only its confirmation is.- Crews running the test suite could starve the machine and take down the cmux control plane (#570).
package.json's"test"script was a barevitest— with norunsubcommand, which in a TTY (exactly what a crew has inside a cmux pane) enters watch mode: the process never exits, holds a filesystem watcher on the whole monorepo, and re-runs the entire 2044-test suite on every file change. Several crews, each continuously editing files, therefore each spun an infinite full-suite re-run loop. (This also explains crews appearing to hang: their "run the full suite, then signal done" step could never return.) Fixed to"vitest run"(one-shot, always exits); watch mode is now opt-in via"test:watch"for humans. Measurement also showed that even one-shot concurrent runs saturate the machine (4x peaked at 11.77 load on 12 cores), soscripts/heavy-lock.mjsadds a machine-wide mkdir-based semaphore (macOS has noflock(1)) bounding concurrent heavy commands viaSQUADRANT_HEAVY_MAX(default 2). Queued runs wait rather than fail — a failing test command would just teach crews to skip testing. A slot held by a dead pid is reclaimed, so a killed crew can never deadlock the repo. The guardrail now lives in code, not in a playbook: the guidance already existed incaptain-opsand was ignored.
- Crew worktrees silently type-checked and tested against the MAIN checkout instead of their own code (#387):
git worktree addnever populatesnode_modules, and crew worktrees live nested under<repoRoot>/.worktrees/. A worktree with no localnode_modulesdoes not fail — Node's module resolution silently walks UP past it into the main checkout'snode_modules. So a crew'stsc/vitestcould resolve workspace packages to the main repo's stale code rather than the crew's own uncommitted changes, and pass. This made crew self-verification unreliable: a TS2339 slipped past every local check and was caught only by CI.addWorktree()now installs the worktree's own dependencies immediately on creation, detecting the package manager from the lockfile present (pnpm / yarn / npm / bun), no-opping for non-JS projects, and warning loudly (rather than silently skipping) when a package.json has no lockfile. pnpm was NOT at fault — each worktree correctly carries its own workspace root. - N concurrent crews could starve the machine and take down the cmux control plane (#387): crews run build/test commands at their own discretion, so there is no central point to queue them. The crew's top-level process is now launched under
nice -n 10, which every child it forks (tsc, vitest workers) inherits — so a burst of concurrent crew builds degrades gracefully instead of starving cmux and the daemon. - Spotlight indexing exclusion for crew worktrees is now automatic (#387): the
.metadata_never_indexmarker is written to the worktree root on creation (macOS only, best-effort). Previously this was a manual, uncommitted, single-machine mitigation.
- Update notifier: warn installed users when a newer squadrant is on npm (#536): CLI startup checks
registry.npmjs.orgdirectly (never thenpm viewCDN — see the v0.13.1 silent-publish-failure incident), caching the result for ~24h. Behind → a single actionable stderr line; up-to-date, offline, or timed-out → silent no-op, fired fire-and-forget vianode:httpswith the request socketunref()'d so it never adds latency or blocks process exit. A failed check backs off for 1h instead of re-hitting the registry on every invocation. Opt-out viadefaults.updateCheck=falseorNO_UPDATE_NOTIFIER. squadrant launch --keepflag (#534): mirrors--fresh— resumes the latest session even after a day rollover or template-hash change, so a captain session can survive across day/template boundaries instead of being forced fresh. Still starts fresh on a genuine first launch.--freshand--keepare mutually exclusive.- Daemon boot/exit markers (#535): the daemon now stamps an explicit boot marker on start and an exit marker on a clean
stop(), closing an un-awaited-shutdown gap where teardown could be skipped from the log's perspective on restart.
squadrant statusdefault view showed a live captain as offline (#538): the default table derived its captain indicator fromstatus.md'scaptain_sessionfrontmatter — a Reactor-engine relic (retired #155) nothing writes anymore — while--detailedalready read the daemon'sLivenessRegistrycorrectly. The default view now shares the samequeryHealth()call as--detailed; an unreachable daemon renders?rather than asserting offline (a false "unknown" is safe, a false "offline" is not).- Captain
crew sendcould silently confirm an open modal's default option, dropping the intended message (#516):confirmedSendToPanehad no equivalent to the delivery path'shasModalOptionListguard, so a paste+Enter into an openAskUserQuestion/permission modal would confirm the modal's highlighted default instead of delivering the message — this produced two wrong commits in production.crew sendnow precheck-fails loudly (throws, no "✔ Sent") instead of silently succeeding, without prematurely clearing a task'sBLOCKEDstate ahead of the precheck. - Two racing captain records for one project could flip a live captain to
gone(#527): when two cmux sessions share a cwd (a live captain plus a stale corpse from session restore), the runtime snapshot returned two records for the same project key andLivenessRegistry's last-write-wins apply could let the dead-pid record win. Records are now grouped and deduplicated per project before applying, preferring the record with a live pid. - Daemon-restart and effort broadcasts could clobber an in-progress captain draft (#529): both broadcasts wrote directly into the captain pane via
CmuxDriver.send, unconditionally overwriting whatever the user was typing. They now route through the same mailbox (appendCaptainMessage) the delivery loop already drain-protects. - CLI-originated interrupts (ping, runtime send) bypassed the same draft-clobber protection (#529, #531): routed through the mailbox;
squadrant runtime send-keyis removed (see Breaking). - Crew close/respawn race could leave a zombie task record or fire a false
CREW STALLEDfor an already-closed crew (#513).
CREW IDLEwording softened (#522):awaiting-inputis reached only via a genuine turn-boundary event (task.turn.completed) — never a watchdog-derived path — so it always means the crew deliberately ended its turn, including a long-lived crew pausing between sequential subtasks. The old "review and reply or close" phrasing read like a possible fault; it now reads "turn ended, awaiting your reply."
- Deleted the
squadrant runtime send-keycommand. It had zero callers across all plugins and templates. Deferring an unconditionalEnterkeypress through the mailbox guard changes its semantics entirely (what it submits depends on what draft the user typed while the key was queued). If you need to simulate a raw keypress without draft protection, shell out to the underlying binary directly:/Applications/cmux.app/Contents/Resources/bin/cmux send-key --workspace "workspace:N" Enter.
- Captain liveness redesign — hybrid ground-truth (retires the streak sweep): captain open/close/crash detection is now driven by a persisted
LivenessRegistry(<stateRoot>/liveness.json, survives daemon restart) reconciled from the cmux session store via a newDaemonSurfaceDriver.liveness()seam and arbitrated by a per-tick pid floor. It distinguishes a clean close (stopped) from a crash (gone— store record lingers with a dead pid) fromalive, with provenance precedenceruntime ≥ agent > scan. Replaces the K=3 title-sweep streak model that produced both false-positives and false-negatives and lost all state on daemon restart. Dashboards, the health IPC, and the Telegram boot-if-down probe now read one ground-truth source. - Per-project effort override:
resolveEffortnow honors a per-projecteffortin the project override config;squadrant effort --project <name>shows the resolved value. The global dial is unchanged, and a project with no override falls back to it. - Web dashboard LIVE "mission control" tab (now the default tab): a compact one-line-per-project view with a state-count header, attention-first ordering (blocked/errored float up, offline sinks), last-seen-age and task-count columns, a search box, sortable column headers, a status filter, and expandable per-crew detail rows. (Per-crew pid/uptime/agent/model are placeholders pending #524.)
- Web dashboard: status filter + per-captain badge on the projects view.
- opencode init setup guidance + default global config provisioning (#511).
- Daemon broadcasts a restart notice to captains on build change (#510).
- Captain misclassified as alive when stopped, blocking Telegram auto-launch (#517): the alive check now reads fresh pid-verified health, so a stopped or crashed captain no longer reads
aliveand correctly triggers boot-if-down. squadrant launchboot-if-down was a silent no-op outside cmux (#520): added an explicit--headlesslaunch path.- CLI and web dashboards showed non-running captains as
idle: a captain with no registry entry (unknown) now correctly rendersoffline. - A locked or partial cmux store read no longer false-closes captains:
liveness()now throws on an unreadable snapshot (leaving the registry intact) instead of returning an empty set that would mark every captain stopped. - CREW IDLE flood during long tool-executing turns (#492): verified fixed (the
pendingToolveto holds) and closed.
- Retired the title-sweep / K=3 streak captain-liveness model in favor of the registry + pid floor.
squadrant ping/squadrant dispatchreach any registered project (#506): cross-project ping (fire-and-forget) and dispatch (tracked task, report-back) are no longer gated on shared group membership — any registered project can reach any other. Group membership now only grants extras:acceptDelegationsgating and boot-if-down (cross-group dispatch fails fast instead of burning the full warmup timeout on a target that was never going to boot).squadrant group dispatchis now a deprecated alias forsquadrant dispatch.
- Daemon self-heals undelivered first turns instead of only alerting (#466): the CREW UNDELIVERED watchdog measured "undelivered" from
lastHeartbeat, which unrelated heartbeats kept resetting — a dropped first turn could go unrecovered indefinitely while the task still reportedworking. The sweep now measures the undelivered window fromcreatedAt(monotonic, immune to heartbeat masking) and auto-resends the first turn once past budget, re-checking TUI readiness and confirmation state to stay idempotent. Scoped toclaudecrews for now. - opencode SSE bridge boot-grace raised 30s -> 120s (#504): the bridge's connection-retry window (30s) was shorter than the first-turn delivery budget (90s) it needs to outlast, so under concurrent crew-spawn load a slow-to-bind opencode server could cause the bridge to give up before delivery even completed — permanently blinding turn-end and permission-gate detection for that crew with only a log line, no captain-visible signal.
- fix(crew): opencode first-turn drop from splash-marker drift (#499): the hardcoded splash marker
Ask anything…(U+2026 ellipsis) never matched opencode's real render,Ask anything...(ASCII dots), so first-turn delivery confirmation could false-positive with no retry safety net. Fixed with drift-tolerant matching, asawSplashfail-closed latch, and a positive readiness gate. - fix(web): idle/never-launched projects health-neutral in the dashboard rollup (#498): projects registered but idle/never-launched (captain not alive) no longer trip the master annunciator to DEGRADED, and template-hash drift is demoted out of the rollup — only a genuinely alive captain's delivery backlog or real faults read DEGRADED now.
- CREW IDLE no longer floods the captain during long tool-executing crew turns (#492):
task.turn.completedis treated as liveness-only while a tool call is still in flight, vetoing spurious awaiting-input flaps from racing lifecycle sources.
- Dashboard health monitoring (#491, closes #322): four new health signals surfaced on the web dashboard.
- B1 —
CaptainDelivery.stats()exposes an in-flight defer count and astuckflag once it crossesmaxDefers; Overview gets a "Delivery defers" trend and each project card rolls up to caution/fault (guarded on!captainStopped). - B2 — the CREW UNDELIVERED watchdog condition is promoted to a first-class
ComponentHealthsignal (detail = "undelivered (<state>)") instead of only firing as a notification side-channel; Overview gets a headline banner + count. - B3 —
TelegramBridge.health()trackspolling,lastSuccessfulPollAt, and the last poll error, closing the "false green while the poll loop is silently dead" gap; surfaced as a Daemon-tab instrument row. - B4 —
LifecycleSourcegets an optionalhealth(), implemented onCmuxStoreSource,NativeHookSource, andCodexAppServerSource, aggregated and rendered as per-source instrument rows on the Daemon tab.
- B1 —
mailboxStatsnow includes rotated archives (#322):sizeBytes/oldestEntryAgeMspreviously only counted the current un-rotated log file.
- README rewritten as a user-journey guide (#489): split into a top-level narrative (You → Captains → Crews) plus a separate QUICKSTART and reference doc.
- Architecture diagrams refreshed (#490): added a self-contained lifecycle+delivery flow diagram and updated the monorepo architecture diagram.
- Daemon no longer auto-answers the captain's modal (#484): delivery no longer auto-submits into the captain's
AskUserQuestion/permission modal. A positiveN. Labeloption-list detector (hasModalOptionList) defers delivery before the ghost/probe branches can mistake a modal's highlighted option for a live draft.
- Crew first-turn no longer dropped on slow boot (#466): crew first-turn delivery now gates on the Claude pane being CC-initialized (the persistent bottom status block), not just the bare input box which renders during claude-mem cold-init where keystrokes are silently dropped. Readiness budget extended 30s->90s because crews cold-init under load (the captain path boots unloaded). First fix in this class to target delivery readiness rather than post-hoc confirmation.
- Captain draft no longer clobbered (#258): delivery defers while the captain's input box has an in-progress draft instead of gluing the crew message onto it and submitting. Grapheme-aware liveness probe (handles drafts ending in a space or emoji), 50ms settle re-read, restores any probed character; inconclusive liveness defers. The maxDefers backstop still guarantees eventual delivery.
- Ghost/hint no longer blocks delivery (#258 follow-up): a ghost/history hint in the captain input box (dim suggestion text) no longer causes delivery to defer forever — a backspace no-op (ghost, non-editable) now delivers, while a real draft (backspace consumes a character) still defers. Closes the regression from the initial #258 defer-on-ambiguity.
- Terminal events survive daemon restart (#474): CREW DONE/BLOCKED/failed/cancelled events bypass the stale-skip and deliver regardless of age, so a daemon restart >5min after enqueue no longer silently drops them. Added per-decision delivery logging to squadrantd.log.
- Inert null-draft escalation (#477): removed a no-op code path (probe escalation was ignored by the cmux driver for null-drafts) and corrected its misleading test. No behavior change.
- First-turn delivery could still silently drop on a single (non-concurrent) spawn of a large
multi-line
--agent claudecrew — the v0.13.2 fix only covered concurrent-spawn load. Three interlocking causes: theCREW UNDELIVEREDwatchdog was unreachable for a crew whose first turn never landed (it never leaves thesubmittedstate), the boot-readiness gate could latch onto the claude-mem startup banner (which has HR lines but no input-box prompt glyph) and paste before the real input box rendered, and a screen change during boot could be mistaken for a confirmed submit. The watchdog now also covers quietsubmittedcrews, the boot gate requires the actual Claude Code input box (❯prompt), and a screen-change only counts as delivery when the paste was observed. (#466)
- First-turn delivery confirmation is now hook-driven instead of inferred from the terminal
screen. A
UserPromptSubmitClaude Code hook (registered per crew) fires when the prompt is actually submitted and authoritatively stamps the first-turn-confirmed signal, ending the class of bugs where a screen-scrape heuristic mis-read an opaque TUI. The confirmation is stamped once and is the sole confirmation source for claude crews (the screen-scrape remains only as a fallback when the hook cannot be installed). (#470, #472)
- First-turn delivery could silently drop under concurrent spawn load: spawning several large
multi-line
--agent claudecrews at once could leave one sitting at an empty prompt (0% context) because the boot-readiness gate latched onto the session-start banner before the input box had rendered, so the paste landed in a not-ready box and the retry loop exhausted without confirming submission — whilecrew spawnstill reported success and the watchdog mislabeled the inert crew as "deep thinking". The boot gate now waits for a parseable input box before pasting, first-turn delivery reports a delivery status and auto-falls-back to the confirmed send path when the paste can't be confirmed, non-delivery surfaces a warning instead of a false success, and the watchdog emits a distinct "CREW UNDELIVERED" alert (via a new first-turn-confirmed signal) instead of "deep thinking" for a crew that never received its task. (#466) - Daemon task-ledger cruft and ghost lifecycle notifications: terminal task records accumulated
indefinitely, and an abandoned task whose crew surface was gone could still fire CREW IDLE /
CREW TIMEOUT notifications, confusingly surfacing mid-session. The daemon now prunes terminal
records per project on sweep, suppresses lifecycle notifications for interactive tasks whose
surface is provably gone, and
crew tasks --all-terminalbulk-purges terminal records. (#457) crew spawn --task-filewas not readable from an isolated-worktree crew's working directory; the file is now copied into the worktree root and the crew is given a short pointer first turn. (#458)- The release workflow reported success even when
npm publishfailed (the publish step wascontinue-on-error), so a bad token could leave npm a version behind while CI stayed green. The publish step now fails the job on error and a verification step confirms the published version is live on the registry. (#463)
- Large first-turn paste-strand: spawning a crew with a large task description could strand the crew's first turn unsubmitted — the big payload pushed Claude Code into bracketed-paste mode and the submit Enter was swallowed before the paste finished rendering, so the task sat in the input box and never started. An un-rendered large paste is no longer treated as submitted; delivery settles the paste window first, then sends a separate confirmed Enter. (#455)
- Transient API retry mis-classified as CREW FAILED: when a crew's underlying CLI hit a transient API error and entered its own retry loop, the pane classifier read the in-flight retry as a fatal failure and fired a bogus CREW FAILED signal even though the crew recovered on its own. In-flight retries are no longer classified as terminal failure. (#459)
- Crew spawn collision on stale branch: if a previous crew left its
crew/<name>branch behind (closed without cleanup), spawning a new crew with the same name hard-failed on the branch-already-exists collision. Stale crew branches are now reused or uniquified on spawn instead of erroring. (#460)
- Native lifecycle ingestion (#333 Phase 1): squadrant now installs and owns its own Claude Code lifecycle hooks via a NativeHookSource (primary), registered namespaced and non-clobbering in
~/.claude/settings.json, alongside a CodexAppServerSource adapter for the codex app-server and a CmuxStoreSource backup that reads~/.cmuxterm. A new internalsquadrant hooks claude <event>CLI bridges Claude lifecycle events to the daemon. These run additively next to the existing cmux events bridge. (#333) - Interactive no-arg
squad launch: runningsquad launchwith no project opens a multi-select and boots the chosen captains in parallel.
- First-turn drop regression (#333): AskUserQuestion was registered as an invalid top-level Claude hook event, which made Claude Code show a blocking "Settings Warning" modal at session start that swallowed a freshly spawned crew's first turn. It is now registered correctly as a PreToolUse hook with
matcher: "AskUserQuestion". (#333) - opencode multi-option picker is now detected as CREW BLOCKED so the captain is prompted to choose, instead of the crew stalling silently while the daemon shows it as working.
- Crew first-turn could silently strand unsubmitted: a large first-turn payload pushed Claude Code into bracketed-paste mode, so the submit Enter landed as a literal newline inside the paste instead of submitting — the crew sat at 0% with the task stuck in its input box. First-turn delivery now pastes, waits for the paste window to settle, sends a separate confirmed Enter, and re-issues only Enter (never re-pastes) if still unsubmitted. (#339, #447)
- Large follow-up
squadrant crew sendmessages could strand the same way (the atomic send path hit the identical paste race). The confirmed-submit sequence is now extracted into a shared helper and applied to the follow-up send path too. (#448, #449)
- The built-in default crew model is now sonnet (was opus); opus remains opt-in via the extreme routing tier or an explicit
--model opus. This matches the intended tokenomics, makes fresh installs default to sonnet, and resolves the recurring config-drift advisory on upgrade. (#446)
- Telegram: the captain now sends a typing indicator when it receives an inbound message, so the phone shows activity while the captain works. (#431)
- opencode crew first-turn boot-race: spawning an opencode crew could silently drop the first turn at the splash while the daemon recorded the task as working (false-healthy). Replaced the fixed 2.25s retry window with a time-bounded 15s confirm-on-delivery loop. claude/codex paths unchanged. (#235, #442)
- Internal: completed the thin-wrapper refactor arc (#367) — CLI commands now parse-call-format over orchestration repatriated into @squadrant/core behind a DI seam (group, launch, side, telegram, crew). No user-facing behavior change. (#432–#439)
- Internal: repatriated core test suite out of cli (S1, #427); relocated generated codex protocol mirror to packages/agents/vendor/ (S5, #429); disambiguated daemon/delivery twin namings (S4, #428, #441).
- Guided onboarding.
squadrant initis now a re-run-safe, TTY-safe 5-step wizard (hub vault, agent + projection setup, plugin guidance, first-project registration, optional Telegram) that prints the exact next command at each step; non-interactive runs print the checklist and exit without blocking. (#424) squadrant doctornow prints an inline remediation hint under each FAILING check (e.g. missing config → runsquadrant init; daemon unreachable → runsquadrant heal daemon; missing plugin → the install command). (#424)
- Self-heal stale pre-rebrand
cockpitreferences in per-project Claude settings. The claude-cockpit → Squadrant rebrand leftcockpit crew _hookcommands in projects'.claude/settingsfiles, firingcockpit: command not foundon every captain/crew turn-end across 11 projects.writePerCrewSettingsLocalnow rewrites these on every crew spawn, andmigrate-to-squadrant.shstep 4.6 now sweeps every registered project, so the error stops and cannot recur. (#422)
- Bumped the opencode compatibility manifest last-verified to
1.17.9(lifecycle-verified). (#424) - Documentation de-cockpit: renamed the monorepo architecture diagram to
2026-06-18-squadrant-monorepo-architecture.html(and the.viversion), and fixed stalecockpitnaming / config paths acrossarchitecture.html,CLAUDE.md,AGENTS.md, and several specs/plans. (#423, #425)
- Bumped the cmux compatibility manifest last-verified version to
0.64.17. The0.64.16→0.64.17release is iOS features + macOS-app-internal Swift/SPM refactors + CI; it changes none of the cmux CLI subcommands or socket contract squadrant depends on, sodoctorno longer warns when running against0.64.17.
- Channel commands now run in any Telegram project topic (#419). Slash commands like
/status,/crews, and/notifypreviously only worked in the supergroup's General command channel; they now run from any project topic too, via a sharedrunChannelCommandhelper that the General channel and project-topic handlers both delegate to.
- Tap-first Telegram commands (inline buttons).
/notify,/effort, and/crews//launch//mute//unmutenow reply with tappable button panels instead of needing typed arguments — pick from buttons, no syntax to remember. Button taps are gated on your user-id (remoteControl) like commands, applied via the existing state writers / curated command runner, and the panel re-renders to mark the new state. Typed forms (/notify cap on,/crews <project>,/effort <mode>) still work for power users. - Guided
/spawnover Telegram./spawnnow replies with a project picker; tap a project and the bot asks (ForceReply) for the task — your reply spawns the crew. No typed arguments needed. (Completes the tap-first command UX.)
- An incomplete
/notifyin a Telegram project topic now replies with a tap-first button panel instead of being sent to the captain. A bare/notify(or/notify@<botname>, or a dimension with no value like/notify cap) previously fell through and was appended as a captain message. It is now recognized as a/notifyattempt: fail-closed behind remote control, then either applied (when typed in full, e.g./notify cap on) or answered with the notification panel (Captain on/off · crew tier · mute/unmute). Ordinary messages and/mute//unmuteare unchanged. - Telegram commands tapped from the
/menu in groups now work correctly. Telegram appends@<botname>to menu-tapped commands (e.g./status@squadrant_bot). The three command parsers (parseCommand,parseNotifyPref,notifyToggle) now strip this suffix from the first token before matching, so menu-tapped commands are recognized identically to manually typed bare commands.
squadrant telegram setupauto-captures your Telegram user-id. The running daemon now passively records the sender id from every allowlisted inbound message intotelegram-state.json(lastUserId). On a re-run ofsetupin reuse mode, if the daemon has seen a message from you, setup auto-offers "Enable remote control for your user-id<id>?" without requiring--user-idor a conflictinggetUpdatesdetection poll. Precedence:--user-idflag >getUpdates-detected (first-run only) >lastUserIdfrom state.squadrant telegram setupis now re-run-safe. Re-running setup with an existing supergroup configured skipsgetUpdatesentirely — avoids the 60 s timeout caused by the daemon's poll consuming the single-consumer long-poll channel. New--redetectflag forces fresh group detection; new--user-id <id>flag lets you enable remote control on a re-run without touchinggetUpdates. Allowlist precedence:--user-id> detected userId (first-run only) > existingcfg.users(preserved).- Daemon auto-restarts when you change daemon-cached config.
squadrant telegram setup,squadrant config set <telegram.*|defaults.taskTimeoutMs|defaults.cmuxEventsBridge|projects.*>, and project registration now restart the daemon so the change takes effect immediately (was: silently stale until a manualsquadrant heal daemon). Use--no-restartto opt out. Interactive crews + tasks + Telegram state recover automatically via the disk store + boot reconcile. - Telegram
/commandmenu registration.squadrant telegram setupnow registers the bot's command menu automatically, andsquadrant telegram register-commands(re)registers it on demand — so/status,/notify,/mute, etc. appear in Telegram's/autocomplete. Setup also reuses an existing bot token on re-run (use--reset-tokento rotate it), reports existing project topics so you can see what's already linked, and never recreates topics that already exist in state. squadrant:telegramskill documenting setup, remote control, command registration, and notification tuning.- Telegram mute confirmations. Turning a project quieter via
squadrant telegram notify <p> off|cap off|crew <lower>now posts a one-time confirmation into that project's topic (bypassing the mute), so you can tell on Telegram that it went silent rather than guessing. - Per-project layered config. A new override layer at
~/.config/squadrant/projects/<name>.jsonresolves as built-in → globalconfig.json→ per-project, merged per key (resolveNotify/loadProjectOverride/saveProjectOverridein@squadrant/shared). Fully additive: an absent project file behaves exactly as the global defaults — no migration. The resolver is generic; Telegram notification is its first tenant (per-projecteffort/modelskeys are reserved, not yet wired). - Telegram notification tiers (per-project). Outbound lifecycle pushes are filtered by a per-project crew tier —
none⊂done_only(task.done/task.failed) ⊂alert_only(+ blocked/approval/input/timeout, the default) ⊂all. New CLIsquadrant telegram notify <project> crew <tier>/cap <on|off>and Telegram/notify crew <tier>//notify cap <on|off>(fail-closed behind remote control) write the per-project config file. The liveactivemute axis (/mute//unmute/notify <project> on|off) is unchanged and stays intelegram-state.json; the live value overrides the config-defaultactive. - Distinct Telegram formatting for
task.failed(CREW FAILED + error),task.approval.requested(APPROVAL NEEDED + question),task.input.requested(INPUT NEEDED + question), andtask.timeout(CREW TIMEOUT) — previously these fell to the generic line. capgate onsquadrant telegram send— with a project's resolvedcap=off, explicit captain messages are suppressed (not sent), independent of idle-mute.
- A config-write restart can orphan in-flight headless crews (interactive crews recover fine) — see #410.
- Telegram notifications are now per-project and muted by default. Lifecycle events (crew done/blocked/idle) are delivered to a project's topic only after you engage that project — by sending any message into its topic, by
/unmute(Telegram, requires remoteControl), or bysquadrant telegram notify <project> on. This changes prior behavior where every project pushed all lifecycle events. Mute again with/mute <project>orsquadrant telegram notify <project> off. Command replies and the General command channel are unaffected.
The Telegram stability slice — closes two usability gaps so the integration is solid enough to release, both gated behind a fail-closed user-id allowlist + an opt-in master switch. Default behavior is unchanged on upgrade (remoteControl defaults to false).
- Project-topic auto-launch (#403). When a project-topic message arrives and no captain is alive, the daemon boots one (async
execFile, bounded warmup poll, per-project debounce) then delivers the message — instead of silently queuing. Acts only with remote control enabled. - General command channel (#402). Slash commands in the supergroup's General topic run a curated registry of squadrant operations from the phone:
/help,/status,/projects,/crews,/launch,/effort,/config get|set,/spawn. Each maps to a validated CLI argv run via asyncexecFile(no shell passthrough); unknown/freeform input gets a/helphint. - User-id allowlist +
remoteControlopt-in (#321).TelegramConfiggainsusers?: number[]andremoteControl?: boolean. Control surfaces act only whenremoteControl === trueandmessage.from.id ∈ users[]— fail-closed; chat membership alone is never enough. squadrant config get/config set— read/write a config value by dotted key.config setover Telegram is restricted to a default-deny writable-key allowlist (currentlydefaults.effort); secrets can never be written remotely.telegram setupenhancement — the wizard now captures your Telegram user-id and offers to enable remote control, writingusers+remoteControlidempotently.
/config setover Telegram rejectstelegram.botToken,telegram.users,telegram.chats, andtelegram.supergroupId(default-deny allowlist). Inbound handlers never let an error escape the poll loop, preserving at-least-once offset semantics.
A patch release adding the agent self-reporting feedback loop and fixing a stale version in the feedback command.
- Agent self-reporting prompt block —
AGENTS.mdgains a "Reporting squadrant bugs" section that instructs crew agents to route detected squadrant defects up to the captain, who can then file a GitHub issue. Enables a semi-automatic defect feedback loop without requiring CLI tooling. CONTRIBUTING.md— new root-level contributor guide covering the development setup, the agent self-reporting convention, and how to file issues.
squadrant feedbacknow reports the real version.packages/cli/src/commands/feedback.tswas hardcoding"0.1.0"as the squadrant version in submitted feedback; it now reads the actual version from the package at runtime.
A patch release fixing four issues that surfaced during the v0.9.0 claude-cockpit → squadrant live cutover.
- Migration build no longer aborts on stale workspace links.
scripts/migrate-to-squadrant.shstep 6 now runspnpm installbeforepnpm build. After the repo folder is renamed, pnpm's workspace symlinks still point at the old@cockpit/*package dirs, so building first failed with hundreds of unresolved-import errors mid-cutover; reinstalling regenerates the@squadrant/*links first. - Memory remap no longer false-alarms "DATA LOSS".
scripts/remap-claude-mem.shnow flags data loss only when the observation count decreases (-lt), not on any change (-ne). A live claude-mem observer can legitimately insert new rows mid-remap (the script never deletes), which previously tripped a spuriousFATAL ... DATA LOSSand aborted. - Captain/crew Stop hooks no longer invoke the removed
cockpitbinary.scripts/migrate-to-squadrant.shgained a step that rewrites stalecockpit crew _hookcommands tosquadrant crew _hookin existing Claude Code settings files (repo-level and global). The hook-generation source already emitted the new command, but settings files written before the rebrand kept failing withcockpit: command not foundon every Stop/PostToolUse hook. - Stray smoke-test file no longer ships in the npm tarball. Deleted
scripts/notify-relay-placement-smoke.mjs, which leaked into the v0.9.0 package becausepackage.jsonfilesincludes the wholescripts/directory.
- Rebrand:
claude-cockpit→ Squadrant. The project grew from a Claude-Code-only tool into a multi-agent orchestration layer (Claude, Codex, opencode, Gemini), so theclaude-brand was retired. Every brand surface is renamed:- npm package
claude-cockpit→squadrant; CLI commandcockpit→squadrant(+ aliassquad). - Internal packages
@cockpit/*→@squadrant/*; daemon bundledist/cockpitd.js→dist/squadrantd.js. - Runtime config dir
~/.config/cockpit→~/.config/squadrant; launchd labelcom.cockpit.daemon→com.squadrant.daemon; hub vault~/cockpit-hub→~/squadrant-hub; skill namespacecockpit:*→squadrant:*; crew env varsCOCKPIT_*→SQUADRANT_*.
- npm package
- A one-time migration script (
scripts/migrate-to-squadrant.sh, idempotent, with--dry-run+ automatic backup) performs the live cutover: moves the config dir and hub vault and rewritesconfig.jsonto the new paths/labels.
cockpit effortno longer self-notifies the captain that ran the command — the active-notify loop now skips the project whose path matches the current working directory (realpath-canonical, with a stale-path fallback). (#383)- Zombie task resurrection — the daemon sweep's timeout branch no longer falls through to clobber a terminal (cancelled) task back to
working; terminal states are now sticky, ending the repeated CREW TIMEOUT + name-collision mis-tag. (#380, #378)
- Explicit
stoppedproject status — closing a captain workspace now reaps its orphaned interactive crews exactly once (on a confirmed captain-gone K-streak) and the dashboard renders a calmstoppedstate (magenta/⏻) instead of a red CRITICAL fault. A genuine fault (corrupt store, unexpected surface-gone) still rolls up togone. (#388, #324, #323) - Debug-gated send instrumentation (
COCKPIT_DEBUG_SEND) — captures a pre/post input-box read-back frame around each captain delivery to catch the intermittent #339 Enter-inserts-newline glitch in the wild. Read-only (never re-sends), strict no-op when the flag is unset. (#386, #339) - Global effort dial (
cockpit effort <max|balance|low>) — one tokenomics lever the captain honors when spawning crews:maxbiases toward the strongest model,lowtoward cheaper agents/models,balancekeeps default routing. Captain-discretion, not a mechanical routing rewrite. (#381, #317)
- Control-plane store hygiene — automatic garbage-collection of stale terminal task records on sweep, a
crewTaghelper to disambiguate crew notifications, and a manual purge command with force override. (#382, #378)
- Runtime smoke step — CI now executes the bundled bins (
node dist/index.js --help,crew --help,cockpitd --help) after build, catching NodeNext ESM.js-extension crashes that tsc + vitest miss. (#384, #344)
A post-reorg cleanup patch. The public CLI surface is unchanged.
- notify-relay fully deleted — daemon-direct cmux delivery is now unconditional (the relay proxy hop is gone). Captain-gone detection moved to projectHealth (stoppedProjects/captainMissingStreak). (#332, #373)
- Semantic crew heartbeat — the watchdog now distinguishes three states instead of one overloaded idle pulse: CREW IDLE (real turn-end, Stop hook only), CREW QUIET (alive but deep-thinking; stays
working, no false 'awaiting-input'), and CREW STALLED (a tool call in flight past TOOL_STALL_BUDGET_MS=10min — a recoverable 'possibly hung' warn that auto-clears on the tool's PostToolUse). Degrades to QUIET-only for opencode/codex. (#354, #375)
- Thin-wrapper refactor —
launch.ts(446→210) andcrew.ts(804→510) now push orchestration logic into @cockpit/core / @cockpit/agents / @cockpit/workspaces (session-freshness, buildAgentCmd, crew-protocol incl. the #278 completion-protocol with an exact-string snapshot guard, crew-lifecycle reap, cmux-readiness, pane helpers), each unit-testable without spawning processes. (#367, #374, #376)
- Refreshed docs/testing/crew-lifecycle-checklist.md for the relay deletion + the new CREW QUIET/STALLED model. (#377)
An architecture release. Cockpit's flat src/ is now an internal six-package workspace monorepo — shared · core · agents · workspaces · web · cli — behind a one-way dependency DAG enforced by TypeScript project references, bundled by tsup into the same single dist/index.js (CLI) + dist/cockpitd.js (daemon). The public CLI surface is unchanged. This release also lands daemon-direct cmux delivery (the notify-relay is off the hot path) and a cluster of daemon/lifecycle bug fixes that surfaced during the cutover.
-
Monorepo reorganization (internal, no user-facing change). The flat
src/tree and three top-level dirs became six private workspace packages, each with a single responsibility, wired by aclicomposition root:@cockpit/shared(config schema, types, leaf lib),@cockpit/core(daemon, state-machine, protocol, and theAgentDriver/driver-seam interfaces),@cockpit/agents(the AI-driver seam — claude/codex/opencode/gemini),@cockpit/workspaces(the environment seam — cmux runtime, obsidian workspace, cmux notifier),@cockpit/web(the observability dashboard), and@cockpit/cli(commands, bin entry, daemon host). TS project references enforce the one-way DAG (shared ◄ core ◄ {agents, workspaces, web} ◄ cli) socorecan never import a concrete driver or the CLI; adding a new surface or agent is a new folder plus one wiring line. tsup inlines all five library packages into the same two bundled outputs, so the launchd daemon entrypoint andcockpitbin are unchanged. (#352, #355, #356, #357, #358, #361, #366, #368) -
Daemon-direct cmux delivery (
daemonDirectCmux). The daemon now delivers crew lifecycle events straight through the cmux runtime/notifier instead of via a separate notify-relay tab, removing a process-lineage wall and a class of relay-tab-death blind spots. (#332, #342, #345, #346, #347, #348, #351)
-
Daemon socket hijack (#360).
cockpitddid an unlink-then-bind on the shared socket with no liveness guard, so a second invocation (including a stray CLI) could orphan a live daemon — state reads survived but new connections failed. It now connect-probes any existing socket and refuses to start if a live daemon answers, and short-circuits--help/--versionwithout booting. (#362) -
Crew-tasks control-plane timeouts = event-loop starvation (#2). A synchronous
execFileSyncin the cmux driver blocked the daemon's hot path, causingcrew tasks/signal/spawnto time out whilestatuskept working — and drove constant daemon PID churn. Converted to asyncexecFile; the churn is gone. (#365) -
Config read ENOENT in the bundled CLI (#363).
package.jsonpath resolution overshot one directory in the tsup bundle (cockpit config/--versionfailed); corrected to resolve relative to the bundleddist/. (#365) -
Codex app-server orphans on daemon stop (#3). The codex interactive driver now stops its app-server cleanly when the daemon stops, instead of leaving reaped-to-daemon orphans. (#365)
-
Hardcoded crew-worktree base branch (#359). Crew/side worktrees now derive their base from
git symbolic-ref refs/remotes/origin/HEADinstead of a hardcodeddevelop, so main-based repos work. (#362) -
Daemon-direct cutover hardening. Re-entrancy guard on the delivery+probe loops (#347), three delivery-storm bugs — cursor corrupt-guard,
writeCursorrace, stale-skip (#346) — production construction ofDaemonCmuxwhen the flag is on (#345), missing ESM.jsimport extensions causingERR_MODULE_NOT_FOUNDat runtime (#343), and alaunchdouble-run / startup-send confirmation fix under cmux 0.64.16 (#340).
/where-i-am(/wim) orientation skill. A quick project-status report for re-orienting at the start of a session. (#364)
- Post-reorg documentation refresh: README,
CLAUDE.md, andAGENTS.mdnow describe the six-package layout; a new current architecture diagram (docs/diagrams/2026-06-18-cockpit-monorepo-architecture.html) replaces the pre-reorg overview; and adocs/README.mdmaster index was added. Shipped/superseded specs, plans, diagrams, and research were archived (bundled to the hub vault) so the tree carries only active docs — nothing deleted. (#349, #369, #370)
A compatibility release aligning cockpit with cmux 0.64.16, headlined by a fix for cockpit launch --fresh (broken by cmux's new pinned-workspace protection) and the elimination of cmux's deprecation noise. Introduces an external-tool compatibility manifest so dependency drift is caught early, plus first steps toward driver-agnostic crew-lifecycle detection via cmux's native event stream.
-
External-tool compatibility manifest +
doctordrift check. Newsrc/lib/compat-manifest.tspins the supported version of every external component cockpit depends on —cmux(min 0.64.0, last-verified 0.64.16),claude(min 2.1.32),node(min 18, last-verified 24.6.0), and presence-checkedcodex0.139.0 /gemini0.38.2 /opencode1.17.4.cockpit doctornow warns (non-blocking) when an installed tool is below its floor or newer than the last-verified version, surfacing a future breaking update early instead of letting it fail silently. (#325) -
cmux native event stream for crew-idle detection (B1). The daemon consumes cmux's
agent.hook.Stopevents as an additional crew-idle signal, keeping the screen-scrape as fallback. (#328) -
Agent-hook working-state to suppress false stalls (B4/A3).
agent.hook.PreToolUse/UserPromptSubmitderive a "working" state so a crew mid-tool-call is no longer misreported as stalled (the #292 class). Additive and gated; the delicate draft scraper is untouched. (#331)
-
cockpit launch --freshworks again on pinned workspaces. cmux 0.64.16 refuses to close a pinned workspace; the driver'sstop()now unpins before closing, so--freshreplaces the captain workspace instead of leaving a stale duplicate. (#325) -
cmux deprecation noise eliminated. Migrated the driver to cmux's canonical noun-verb commands (
workspace list/create/rename/close) and setCMUX_QUIET=1in the cmux subprocess env, removing the per-call "legacy alias" notices. Read commands also lock--id-format refsto stay robust against a future default change. (#325, #327) -
Focus-neutral crew spawn (A1/B3). cmux's new freeform-canvas layout broke the index-based focus-restore dance; the driver now passes
--focus false(cmux's new default) and drops the dance entirely, preventing keystroke leakage into a crew's launch line. (#327) -
--jsonparsing forworkspace list/tree. Replaces brittle regex parsing of cmux text output with structured JSON. (#327) -
Relay-health log noise pruned. The daemon no longer floods
not_found: Workspace not foundevery sweep on stale closed-crew refs — stale records are pruned and logged once. (#329)
- Agent Hibernation evaluated, gated off. cmux's agent-hibernation is global-only and would hibernate the captain/relay, so it ships behind
defaults.cmuxAgentHibernation(defaultfalse) with documented rationale rather than enabled. (#329)
- cmux 0.62→0.64 compatibility audit, the agent-lifecycle + daemon-architecture research dossier, and a workspace-groups (audit C1) deferral note — backing follow-up issues #326 (compat backlog), #332 (deprecate relay → daemon-direct cmux), #333 (driver-agnostic
LifecycleSource), and #114 (native codex TUI via hooks). (#327, #330, #334)
A patch release bundling the web observability dashboard and a startup-delivery fix — the work that accumulated on develop after 0.6.1, ahead of the cmux-compat changes that land in 0.7.0.
- Web observability dashboard. New
cockpit dashboard --web [--port] [--interval]serves a zero-dependency localhost HTTP+SSE dashboard. It assembles a degrade-never-blankFullSnapshot(Tier 0–4: daemon state, crews, mailbox stats, and external probes for cmux / agent CLIs / vaults / config behind injectable runners) and renders pure HTML/SSE with a severity rollup, a stale banner, and remediation text. Ships a light, WCAG-AA theme with an explanatory title on every widget, tabs, and zero-dep SVG donut/sparkline charts. Read-only beta. Closes #314, #319. (#314, #319)
-
Dashboard snapshot/probe accuracy. Corrected the hub-spoke vault health check, project-scoped probes, lag reporting, and severity classification; adds a
staleProbeStatefor template-drift caution. Data audited 68/68 fields accurate. Closes #320. (#320) -
Startup prompt delivered exactly once. The captain launch path now recognizes non-streaming working states (a shell-waiting spinner carries no token down-counter), so a working captain is no longer misread as idle and re-sent the startup prompt — eliminating the 3× duplicate startup runs. (#312, #292 follow-up)
A reliability patch addressing relay ghost-materialization, headless launcher I/O pressure, cross-project boot-if-down, and crew spawn focus leakage — plus a build fix and a CI gate.
-
Cross-project boot-if-down now works reliably. The target captain is brought to operational before warmup is judged; warmup timeout extended from 30 s → 120 s and exposed as
--warmup-timeoutflag. Closes #288. (#291) -
Headless launcher I/O pressure reduced. Task-progress writes are coalesced via a 250 ms / 50-chunk debounce with a final flush, stopping O(chunks) file writes. stdout/stderr capture is capped at a 4 MB tail to bound memory. Closes #88. (#293)
-
Relay no longer materializes Claude Code ghost-suggestions into drafts. A buffer-liveness probe replaces the destructive re-paste mechanism: the probe distinguishes a ghost auto-suggestion from a real draft without clearing the input, so a ghost can never be committed as a crew message. The ~5-min defer stall from the previous heuristic is also eliminated via an early stability-probe path. Closes #294 and #302. (#297, #303)
-
crew spawn(tab) no longer steals cmux focus. Captain focus is restored after the new-pane call, preventing keystroke leakage into the crew launch command. Closes #295. (#299) -
TypeScript build error in headless-launcher tests fixed. The
writeResultmock was typed incorrectly, breakingnpm run build. Closes #300. (#301)
-
crew spawndefaults to an isolated git worktree+branch. Parallel crews no longer collide on a shared working tree; opt out with--sharedfor small single-file tasks. Closes #296. (#298) -
CI
build-and-testis a required merge gate ondevelop. Broken builds and test failures are now caught at PR time rather than after merge.
A coordination-layer release headlined by leveled crew routing and the side-sessions framework, plus two crew-lifecycle reliability fixes — crews now signal DONE on their own, and --worktree crews are genuinely isolated. Also ships experimental cross-project intra-group delegation.
-
Leveled crew routing. Captains now pick a crew's agent + model by task tier via a configurable
defaults.crewRouting.rules[]ruleset (JSON, keyword → tier →{agent, model}, first-match-wins by array order).resolveCrewRouteis consulted at spawn; an explicit--agent/--modelalways overrides. Newcockpit:add-pick-crew-ruleskill edits the ruleset. Default tiers: extreme→claude/opus, hard→claude/sonnet, mobile→codex, daily→opencode. Closes #275. (#276) -
Side-sessions framework. New
cockpit side spawn|send|list|close --role research|debugopens a dedicated fresh-context tab on the captain model (opus), deliberately off the crew/daemon lifecycle — no task record, noCREW IDLE/DONEnoise to the primary captain.researchdiscusses ideas and produces artifacts (issue/spec/plan) with no edits;debugdoes systematic-debugging in an isolated scratch worktree (instrument + failing test, never ships) and hands a diagnosis + optional draft patch back. Report-back is offer-and-confirm: a structured handoff viacockpit runtime sendto the captain pane plus a durable{spokeVault}/side-handoffs/<topic>.mdrecord. Newcockpit:side-sessionskill. Closes #283. (#284, #285) -
Cross-project intra-group delegation (experimental).
cockpit group dispatch <to-project> "<task>"records a tracked task on a same-group sibling and wakes its captain via the existing mailbox/relay; the dispatcher yields and is notified when the task settles (done/blocked/failed). Validates same-group +acceptDelegations, attempts boot-if-down with a bounded warmup poll, and rejects loudly (task not recorded) on warmup failure. Experimental: boot-if-down of a down sibling does not yet reliably produce an operational target captain (#288) — works best when the target captain is already up. Closes #246. (#274) -
PR-time CI.
ci.ymlnow runs the build + full test suite on every pull request todevelopandmain, closing the gap where broken tests could reachdevelopsilently (tests previously ran only on push tomain). (#273)
-
Crew
DONEis now signalled reliably and unprompted. claude and opencode crews used to finish their work, report via text, and end the turn without ever runningcockpit crew signal done→ the watchdog parked the task atawaiting-input(CREW IDLE), so the captain never sawCREW DONE. The first turn sent to claude/opencode crews now carries a concrete completion-protocol suffix with--task-id/--projectbaked in (codex parity — robust to the keystroke-env race AND to model discretion). captain-ops also gained a "Handling CREW IDLE" reconciliation step (classify done-vs-waiting-vs-working on a single spot-check). Closes #278. (#281) -
--worktreecrews are now isolated. A--worktreecrew used to run git in the captain's MAIN checkout (the pane was created in the captain workspace cwd and the session nevercd'd into the worktree), dragging the captain's HEAD onto the crew branch. The claude/opencode launch nowcds into the spawn cwd first (no-op for non-worktree spawns). Closes #279. (#282) -
crewRouting config-migration backfill. Existing
~/.config/cockpit/config.jsonfiles written before routing existed never receiveddefaults.crewRouting, so leveled routing was a silent no-op.loadConfignow backfills the default ruleset when absent, persists it once, and prints a one-time upgrade notice. Closes #286. (#289) -
Relay draft-preservation third state.
parseDraftFromScreennow defers delivery on an overlay/unknown screen instead of misclassifying it, so a crew reply can't clobber an in-progress captain draft in that state. (#268) (#272) -
Daemon teardown flake + crew anti-polling guidance. The daemon test teardown now awaits async server close (eliminates an
ENOTEMPTYflake, #146); captain-ops gained an anti-polling guard so captains don't spin unboundeduntilloops reading crew screens (#241). (#277)
A stability release headlined by the RAM-flood fix — orphaned headless claude -p sessions no longer accumulate until the machine runs out of memory. Also adds per-spawn --model override, captain-draft preservation in the inbox, a captain-managed relay with live cockpit relay logs, PROTOCOL_VERSION framing, cockpit heal, and project-management skills.
-
--model <alias>flag for per-spawn crew model override.cockpit crew spawn --model <alias>overridesdefaults.roles.crew.modelfor a single spawn, taking precedence over runtime config — fixing model-drift when the stored config is stale. Closes #250. (#265) -
cockpit-register-projectandcockpit-new-projectskills. Two agent-usable skills so captains can register an existing repo (resolve path, derive name, pick group,cockpit projects add, verify) or stand up a brand-new GitHub repo (gh repo create→ clone → register) without hand-editingconfig.json. Both document the--group-roleauto-primary gotcha. Closes #262. (#263) -
Captain-managed relay supervisor. The captain now owns its notify-relay as a single
run_in_backgroundprocess running an in-process restart loop (3s backoff), replacing the separate✉ notify-relaycmux tab spawned bycockpit launchthat could die unnoticed. Closes #240. (#242) -
cockpit relay logs <project> [--follow]. On-demand live visibility into the captain-owned relay over a per-project unix socket — no persistent logfile (lines are broadcast to connected readers only and dropped when nobody is watching), and the relay core is untouched (wired via the existingopts.loginjection point). Closes #244. (#247) -
Relay logs a
deliverline on successful delivery. The relay's happy path now logs each crew signal flowing through, so a healthy relay no longer looks idle incockpit relay logs— previously it only logged on failure. (#244) (#248) -
Relay-as-cmux-proxy for crew-surface liveness. Surface-liveness probes now run inside the captain's cmux lineage (where the relay lives) and post results back to the daemon, instead of the launchd daemon calling
cmuxdirectly — which always returned empty"gone"verdicts and ghost-reaped live crews. The pre-result default stays"unknown", which never reaps. (#239 Phase B) (#257) -
Socket-boundary schema validation.
daemon.handle()validatesevent.typeagainst the fullControlEventunion before touching state and fast-errors on malformed frames; the event reducer gained an exhaustivedefaultso an unknown/future event type can never returnundefined. Closes #87. (#256) -
Wire
PROTOCOL_VERSIONand keepalive framing.src/control/protocol.tsnow exportsPROTOCOL_VERSION = 1. The client (sendRequest) stamps_von every outgoing request; the server stamps_von every reply. On a version mismatch the client rejects with a clear error (cockpitd protocol vN, this client expects vM — upgrade cockpitd or this CLI) instead of silently misparsing. An absent_v(pre-v1 daemon) is treated as compatible — no breakage on rolling restart. The bump policy is documented inline: bump for any wire-shape change.startServeralso emits a{"type":"_keepalive"}frame every 10 s on held-open attach connections (crew-attach stream and future subscribe channels), using an injectable clock so tests drive the timer without real delays.createDecoderanddecodeFramessilently discard keepalive frames at the shared decode layer, so no consumer ever sees them. Closes #92 and #94. -
cockpit heal <component>— targeted remediation surface. Three subcommands close the detect → notify → remediate loop for remote/unattended operation.heal status [--project P] [--json](dry-run: prints unhealthy components and the exact fix command;--jsonis machine-readable for skill/Telegram bridges; exit 0=healthy, 1=error, 2=unhealthy).heal relay <project>(re-establishes the notify-relay via the existingspawnInjectorprimitive; idempotent — no-op on alive/stale relay so it never competes with the captain's#240-owned supervisor).heal daemon(restarts cockpitd via the idempotent launchd kickstart path).cockpit heal crew <id>is explicitly deferred (overlaps #100). Closes #234. (#234) -
Hard crew task-timeout. The daemon sweep now detects non-terminal tasks that exceed a per-task wall-clock ceiling (default 8h, configurable via
defaults.taskTimeoutMs). When the ceiling is crossed the daemon fires a detect-onlyCREW TIMEOUTescalation to the captain via the existing mailbox → notify-relay pipe — the same pathCREW STALLED/CREW DONEride. No state change or kill (detection-first, per #77). Distinct from the heartbeat/stall budget, which only measures heartbeat freshness; a continuously-heartbeating crew stuck on one task for hours is now caught. Closes #225. (#225)
-
RAM-flood root-causes — the freeze that filled swap and forced restarts. Orphaned
claude -pheadless sessions were accumulating until the machine ran out of memory (seen at 13.5 GB swap on 24 GB, load avg 82). Three root causes fixed: (#259) the launchd-throttled (KeepAlive+ThrottleInterval=10)cockpitdcrash-loop that re-dispatched headless tasks on every boot — a straysrc/control/cockpitd.jslaunch path is guarded, socket-write failures no longer escape as fatals, and aninFlightHeadlessIdsguard stopsreconcile()from double-dispatching; (#260) the test suite shelling out to the realclaudeCLI whenstartCockpitdran without a mocked spawn — alaunchHeadlessinjection seam isolates tests; (#261) headless children orphaning to PPID 1 and surviving daemon death —activeHeadlessKillsreaps them onstop(). Verified end-to-end: a live daemon on the fix ran with a single boot, 0 crash signatures, and 0 leaked sessions. (#264) -
Captain's in-progress draft is preserved on relay delivery. Typing in the captain inbox while a crew reply arrived used to concatenate your draft into the delivered message and submit both. The relay now defers delivery while you have a real in-progress draft and delivers only when the input is empty (deliver-when-empty); a configurable walk-away fallback (
relay.maxDeferDeliveries, default300≈ 5 min at the ~1s poll) force-delivers a long-held draft via best-effort backspace clear-and-restore.parseDraftFromScreenis scoped to the live input box so transcript text never triggers a spurious defer or gets re-pasted. (#258 — #266, #267, #269) -
cockpit shutdownnow terminalizes crew task records before closing workspaces. Previously, closing a captain workspace left all its crew task records non-terminal in the daemon store (ghost records). On daemon restart the#225timeout sweep fired against every ghost simultaneously, flooding the captain withCREW TIMEOUTnotifications.cockpit shutdown [project]now sendstask.cancelled(reason:captain shutdown) for every non-terminal crew task before closing the workspace — the same terminalizationcockpit crew closealready performed. Daemon errors during terminalization are swallowed so a down daemon never blocks the workspace close. Closes ghost-source root cause of the#225timeout flood. (#225) -
Crew task-timeout now terminalizes the record (persistent dedup, flood-proof across restarts). The prior
#225implementation used an in-memoryfiredTimeoutSet that reset on daemon restart, re-firing everyCREW TIMEOUTnotification for all still-non-terminal tasks on every restart. The Set is removed; when a task exceeds the wall-clock ceiling the sweep now transitions it tocancelled(lastEvent: "sweep.task-timeout") viastore.putbefore firing the notification. The terminal state is the persistent dedup:TERMINAL_STATES.has(r.state)at the top of the sweep loop gates every future pass, including passes from a freshly-restarted daemon instance. The timeout message continues to report the task's original state (e.g.state: awaiting-input), notcancelled. Reverses the detect-only decision from#77; detect-only + volatile dedup was the flood bug. (#225) -
Running captains no longer show 'gone'. Captain liveness now derives from the relay heartbeat the daemon can see over the socket, instead of a cmux read the launchd daemon is always denied. Relay beating → captain alive; heartbeat gone → captain gone; no relay registered → unknown (no false alarm). (#239 Phase A)
A reliability and config-hygiene release. Adds a service-health layer and config-drift detection, and hardens the daemon against false crew-cancellation and hung cmux subprocesses.
- Service-health layer. Relay register / health-check / heal plus component liveness;
cockpit doctorandcockpit status --detailednow surface component health, and the daemon best-effort heals a downed relay while always surfacing it as actionable. (#226, closes #207, #77, #208) - Config drift detection.
config.jsoncarries a_cockpitVersionstamp and is checked against the current default schema after an update.cockpit config check --fixapplies the safe tier (missing/deprecated keys); theconfig-doctorskill reconciles judgment calls (changed defaults, invalid values). Closes the config.json half of cockpit's auto-update story. (#230)
- No more false-cancellation of live crews.
SessionEndterminalization is gated behind a surface-liveness probe, so a nested/subprocessSessionEnd(GSD, subagents, claude-mem) no longer cancels a live crew. Fixes a regression introduced in 0.5.2. (#229, closes #227) - A hung cmux can no longer wedge captain/relay. cmux subprocess calls now time out (15s) so a hung cmux fails fast instead of wedging the captain or relay. (#228, closes #209)
- reapCrewChildren works on busy machines. Raised the
ps auxEmaxBuffer so child-process reaping does not silently fail under load. (#222 — shipped in 0.5.2; its changelog entry was omitted at the time.)
A daemon-reliability and crew-safety patch release. The headline fix
terminalizes dead interactive crews so they stop re-emitting false
CREW STALLED alerts, plus per-crew worktree isolation and shell-injection-safe
crew dispatch.
- Per-crew git worktree isolation. Crews can now run in their own git
worktree via
--worktree, so concurrent crews no longer collide on a shared working tree / HEAD. (#216, #218) - opencode CP3 permission gate. Interactive opencode crews can surface
permission.askedto the captain (opt-in), closing the last notify-and-answer gap for opencode. (#215) - Injection-safe crew dispatch.
cockpit crew spawn/sendaccept--task-file/--message-fileto pass briefs and messages by file, bypassing shell metacharacter substitution and inline-brief truncation. (#177, #205)
- Dead interactive crews are now terminalized. A three-part fix stops
orphaned interactive crews (no live pane, no heartbeat) from oscillating
working ↔ stalledand firing falseCREW STALLEDalerts forever:SessionEndnow terminalizes a claude crew instead of resuming it toworking;crew closeterminalizes the daemon task even when the pane is already gone; and a surface-liveness backstop in the daemon's sweep/reconcile reaps crews whose surface is provably gone. Liveness-based, so the 24h interactive heartbeat budget (#131/#133) is preserved. (#139, #219) - notify-relay no longer silently drops events. The daemon↔relay formatter
is unified so the daemon is the single source of truth;
CREW IDLEandtask.approval.requestedevents reach the captain instead of being discarded on formatter drift. (#210, #214, #217) - codex first-turn race. The initial codex turn is no longer dropped
("no thread for task") — first-turn
say()is gated on the in-flight dispatch. (#212, #213) - Bounded crew read/tasks output.
cockpit crew read/tasksoutput is bounded to prevent truncated results and/compactchurn. (#206)
- Corrected the crew-lifecycle checklist methodology (two signal mechanisms, CP4 gap #210, 2026-06-03 findings). (#211)
This release closes the cross-agent crew-lifecycle parity goal: all three agents (claude, codex, opencode) now have a controlled lifecycle plus notify-and-answer on questions/permissions, each driven by a reliable signal source rather than screen-scraping.
- opencode SSE turn-end bridge. Interactive opencode crews now launch as
opencode --port <N>; the daemon opens a long-lived subscription to the crew's/eventstream and maps the documentedsession.idleevent to a turn-end, so it learns a crew is idle without the crew shelling out to cockpit. The daemon no longer sits atworkingforever. (#188) - codex crew sandbox parity. Codex crews now run with
sandbox: "danger-full-access", matching the already-unsandboxed claude/opencode crews, socockpit crew signal done|blocked|failedcan reach the daemon socket. The full codex signal lifecycle (question / done / reopen) now works end-to-end.approvalPolicyremains an independent axis, so the permission gate still fires under--approval. (#190) - Semi-automatic claude crews.
acceptEditspermission mode plus a permission allowlist let crews run on cheaper models while still gating risky operations. (#178) - Event-driven permission detection. The claude
Notificationhook surfaces a real permission prompt as CREW BLOCKED within ~0–3s; the in-cmux notify-relay also detects crews parked at a prompt. (#180, #181) - Trailing-question detection. A crew that ends a turn on a question is surfaced to the captain as CREW BLOCKED. (#174, #176)
- Crew lifecycle test checklist. A reusable 6-checkpoint regression harness
to run on any crew/daemon/relay/template change.
(
docs/testing/crew-lifecycle-checklist.md)
- codex approval round-trip.
answer()maps approve/deny to the codex app-server schema (oldapproved/denied, v2accept/decline), so captain approvals are accepted instead of silently rejected. - Turn-end no longer clobbers a blocked crew.
task.turn.completedfromblockedis now a no-op, so an app-server/SSE trailing turn-end can't drop the question asignal blockedjust raised. - Silent mid-turn re-block.
crew sendto a blocked/awaiting crew re-arms the daemon so a subsequent permission prompt re-fires CREW BLOCKED. (#183) crew closeterminalizes the task via a silentcancelledstate, ending phantom CREW BLOCKED/IDLE pushes after a captain-initiated close. (#184)- Exactly-once first-turn delivery plus an opencode boot-race readiness gate. (#175)
- CREW IDLE notifications reverted. The idle-ping feature (#182/#185/#185b)
was removed: it depended on the claude
Stophook, which fires unreliably in the claude-mem/cmux environment (a probe sat atworkingfor 216s+ with the hook never firing). Reliable idle/turn-end detection now comes per-agent from real protocol events — opencode SSEsession.idleand codex app-serverTurnCompleted— instead of a flaky hook.
- Control-plane daemon (cockpitd). A new background daemon provides an
AF_UNIX socket server with newline-JSON framing, a task state machine, atomic
per-task JSON state store, heartbeat watchdog with stall detection and
automatic recovery, startup crash-reconciliation, and self-healing daemon
management on every
cockpitinvocation. A launchd plist target is included for macOS service integration. (PR #85 and the full control-plane series) - Codex interactive crews. An
AppServerClientspeaking the codex app-server v2 protocol (mandatory handshake, thread start/resume/read, id-correlated requests, notification fanout), aCodexInteractiveDriverowning the app-server child process, an approval/gate primitive, acockpit crew attachcmux-tab renderer, andcockpit crew chat --provider codex/--approval/reply --gateverbs. (#86 interactive slice, #96–#104) - Claude interactive crews routed through the daemon. Claude crew sessions now flow through cockpitd rather than bypassing it, unifying the session lifecycle under the daemon's state machine. (#108, #64 slice)
- opencode interactive crews wired through the daemon. opencode crews gain
a dedicated crew template, per-crew permission configuration, and
autoApprove/model passthrough, all served through the daemon. (#127, #128, #129) - Daemon push-notifications to the captain. Terminal task events are delivered to the captain via an in-cmux relay, keeping the captain informed without polling. (#109, #110, #111, #112)
- Mailbox + injector foundational refactor. A new mailbox abstraction and injector layer underpin the daemon's communication channels. (#113, #116)
- Dashboard status grid now reads live daemon task state. The dashboard no
longer depends on
status.md— it queries the daemon directly for current task state. (#154) - Self-contained architecture HTML report, with a Vietnamese translation. (#147, #149)
- Process-cleanup rule added to crew templates and the
captain-opsskill to ensure child processes are cleaned up on session exit. (#164) - Release automation. A GitHub Actions workflow tags
vX.Y.Zfrompackage.json, publishes a GitHub Release with notes from theCHANGELOGsection, and (when anNPM_TOKENsecret is set) publishes to npm — on every push tomain. (#170)
- Crew sessions use an identity-first generic template with a no-nested-subagents rule, replacing agent-specific templates. (#105, #106)
- Source-managed directories self-heal on every cockpit invocation. Missing directories under source control are re-created automatically. (#74)
- Plugin manifest registers the cockpit skill namespace. Dead
plugin/package.jsonremoved. (#72, #73)
- Multi-line crew prompts no longer fragment. Newlines are collapsed before the cmux send, preventing truncated prompts. (#136, #166)
- First-turn crew dispatch no longer drops on slow CLI boot. Fixed delays have been replaced with pane-readiness polling for reliable first-turn delivery. (#165, #167)
- False
CREW STALLEDalerts eliminated. TheStopmap now correctly resolves toawaiting-input, and the heartbeat refreshes mid-turn via aPostToolUsehook. (#124, #131, #133) - cmux shell-injection closed in
sendToPane/sendToSurfaceand the notify path. (#119, #122) - notify-relay now runs as a hidden background tab rather than a split pane, preventing accidental interference. (#117, #123, #161, #162)
- Daemon-bounce loop fixed by separating
PATHdrift detection from program-arg changes. (#126) - cmux stderr no longer leaks into the captain terminal. (#121, #125)
- Fresh-install gaps closed: cmux binary path resolution cascade (issues #1,
#144), launchd plist
PATHbaking (issues #5, #143), and a reconciled Node >=18 floor across README,cockpit doctor, andpackage.json(#142). - Cockpit hooks delivered via
.claude/settings.local.jsoninstead of--settings, aligning with Claude Code's recommended hook mechanism. (#134, #137) - codex
approvalPolicydefaults to'never'for unattended crews. (#132) task.reopenedsemantic fixed. Re-tasking a done crew now firesCREW DONEagain as expected. (#148, #150)- vitest scoped to
src/**/*.test.tsto avoid picking up non-source test files. (#157, #158) - Captain tab renamed and pinned so crew reports route to the correct surface. (#83, #84)
- Projection reads the canonical project source outside the
cwdsandbox. (#63) - Control-plane red-team hardening: path-traversal sanitization, fail-loud
interactive dispatch, and
PATHbaked into the launchd plist. - Captain is notified when a crew goes idle. An idle interactive crew now
transitions to
awaiting-inputand fires a single accurateCREW IDLEnotice instead of a misleadingCREW STALLED; the explicitsignal donepath still firesCREW DONE. (#172) - codex crews can report terminal state.
cockpit crew signalaccepts--task-id/--projectflags, and codex threads receive their concrete task id + project viadeveloperInstructions, so codex crews can signal done/blocked/failed like claude/opencode. (#173)
- Reactor engine. The always-on GitHub poller / auto-delegation engine has
been retired — reaction rules (
reactions.json), the polling and matching scripts, the auto-status poller and status classifier, thereactorrole and its skill, and thecockpit reactorcommand are all gone. Event-driven auto-delegation is no longer part of cockpit; agents are launched explicitly. - Aider runtime driver and support. The
aiderdriver, its tests, and all spawn/launch/doctor/template wiring have been removed. Aider was never wired intosrc/config.tsand saw no active use; cockpit's supported agents are now Claude Code, Codex, Gemini CLI, and opencode. The--agent aideroption no longer exists.
- opencode CLI agent support. New driver (
createOpencodeDriver) probesopencode --versionand declaresauto_approve / json_output / streaming / model_routingcapabilities.cockpit crew spawn ... --agent opencodebuildsopencode run "<prompt>"(plus--format jsonand-m <model>when applicable). The matching projection emitter writes to~/.config/opencode/AGENTS.mdat user scope and<root>/AGENTS.mdat project scope, sharing the same marker-merge flow as codex. opencode crews run as interactive sub-sessions like claude crews —cockpit crew senddelivers follow-up turns to the live TUI. Print-mode is still used for one-shot roles (reactor, exploration).
- Crew now honors configured model routing.
cockpit crew spawnwas not passing--modelto the agent CLI, so Claude crews silently fell back to the user's global default (typically opus) instead of the configureddefaults.roles.crew.model(sonnet by default). Read the model from config and pass it throughbuildCommand. Token spend for crew sessions drops accordingly. - Model passthrough is agent-aware: only applied when the spawn agent
matches the role's configured agent (
defaults.roles.crew.agent). Cross- agent crews (e.g.--agent codexwhile config routes crew to claude) skip the model arg, since model names are agent-specific (sonnetis a Claude alias and would be invalid for codex / aider / gemini).
Crew sessions become interactive sub-sessions instead of one-shot print
runs — the captain's equivalent of a Claude Agent Team subagent. Each crew is
named, addressable, stays idle between turns, and is driven by new
cockpit crew send/read/close/list verbs. Closes #56.
- Interactive Claude crews —
cockpit crew spawnboots Claude without-p, then sends the task as the first turn after the CLI is ready. The session stays alive between turns waiting for the captain's next message. - Named crews —
--name <n>(or auto-generatedcrew-1,crew-2, … picking the next free slot from existing tabs in the captain workspace). Tab title becomes🔧 <project>:<name>so the surface itself is the registry — no state file. cockpit crew send <project> <name> "<message>"— send a follow-up turn to an existing crew. Replaces the "spawn a new tab for every turn" pattern.cockpit crew read <project> <name>— read the crew's current screen from the CLI (no need to flip into the cmux UI).cockpit crew close <project> <name>— shutdown a crew (closes its tab).cockpit crew list <project>— list live crews for a project.SpawnOptions.interactiveflag — Claude driver omits-pwhen set so callers can deliver the prompt over runtime.send.RuntimeDriver.listSurfaces(workspaceId)— enumerate surfaces (tabs / panes) inside a workspace with their titles. Cmux driver parsescmux tree --workspace.
- Captain templates +
captain-opsSKILL rewritten — teach the new spawn-once / send-follow-ups / close-when-done pattern. Stops the "tons of tabs" growth seen pre-0.3.1. - README + CLI help updated with the new verbs.
- Non-Claude agents (codex / gemini / aider) still launch in print-mode; full interactive support per agent is a follow-up.
- Crew tabs do not persist across
cockpit shutdown <project>— they're surfaces inside the captain workspace and die with it. Matches Agent Team semantics.
The thin-redirect release. Cockpit becomes a thin multi-agent orchestration layer where the captain is disposable, crew are fresh CLI sessions in split panes (any agent), Command is on-demand, and an auto-poller derives liveness from cmux pane content so agents don't have to write status.
Umbrella tracking: #40 (closed). Design spec:
docs/specs/2026-05-05-cockpit-thin-redirect-design.md.
- Crew spawn via split-pane CLI —
cockpit crew spawn <project> <task> [--direction <d>] [--agent claude|codex|gemini|aider]opens a fresh agent CLI in a split pane next to the captain. Replaces Claude-onlyTeamCreate/Agenttool. Works for any agent (#41, #46). RuntimeDriverpane operations —newPane,closePane,sendToPane,readPaneScreenso callers reach panes via the existing abstraction (#41).- Auto-status poller — reactor reaction polls captain panes via
cockpit runtime read-screen, classifies state (idle/busy/blocked/errored/ offline) from the last ~50 lines, writes{spokeVault}/status.mdwith state + timestamp + last-activity excerpt. Pure machine, no agent action required (#43, #48). - Dashboard —
cockpit dashboard --paneopens a refreshing sidebar grid in cmux; hub Obsidian Dataview page aggregates all spokestatus.mdfiles. Both consume the same auto-derived data (#44, #49). cockpit command [--task briefing|learnings-review|wiki-aggregate]— on-demand one-shot Command session in a split pane, instead of an always-on persistent Command workspace (#42, #47).- Multi-agent template parity —
captain.generic.md/crew.generic.mdprojected to~/.codex/AGENTS.md,~/.gemini/GEMINI.md,.cursor/rules/cockpit.mdcso non-Claude agents have working captain/crew contracts (#45, #50).
- Captain templates and
captain-opsskill rewritten — no moreTeamCreate/Agent/SendMessagereferences; crew spawning routes throughcockpit crew spawn; mandatory write-status-after-every-event rule removed (the auto-poller covers liveness). captain.claude.md— added one-line compact-recovery doc note. Verified live: role survives/compactvia--append-system-prompt-file, so role-amnesia is not a real problem; only work-context loss remains and is covered by handoffs.launch --all— no longer auto-launches a Command session. Barecockpit launchno longer defaults to Command. Command is opt-in via the newcockpit commandsubcommand.- Vault discipline — handoff / wiki / learnings are now opt-in (captain writes when meaningful), not nagged on every event. Vault becomes a consumer of auto-derived status, not the primary write target.
scripts/spawn-crew-pane.shis now a thin shim that forwards tocockpit crew spawn(preserved for backward compat).
- "Captain MUST write status after every significant event" rule
- "Daily log" requirement (still possible, just opt-in)
- Auto-launched Command session in
--allflow - Claude-only
TeamCreate/Agenttool dependence in captain workflow
0.2.0 - 2026-05-05
First tagged release. Establishes cockpit as a multi-agent orchestration layer (Command → Captain → Crew + Reactor) with a pluggable slot architecture and GitHub-driven automation.
- Driver model for multi-agent support — Codex, Cursor, Gemini CLI, and Aider alongside Claude Code (#16).
- Multi-agent direction statement and Karpathy coding-discipline skill applied across captain/crew/direct edits (#32, #33).
- Projection slot V1 — cross-agent config sync so non-Claude agents see the same project context (#31, #36).
- Phase 1: Runtime slot — abstracts cmux behind a runtime driver (#20).
- Phase 2: Workspace slot — pluggable workspace provisioning (#26).
- Phase 3: Tracker slot — pluggable status/progress tracking (#28).
- Phase 4: Notifier slot — pluggable notification surfaces (#29).
- Reaction engine — declarative GitHub event polling with rule-based actions in a dedicated workspace (#1).
- CI Feedback Reactor — auto-fix CI failures via crew dispatch (#3).
cockpit retrocommand — weekly/sprint retrospective summaries from daily logs and git history (#6).
cockpit launchandcockpit shutdown— bootstrap and tear down the Command/Captain/Crew workspace set in cmux.cockpit standup— daily standup summary from captain logs.cockpit feedback— capture user feedback into the project record.- Daily briefing on new day; captain writes daily logs.
- Project groups — sibling repos share context via claude-mem; primary
repo auto-detected;
--group-roleenforced. - Auto-discovery of repos under a parent directory with primary/sibling identification.
- Auto-generated unique captain names with collision validation on
projects add. - Session continuity — resume last session by default;
--freshflag forces a new session; built onclaude -c. - Configurable permission modes for command and captain sessions (#21,
#22) — defaults to
auto. - Workspace icons — command, captain, crew — for cmux visual distinction.
- LLM Wiki knowledge compilation system — Karpathy-inspired ingest/query/log scripts per spoke vault (#13).
- GSD integration for crew wave-based execution on multi-step tasks (#14).
- Model routing config — Opus for command/captain/review, Sonnet for crew/reactor (#12).
- Task Master integration via session handoff files.
- Docs scaffolding for research, specs, and ADRs.
- Project roadmap covering 13 features across P0–P3.
- Cockpit roles default to
autopermission mode at launch (#21, #22). --append-system-prompt-fileused for roles to preserve project CLAUDE.md; templates deployed viacockpit init.- Captain writes status on session start and after every task event.
- Command session restricted to delegation-only tools (Bash/Read/Write); no Grep/Glob/Edit on project source.
- Switched from manual
git worktreeto Claude Code's built-in worktrees.
- Command-ops freshness gate — validates captain workspace age before reuse, preventing stale-session bugs (#37, #38).
- Exact captain-name matching enforced — never reuse similar workspaces.
- Use absolute cmux path everywhere; auto-launch the cmux app if not running.
- Detect external-terminal launches and bring up the cmux app.
- Use
workspace:Nrefs (not names) forcmux select-workspace. - Install CLAUDE.md into workspace cwd; navigate to command on launch.
- Brove project path corrected; warn on
projects addwhen no.gitfound. - Strengthened command CLAUDE.md hard rules against doing work directly.
- Correct plugin keys, captain naming, and status display.
- README with install, commands, and architecture.
- Multi-agent direction spec (
docs/specs/2026-04-24-multi-agent-direction.md). - P0 roadmap items marked complete; out-of-repo work moved out.