Bridge seam: consolidated egress + file-protocol drain (replaces #435, #445) - #509
Open
gcko wants to merge 12 commits into
Open
Bridge seam: consolidated egress + file-protocol drain (replaces #435, #445)#509gcko wants to merge 12 commits into
gcko wants to merge 12 commits into
Conversation
Port internal/bridgeegress (events.jsonl + session-marker writer) intact from the harness-agnostic-fo-events branch: host normalization (canonicalEventName/canonicalPiEventName), worktree-stop canonicalBridgeRoot, first-write-wins markers, and the 2000->1000 events trim. Wire the hidden `spacedock bridge egress emit --host <host>` entrypoint in newBridgeCommand (observe-only, silent, no-op-safe). Add a contract-shape test asserting the emitted line carries the seam-contract §2.5 ts/event/session_id keys for a Claude SessionStart payload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
Port internal/bridgeingress/wake.go + wake_test.go intact from the harness-agnostic-fo-events branch: the codex-only guard, stale-lock TTL reclaim, pending-line grouping by resumable session, and the detached `codex exec resume` launch. wake.go carries the shared inbox/reply/heartbeat helpers (inboxCursor, loadReplies, replyKey, targetsFor, discoverHeartbeatSlugs, loadHeartbeatAnyAge, safeSessionID, normalizeHost, lineScanner, safeSlugPattern) the Stop-hook check will reuse. Rewrite wakePrompt to speak the direct _bridge/ file protocol (seam-contract §3): read inbox lines past the per-slug cursor, dedup against fo-replies.jsonl, append terminal acks, whole-file-replace the .inbox-cursor.<slug>, refresh the heartbeat — naming no dropped `spacedock bridge inbox` verb. Wire the hidden `spacedock bridge ingress wake --host codex` entrypoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
Carve internal/bridgeingress/check.go out of the harness-agnostic-fo-events branch's drain.go, taking ONLY the read-only Stop-hook decision path: stopPayload parse (incl. stop_hook_active anti-loop), resolveSessionSlugs (heartbeat + session-marker match), workflowForSession, readInboxFull (+ its fullInboxRecord type and routing() projection), addressedTo (target_set authoritative), the pending computation, HookDecision emission, validSlug, and absRootOr. Drain/Ack/ Commit and their exclusive helpers (appendReply, truncateReplies, lowestCommittedCursor, adoptCursor, stampHeartbeat, resolveSessionID, ...) are NOT ported — FO judgment is direct _bridge/ file writes now (scope 2). Check never called adoptCursor, so its absence is behavior-neutral. Rewrite drainReason to instruct the file protocol (read inbox lines past the per-slug cursor, act, append terminal acks to fo-replies.jsonl, whole-file- replace the cursor, refresh the heartbeat) and name no dropped verb; drop the now-unused host parameter. Adapt check_test.go to the file-protocol reason, add a no-dropped-verb assertion, and add the review-B2 case (a heartbeat missing its session_id resolves nothing -> no block). Wire the hidden `spacedock bridge inbox check` entrypoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: test <test@test.com>
Wire the harness lifecycle hooks that drive the seam entrypoints, so the egress producer and the synchronous Stop inbox-check actually fire. - scripts/spacedock-bridge-events.sh (async, observe-only) and scripts/spacedock-bridge-inbox-check.sh (synchronous Stop decision), both with $SPACEDOCK_BIN -> command -v spacedock -> silent exit 0 bin-resolution; 0755. - hooks/hooks.json: six Claude egress events (SessionStart, UserPromptSubmit, PostToolUse, Notification, Stop, SubagentStop) plus the synchronous inbox-check on Stop. Notification is kept per contract §2.5 (liveness ping, preserve semantics). - hooks/codex-hooks.json: six non-async sh -c one-liners; no Stop-block (codex delivery is external wake via `bridge ingress wake`). - .claude-plugin/plugin.json + .codex-plugin/plugin.json each gain the "hooks" key — without it the loader never reads the hook config and the seam is inert (review blocker B1). Versions left at main's current value. - .pi/extensions/spacedock.ts: re-apply the pi egress registration onto main's current extension (skill discovery + lifecycle egress emit). - Port the hook->file production smoke tests: codex_bridge_egress_hook_test.go (codex manifest/hooks shape + minimal fixture) and bridge_session_link_test.go (Claude adapter -> events.jsonl + session marker contract conformance). Signed-off-by: Jared Scott <jared.scott@variable.team>
Author the single seam mod as a direct-file producer per Bridge's docs/seam-contract.md, replacing the retired verb-based bridge-inbox mod. - mods/bridge-seam.md (canonical) + docs/dev/_mods/bridge-seam.md (identical dogfood copy). Non-standing frontmatter (name/description/ version, no `standing:`); `## Hook: startup` + `## Hook: idle` prose conventions before `## Agent Prompt` (parser requires Agent Prompt last). - Encodes the B2 load-bearing requirement: the heartbeat MUST carry session_id = the harness session id, with a per-host source table (claude $CLAUDE_CODE_SESSION_ID, codex $CODEX_THREAD_ID, pi its id). A missing/wrong id silently kills Claude Stop-check wake delivery. - Verbatim JSON shapes quoted from contract §2: heartbeat (§2.4), inbox line (§2.1), fo-replies ack with the kind/status validity table (§2.3), fo-initiate cards (§2.7), fo-alerts permission-request (§2.8). - Drain recipe per §3: cursor read -> physical-line route (target_set authoritative) -> dedup against terminal acks by in_reply_to_id -> act -> wc -l -> monotonic whole-file cursor replace. Cursor-safety note: never lower; over-advance silently and permanently skips intent (the check only sees pending below the cursor). - Per-member fleet clause (one heartbeat/cursor/ack set per slug); park obligations (open gate-review before parking, state:idle heartbeat); fo-feed noted optional (§5); events/marker noted hook-driven. - Allowlist mods/bridge-seam.md in the contractlint lifecycle-hook absence guard (TestNoUnexpectedModHookOrPRMergeIntroduced), alongside the canonical pr-merge mod. Signed-off-by: Jared Scott <jared.scott@variable.team>
Signed-off-by: Jared Scott <jared.scott@variable.team>
Re-authored against current main (the branches drifted 40 commits): the before-greet «bridge.boot-liveness» step + function in shared-core, the eager top-of-loop drain step in dispatch-core, the per-host ## Bridge seam sections (claude/codex/pi runtime), present-gate's direct fo-initiate gate emission + channel-boundary rule, the fleet operating contract (fo-fleet.md), the first-officer SKILL fleet clause, and the ensign running-badge note. All speak the direct file protocol per docs/seam-contract.md — no dropped verbs. Re-baselines two contractlint shrink ratchets (foFunctionReferenceBaselineBytes, preChangeSharedCoreBytes) and the «dispatch.next-action» ordered-marker sequence to their new floors: the seam is a real new capability adding prose across the FO boot/dispatch/gate/runtime surfaces, mirroring #435/#445's growth. Counted-surface growth was minimized by keeping the full drain protocol in the uncounted bridge-seam mod + deferred fo-bridge.md/fo-fleet.md. Signed-off-by: Jared Scott <jared.scott@variable.team>
…sure, mod frontmatter, session-id marker) Signed-off-by: Jared Scott <jared.scott@variable.team>
…-seam.md Signed-off-by: Jared Scott <jared.scott@variable.team>
Signed-off-by: Jared Scott <jared.scott@variable.team>
…gfood gate) - check.go/readInboxFull counts only newline-terminated lines (bufio.Reader), matching Bridge's fointents.go wc-l semantics — a torn trailing inbox write no longer over-counts pending and forces a spurious Stop-block. - replyKey is id-primary and loadReplies accepts id-only acks (what the seam mod writes) filtered to TERMINAL statuses — the dedup guard is now operative for the real ack shape, not dead for it. - Check resolves the _bridge/ root by walking to the nearest .git (gitRootOr), matching bridgeegress.canonicalBridgeRoot, so a subdir Stop-payload cwd still finds the fleet inbox. - Dogfood mod copy made byte-identical to canonical + a dedup gate (TestBridgeSeamDogfoodCopyMatchesCanonical) and both mod paths added to the dropped-verb absence gate, so one-path drift cannot slip through. Signed-off-by: Jared Scott <jared.scott@variable.team>
This was referenced Jul 14, 2026
…+ cursor) Three parallel fresh-eyes reviews (schema parity, prose fact-check, runtime behavioral) confirmed producer↔reader schema parity COMPLETE and end-to-end compatibility. Folded their findings: - docs/dev/bridge-seam.md: removed a FALSE "1 MiB per-line inbox-check cap" claim (check.go reads the inbox uncapped; the 1 MiB cap is the events.jsonl scan, and it contradicted the authoritative seam-contract.md). Corrected the Stop-hook slug resolution to note heartbeat AND markers (heartbeat effective). Added the durable-wake caveat (Stop-hook block is the interactive path; headless is daemon-managed via in-process resume, not the hook). Softened the fo-feed drop note (complete-verb + free text are absent, not reconstructed). - claude-first-officer-runtime.md: terse headless-wake caveat pointer. - egress.go timestampFor: only pass a payload timestamp through when it parses as RFC3339, else stamp our own — a non-RFC3339 Codex/Pi value would otherwise fail Bridge's time.Time unmarshal and silently drop the event line. - mods/bridge-seam.md drain step 5: advance the cursor to the highest line actually read in step 2 (not an independent wc -l recount), making over-advance past an unread intent structurally impossible; dogfood copy synced. Empirically confirmed the load-bearing B2 binding is sound: $CLAUDE_CODE_SESSION_ID is a real env var (spacedock already reads it in internal/dispatch/build.go), so the Claude Stop-hook slug resolution is not broken. Signed-off-by: Jared Scott <jared.scott@variable.team>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
One consolidated Bridge seam that replaces open PRs #435 and #445 (#445 is stacked on #435). It gives Bridge everything it consumes from the
_bridge/file seam, shrunk to the minimal Spacedock surface per the analysis in thebridgerepo (docs/plans/2026-07-14-spacedock-optionality-findings.md§B/§D) against the Bridge-owned schema contract (bridge/docs/seam-contract.md).Built fresh off
main(the two branches were 40 commits behind, with 9 conflicting prose files), so all prose was re-authored against currentmain, never rebased.The consolidation, in one line
Bridge consumes files, not verbs. The five packaged CLI verbs from #445 (
inbox drain|ack|commit,alert,initiate) are retired — the FO writes the seam files directly per thebridge-seammod and the published contract. What survives is only what genuinely needs the harness.What ships (the irreducible core)
spacedock bridgeentrypoints, all hook-/daemon-invoked, never FO-called:egress emit(normalize a turn payload →events.jsonl+ the session→entity marker),inbox check(the synchronous ClaudeStop-hook block-until-drained wake),ingress wake --host codex(external resume). Theinternal/bridgealertandinternal/bridgeinitiatepackages and the drain/ack/commit verbs are gone.mods/bridge-seam.md, + byte-identical dogfood copy) carrying the whole drain/heartbeat/ack/gate/alert file protocol — a bare LLM agent with only the contract can implement it.hooks/hooks.json+hooks/codex-hooks.json+ the pi extension), and the"hooks"key added to both plugin manifests.## Bridge seamsections, present-gate direct fo-initiate emission, fleet mode).Completeness — every Bridge reader has a producer
A fresh-eyes audit walked every reader in
bridge/internal/spacedock/*.goagainst a producer: inbox/cursor/replies, heartbeat, events, session marker, initiate cards, alerts — all satisfied at the right schema;fo-feed.jsonlis the one deliberate drop (redundant with git narration + the marker-derived feed, contract §5). Verdict: PASS.Load-bearing detail
The
fo.<slug>.jsonheartbeat must carry the harness session id ($CLAUDE_CODE_SESSION_ID/$CODEX_THREAD_ID); the Claude Stop-hook check resolves which slugs belong to a stopping session by matching the Stop payload'ssession_idagainst the heartbeats. A heartbeat without it → the check resolves nothing → a Claude FO is never nudged and queued intent sits forever. The mod requires it, a contractlint gate pins the markers, and an integration test exercises the block chain.Process
Plan-first with two adversarial gates. The plan review caught two blockers pre-code: the plugin
hookskey was undispositioned (the whole Claude seam would have been inert) and the prose-written heartbeat had to carry the harness session id or Claude delivery silently dies. The implementation review returned SHIP + completeness PASS with four MINORs — all folded in: torn-trailing-line count now matches Bridge'swc -l, the ack dedup is id-primary and terminal-status-filtered (matching the mod's real ack shape),inbox checkwalks to the git root like the egress producer, and the dogfood mod copy is gated byte-identical.Reviewer attention — contractlint ratchet re-baselines
Spacedock enforces monotonically-shrinking FO-prompt-surface ratchets. The seam is a genuine new capability adding prose across the boot/dispatch/gate/runtime surfaces (as #435/#445 also did), so this bumps two baseline constants (
foFunctionReferenceBaselineBytes,preChangeSharedCoreBytes) and the«dispatch.next-action»ordered-marker sequence, each with an attributing comment. Growth was minimized by keeping the full protocol in the uncounted mod + deferred refs; the constants sit ~150 bytes above the actual measured surface. This is the one spot worth a close look.Fresh-eyes compatibility review (applied)
Three independent fresh-eyes reviewers audited this PR bottom-up from code (not from these docs), hunting false assumptions, hallucinations, and Bridge↔Spacedock mismatches:
_bridge/files matches field-for-field between the producer (egress/check code + the FO-authored JSON in the mod) and Bridge's reader struct tags/validation — names, types, required-vs-optional, and the reply/initiate/alert enum vocabularies all line up.inbox check" indocs/dev/bridge-seam.md(the inbox scan is uncapped; the 1 MiB cap is theevents.jsonlscan) — removed, and it had contradicted the authoritativedocs/seam-contract.md, which itself verified accurate line-by-line.$CLAUDE_CODE_SESSION_IDis set and spacedock already reads it ininternal/dispatch/build.go).Also folded in: the durable-wake caveat (the Stop-hook block is the interactive path; headless is daemon-managed), a timestamp-passthrough guard so a non-RFC3339 Codex/Pi hook value can't silently drop an event line, and a cursor-advance change (advance to the highest line actually read, not an independent recount) that makes over-advancing past an unread intent structurally impossible.
Two flagged as gating follow-ups (not this PR): the
bridge-seammod is only wired into the dogfood workflow today — commission/refit scaffolding must land before the daemon manages a non-dogfood fleet (else a live managed FO writes no heartbeat and Bridge shows it detached); and the LLM-driven cursor is now the endorsed files-not-verbs tradeoff (the step-5 change removes the recount race, but no code enforces the count).Validation
go test ./...andgo test ./... -racegreen (the only failure is the pre-existing, environment-causedTestSurveyCodexPresenceThroughSync, unrelated to this change);gofmtclean; 105 contractlint gates pass including 4 new structural ones (dropped-verb absence, reference closure, mod frontmatter, session-id markers).Follow-ups (spacedock issues)
bridge-seammod into non-dogfood workflows (today it ships canonically + a dogfood copy).agent_type/agent_idon the real harness (per-ship running attribution degrades to git-narration-only if not — honest per contract §2.6, inherited from Bridge seam: harness-agnostic FO-event egress (DRC-3798) #445).Closes the need for #435 and #445.
🤖 Generated with Claude Code