Skip to content

Release v0.19.0 - #719

Merged
tu11aa merged 83 commits into
mainfrom
release/v0.19.0
Aug 23, 2026
Merged

Release v0.19.0#719
tu11aa merged 83 commits into
mainfrom
release/v0.19.0

Conversation

@tu11aa

@tu11aa tu11aa commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Release v0.19.0 — cut from develop @ afa5a61.

82 commits since v0.18.1 (2026-08-13).

Headline

Verification

Known open issues (deliberately not blocking)

Verified via git that these do not regress this release:

Post-merge checklist

  1. release.yml's npm publish step is continue-on-error — a green workflow does not prove publish succeeded (this bit us at v0.13.1). Verify with curl https://registry.npmjs.org/squadrant rather than npm view (CDN lag).
  2. Back-merge maindevelop.

tu11aa and others added 30 commits August 13, 2026 23:15
Scope fixed to claude + opencode; both control APIs verified live.

- ControlChannel port beside the existing LifecycleSource pattern
- DeliveryOutcome union replaces confirmedSendToPane's boolean
- capability tiers (T0 send / T1 observe / T2 interact) rather than
  the intersection of the two agents
- liveness mapping incl. the origin:"agent" vs "scan" trap that would
  otherwise silently discard waitingFor:"permission prompt"
- shadow-mode rollout so the false-signal fix is measurable, not asserted
- four independently revertable delivery slices
Slice 1 (liveness): ClaudePeerRegistrySource + OpencodeSseBridge onto the
LifecycleSource port + stale comment fix. Read-only, no behaviour change.

Slice 2 (control channel): ControlChannel port with a five-branch
DeliveryOutcome + OpencodeHttpChannel over prompt_async + off-by-default
per-agent rollout flag.

Both are independent and can run as parallel crews.
feat(#667): Slice 2 — ControlChannel port + opencode delivery
feat(#667): slice 1 — native agent liveness
…uest

Legacy GET /permission + POST /session/{id}/permissions/{id} (deprecated
in the server's own OpenAPI doc) are what squadrant's merged code
actually uses and they work. The v2 pair this spec's open-unknowns
table named (GET /api/permission/request, POST
/api/session/{id}/permission/{reqId}/reply) exists and matches its
documented schema, but its backing SessionRunner errors with
ModelUnavailableError before it can ever reach a tool call in
opencode v1.18.18 (reproduced against two unrelated providers), so it
never sees a real pending permission. Recorded live transcripts for
pending/empty/dead-session/dead-request-id on both endpoint pairs.
…er message

Reconfirms the 2026-08-08 spike's hold trigger (from-mode="bypass"
against a --permission-mode auto receiver) on Claude Code 2.1.233, then
goes further and exercises both resolutions live for the first time:

- deny (default selection, no navigation): receipt status:"denied",
  no user turn added to the transcript.
- approve (arrow-down to "Deliver", then confirm): receipt
  status:"delivered", and the message lands as a real type:"user"
  transcript entry.

The confirm key is not a plain \r/\n/space/tab/digit — none of those
resolved the prompt despite arrow-key navigation visibly working. Only
the Kitty keyboard protocol CSI-u encoding (\x1b[13u) worked. Also
documents an environmental trap: spawning a throwaway claude session
via `nohup ... &` from inside another Claude Code session leaks
CLAUDE_CODE_* env vars into the child and silently breaks its session
registry entry; fixed by spawning with a sanitized env.

No human-in-the-loop blocker was needed — the earlier "never
exercised" unknown was closed entirely via automated pty driving.
docs(#667): close both open-unknowns via live smoke
Slice 3 (claude delivery): ClaudePeerChannel over the UDS session inbox,
a receipt listener so held/denied/delivered are observable, T1-confirms-T0
for the silent accept path, --messaging-socket-path at BOTH spawn sites
(crew via driver.buildCommand, captain via buildAgentCmd), and per-agent
channel selection replacing slice 2's single channel.

Slice 4 (chat): captain delivery through the channel behind a separate
defaults.captainChannel flag, honest ping outcomes, and Telegram receipts
for held/unreachable. Inbound gate hardening stays in #675.

Both plans argue from the approved spec and incorporate all three
open-unknowns now closed by live smoke (opencode queues mid-turn 204;
v2 permission endpoint unreachable so the legacy pair stays; claude
held-message approve/deny receipted, confirm key is \x1b[13u).
docs(#667): implementation plans for slices 3 and 4
…CLI channel construction

Completes Task 7: readClaudeStatusByCwd/status helper in registry.ts, agents
exports for the peer channel, ClaudePeerChannel construction in crew.ts,
messagingSocketPath on TaskRecord and at claude crew spawn.

NOTE: the crew that wrote this had also disabled the #670 monorepo-checkout
guard in squadrantd.ts (isMonorepoCheckout -> return false) so its worktree
build could start as a daemon. That change was uncommitted and has been
discarded — the guard is intact here. See #682.
tu11aa and others added 28 commits August 20, 2026 10:30
Part A (daemon self-heal): when roleFromTemplate() reads "unknown" from
the cmux store's launchCommand.arguments AND the session's pid is
confirmed alive, parseStoreRecords now falls back to the real argv from
the OS process table (ps -o command=) and reclassifies from that.
Injected as an optional dependency (isPidAlive/readArgv), same pattern
as the existing readFile seam, so store-fingerprint.ts stays pure and
unit-testable with no behavior change when the stored argv is intact.

Part B (escape hatch): `squadrant heal captain [project] --all`
resolves the live captain from the cmux store + process table, writes
the corrected sessionId/pid into the LivenessRegistry, then reloads the
daemon via write-then-`launchctl kickstart -k` (never bootout, which
loses the ~2s KeepAlive respawn race). Idempotent — a project whose
registry entry already matches the resolved captain is left untouched
— and never invents an entry for a dead/hibernated pid.
heal captain: the running daemon persists its in-memory registry map
every liveness tick, and can clobber our applyEntry() write in the
race window before `launchctl kickstart -k`'s kill actually lands —
silently reporting success on a heal that changed nothing on disk.
runHealCaptain now re-reads the on-disk entry after kickstart and
retries once (re-write + re-kickstart) if it didn't land; a project
still mismatched after the retry is reported as a real failure (exit
1, stderr) instead of a false "healed".

readArgvFromPid: cache successful reads per pid. Argv is immutable for
a live process, but a session that stays role:"unknown" even after the
OS-argv fallback (a legitimate side.research.* session) was re-shelling
out to `ps` on every liveness tick for as long as it exists. Only
successful reads are cached — a transient ps failure on a genuinely
alive pid is still retried next tick, never given up on permanently.
fix(#699): re-adopt a captain whose cmux argv was truncated
…pic fallback

Item A: `squadrant launch <project> --agent <x> --model <y>` — explicit CLI
flags now win over defaults.roles.captain.{agent,model}, mirroring the
explicit-flag-wins precedence crew spawn already uses (#275). Absent flags
fall back to config exactly as before.

Item B: ensureGlobalOpencodeConfig defaults opencode's global config to
model: "anthropic/claude-sonnet-4-5". An opencode/codex/gemini captain
launched with no explicit model silently falls through to that default and
resolves to an Anthropic model — the exact outage manual mode exists to
survive. `squadrant launch` now refuses to boot such a captain, naming the
actual resolved model. Crew spawns get the same detection but only warn
(non-blocking): a crew depending on Anthropic through a fallback agent is
less catastrophic and more often intentional than a captain doing the same.

Known adjacent gap (not fixed here, out of scope): buildAgentCmd's non-claude
branch still emits a one-shot `opencode run "..."`, so once a non-claude
captain clears this guard it answers once and exits (Phase 2.1 interactive
boot work, tracked separately).
…, role-scope comment still stale

Parking mid-fix per captain instruction. State:

DONE:
- isAnthropicModel now also matches bare "claude-*" model names (not just
  the "anthropic/" prefix), closing the gap where --model claude-sonnet-4-5
  sailed through the guard. Tests added and passing.
- Added anthropicRefusalMessage(role, workspaceName, agentName, model) as a
  role-agnostic pure helper, with a test pinning that it behaves identically
  for captain/command/side (no per-role branching). Exported from
  model-guard.ts but NOT YET wired into launch.ts.

NOT DONE:
- launch.ts still builds its own inline refusal message instead of calling
  anthropicRefusalMessage — needs to be swapped in.
- launch.ts's comment above the guard still says "Refuse (not warn) for the
  captain role", which misdescribes the guard's actual scope (it fires for
  whatever role launchOne is called with — today only "captain" in practice,
  since launch.ts only ever calls launchOne with role="captain", but the
  check itself has no role special-casing). Comment needs rewriting to state
  this deliberately.
- Full pnpm build / pnpm test NOT re-run since these last two edits — do
  that before considering this fix complete.
Fixes instruction-file drift across projected agent formats where the old
strict GitNexus wording was still present. Softens the language in AGENTS.md,
GEMINI.md, and .cursor/rules/squadrant.mdc to match CLAUDE.md.

The drift occurred because the `gitnexus analyze` CLI command embeds its own
static template (which still contains the strict wording) and overwrites the
GitNexus block in AGENTS.md. When `squadrant projection` subsequently runs,
it propagates this overwritten block to the other agent instruction files.
This will need to be fixed in the gitnexus package directly.
…n it

The guard's comment claimed the refusal was captain-only, but
isBlockedFallback takes no role argument — it can't special-case by
role. It fires for whatever role launchOne is called with; today
that's only "captain" because launch.ts's call sites all pass that
literally, and squadrant command/side spawn agents through separate
code paths that never touch this guard.

Extracts the inline check into resolveAnthropicRefusal (also swaps in
the already-existing anthropicRefusalMessage helper instead of
building the message inline) and adds a launch.test.ts case that pins
identical refusal behavior across captain/command/side, so a future
`if (role === "captain")` around the guard would fail a test instead
of silently narrowing scope.
docs: sync gitnexus guidance across agent formats
WIP: #627 item A+B — launch --agent/--model override + Anthropic-fallback guard
Removes all remaining references to GitNexus since the tool is no longer available on the machine.

Files modified:
- CLAUDE.md: removed GitNexus section.
- AGENTS.md: removed GitNexus section.
- GEMINI.md: removed GitNexus section.
- .cursor/rules/squadrant.mdc: removed GitNexus section.
- CHANGELOG.md: removed gitnexus note.
- docs/specs/2026-07-28-captain-context-budget.md: stripped references.
- docs/specs/2026-06-24-codebase-structure-refactor.md: stripped references.
- docs/specs/2026-07-29-persisted-work-tracking.md: replaced gitnexus checks with manual checks.
- docs/specs/2026-07-07-captain-liveness-redesign-plan.md: replaced gitnexus instructions.
- docs/specs/2026-07-07-captain-liveness-redesign.md: replaced gitnexus instructions.
- docs/architecture.html: stripped references.
- docs/architecture.vi.html: stripped references.
- docs/README.md: stripped references.
- docs/superpowers/plans/2026-06-23-telegram-v010-stability.md: replaced gitnexus instructions.
- scripts/gen-codex-types.sh: stripped references.
- packages/core/src/__tests__/delivery-loop.test.ts: changed dummy project name to testproj.
- packages/agents/src/codex/driver.ts: changed gitnexus to mcp in comments.
- packages/cli/src/lib/__tests__/canonical-source.test.ts: changed gitnexus to mcp in mock.

Files deleted:
- .claude/skills/gitnexus/ (6 skills)
- .gitnexusignore
- Restored CHANGELOG.md, docs/specs/, docs/superpowers/, and packages/agents/src/codex/driver.ts to their original historical state (preserving records of GitNexus usage and incidents).
- Removed the dangling 'and the code knowledge graph' clause from docs/architecture.html and .vi.html completely instead of leaving a vague fragment.
Removed the frozen <claude-mem-context> sections from AGENTS.md, GEMINI.md, and .cursor/rules/squadrant.mdc. This section was 3 months stale and is superseded by the fresh context injected at session start by claude-mem.
chore: completely remove GitNexus
…epo CLAUDE.md

CLAUDE.md was the last instruction file outside the projection system,
which is exactly why it drifted from AGENTS.md. Bring it in:

- packages/agents/src/projection/claude.ts: new emitter, same shape as
  codex.ts/gemini.ts (marker-guarded merge, user ~/.claude/CLAUDE.md +
  project <root>/CLAUDE.md, both shared/markdown). Wired into the
  registry (index.ts) and the doctor/projection CLI commands, matching
  how codex/gemini/opencode are registered there. Deliberately NOT
  wired into init.ts's user-level auto-emit-on-init flow — that code
  already has a comment explicitly scoping it to "non-Claude agents",
  a prior intentional decision this task didn't ask to reverse.
- AGENTS.md: restore the "(captain, crew, and direct edits)"
  parenthetical to the Coding Discipline section, matching CLAUDE.md's
  more accurate wording — it names who the rule binds.
- CLAUDE.md: regenerated from AGENTS.md via the new emitter, wrapped
  in squadrant markers. Previously had 3 of AGENTS.md's 6 sections;
  now has all 6, including Telegram (#65, with its security-gap
  warning) and Managed ~/.claude/settings.json (#615) that Claude
  Code — which only auto-loads CLAUDE.md — was previously missing
  entirely.

Verified via the built emitter directly (no daemon/launch involved):
a re-emit against the regenerated file is byte-identical (idempotent),
and content placed outside the markers survives a re-emit with changed
source content. claude.test.ts pins the same guarantee at the unit
level, mirroring codex.test.ts's marker-duplication regression test.
Bring CLAUDE.md into the projection system
…ctName (#706)

launch.ts's socket-path interpolation read the command-level `project`
positional instead of launchOne()'s `projectName` parameter. That
positional is only ever set on the single-project launch path — on
--all and interactive-parallel launches it's undefined, so every
captain collapsed onto the same
/tmp/cc-socks/squadrant-captain-undefined.sock. First bind wins; the
rest of the captains silently failed to boot.

Extracts captainSocketPath() (matching the shouldWireCaptainChannel /
resolveAnthropicRefusal pattern already in this file) so the mapping
from project name to socket path is unit-testable, and pins the fix
with a regression test.
…st call site (#706)

Follow-up to d63be10, per review feedback:

1. Deleted the duplicate captainSocketPath() this branch had added to
   launch.ts. Import the existing one from @squadrant/core instead
   (packages/core/src/captain-channel.ts), the same way
   captain-channel-factory.ts already does — a second local copy of
   the formula was itself a instance of the drift #706 is about, and
   it dropped core's project-name safety regex.

2. resolveCaptainSocketPath() now throws when the captain channel is
   enabled but no project name reached the call site, instead of
   silently falling back to `undefined` (no channel). A captain
   silently booting without its messaging channel is exactly the kind
   of quiet failure #667 exists to close; the launch for that one
   captain now fails loudly and is reported, while the rest of an
   --all batch continues.

3. Added launch-captain-socket.test.ts, which drives the real
   launchCommand `--all` action (not just the pure helper) and asserts
   the two captains it spawns get distinct project-derived socket
   paths. Reintroducing `${project}` at the buildAgentCmd call site
   fails this test even though a unit test of the helper alone
   wouldn't catch it — verified by temporarily reintroducing the bug
   and confirming this test fails, then restoring the fix.

Also added a parity test asserting resolveCaptainSocketPath's output
matches @squadrant/core's captainSocketPath exactly, so the two
formulas can't silently drift apart again.

Verified: `tsc -b --force` across all packages, `pnpm build`,
`node dist/index.js --help` (exit 0), and the full monorepo test
suite — 2728 passing (was 2723 before this branch; no tests removed).
fix: captain socket path collision on parallel/--all launches (#706)
Resolve the captain's own sessionId from the same registry entry
statusFor already reads (keyed by the launch-time socket path) and
wire it into ClaudePeerChannel's sessionIdFor, instead of hardcoding
undefined. Restores the pid-reuse guard and drops the "Another Claude
session sent a message" wrapper on captain-bound sends. Falls back to
omitting session_id (never throws) when the captain hasn't registered
yet.
squadrant never passed claude's `-n, --name` flag, so every session's
display name auto-derived from the cwd basename (e.g.
"squadrant-fix-706-67", "helpa-06") — indistinguishable via ListAgents.
A crew that could not tell its captain apart from an unrelated session
sent its PR report to the wrong one.

Adds `@squadrant/shared`'s captainSessionName/crewSessionName
(sanitized to the same safe-filename charset captainSocketPath()
already enforces) and wires them into both claude launch paths:
buildAgentCmd (captain/command, launch.ts) and the claude driver's
buildCommand (crew, crew-spawn.ts). Format: "squadrant-captain-<project>"
and "squadrant-crew-<project>-<name>".

`-n` is claude-only — added as an optional SpawnOptions field only the
claude driver reads; codex/gemini/opencode buildCommand implementations
were checked and ignore unknown fields safely, so they're unaffected.
Captain session identity: session_id wiring (#709) + self-describing -n names (#708)
…orever

A transient listen EACCES at daemon boot used to log once and permanently
degrade the daemon to pane delivery for its entire process lifetime, with
no retry — a plain restart bound the same socket fine minutes later.

- buildCaptainChannelWithRetry() retries buildCaptainChannel() with capped
  exponential backoff, forever, instead of giving up after one attempt.
- sharedReceiptListener() only caches its singleton AFTER a successful
  start(); previously it cached before start() resolved, so a failed bind
  poisoned the singleton and every later retry silently returned the
  never-started listener instead of re-attempting the bind.
- squadrantd.ts now calls the retrying wrapper at the #667 slice 4 call
  site instead of a one-shot .catch(). ping.ts's one-shot buildCaptainChannel()
  call is untouched — a short-lived CLI command should not retry forever.
Review nits: the default sleep()'s setTimeout must not hold the event loop
open on shutdown (unref); the squadrantd.ts call site's .then() needs a
.catch() so a throwing log call can't escape as an unhandled rejection
(the retry loop itself only stops by resolving).
fix(#712): captain-channel init retries instead of latching pane-only forever
Redraws the monorepo architecture diagram (new dated file, adds a
Control/Captain Channel slide) and brings docs/AGENTS.md up to date
with the #667 control/captain channel and #333 lifecycle-source work
that had shipped but was undocumented.

- docs/README.md: relink diagrams/specs, correct stale status entries
- AGENTS.md (+ CLAUDE.md/GEMINI.md/.cursor mirrors): new "Captain/
  Control Channel (#667)" section
- docs/reference.md: new "Lifecycle Sources" and "Control/Captain
  Channel (#667)" subsections
- docs/diagrams/2026-08-22-squadrant-architecture.html: new diagram,
  old 2026-06-18 one kept and marked superseded (not deleted)
- docs/architecture.html: banner pointing stale Control Plane section
  at reference.md as the living source
- 3 specs: added Status-update lines for shipped work, no deletions
shadow was described as a safe verification fallback that keeps
comparing against the old inference path. It isn't: the probe result
is logged and discarded (captain-channel.ts:67), shadow contributes
no liveness of its own, and dropping to shadow hands back the full
pane-defer machine (draft/ghost/modal/no-box deferral) — the path
behind the ~4h delivery jam on 2026-08-22 (#712). Reworded per
captain review to state on/shadow's actual behavior, and decoupled
the wrapper-text mention (#711, a sender-identity artifact, not an
inherent property of the channel).

Applied to AGENTS.md and mirrored to CLAUDE.md/GEMINI.md/.cursor
rules, plus reference.md and the architecture diagram's slide 6.
docs: refresh architecture diagram + fill #667 control-channel gap
@tu11aa
tu11aa merged commit 26cf48b into main Aug 23, 2026
1 check passed
@tu11aa
tu11aa deleted the release/v0.19.0 branch August 23, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant