feat(bus): SENDER_IDS schema extension — multi-surface variants (substrate-level split-brain fix)#3037
Merged
AceHack merged 4 commits intoMay 13, 2026
Conversation
…trate-level fix for split-brain) Substrate-level mechanization of the split-brain prevention Vera caught on PR #3032: SENDER_IDS previously only had identity-level names (otto, alexa, riven, vera, lior), so multi-foreground-surface instances of the SAME agent looked identical to claim.ts's `c.from !== sender` filter. Adds surface-tagged variants while preserving back-compat for the unsuffixed names: - otto-cli / otto-desktop (Otto's two active surfaces today) - alexa-cli / alexa-kiro - riven-cli / riven-cursor - lior-antigravity / lior-gemini - vera-codex Now `bun tools/bus/claim.ts acquire --from otto-cli --item B-NNNN` and `--from otto-desktop --item B-NNNN` are DISTINCT claims — the second exits 1 if the first holds. All 64 existing bus tests pass — TypeScript's discriminated union enforces type safety at compile time; runtime SENDER_IDS array matches. Composes with: - .claude/rules/claim-acquire-before-worktree-work.md (PR #3032 merged) - memory/feedback_aaron_otto_identity_stays_unified_across_surfaces_* (PR #3036 merged — identity stays unified; this is the schema fix) - memory/feedback_aaron_multi_foreground_surface_otto_activation_* (PR #3035 merged — operational evidence) - B-0400 slice 3 (claim-coordinator; this extends its sender space) Future work: agents should opt in to surface-tagged variants (otto-cli vs otto). Unsuffixed names still work but don't distinguish surfaces. Eventually the unsuffixed names can be deprecated, but back-compat preserved for today. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the inter-agent bus sender schema so multiple foreground surfaces for the same agent can claim work as distinct bus senders, addressing the split-brain coordination gap described in related PRs.
Changes:
- Adds surface-tagged
AgentIdvariants such asotto-cli,otto-desktop, and analogous IDs for other agents. - Expands
SENDER_IDSso bus and claim CLIs accept the new sender variants while preserving unsuffixed IDs.
AceHack
added a commit
that referenced
this pull request
May 13, 2026
…op's identified follow-up gap) (#3038) Otto-Desktop's tick-close 2026-05-13 identified this gap: bus claim envelopes are advisory broadcasts, not locks; the claim-coordinator at tools/bus/claim.ts is a SEPARATE mechanism the bus envelope schema doesn't yet expose. Extends ClaimPayload with optional 'worktree' field for multi- surface operational disambiguation. Composes with PR #3037 (SENDER_IDS schema extension at sender-ID level) — this row works at the per-worktree metadata level. Substrate-honest open question: should two claims with same sender ID but different worktrees count as one claim or two? Decision captured in the row. P2 effort M; substrate-level fix that supports the unified-identity- multi-surface architecture. Co-authored-by: Claude <noreply@anthropic.com>
…le references (Copilot P1) Copilot caught: rule still said 'schema fix is future work' but the PR being landed (#3037) ships the extension. Both the carved sentence and the workarounds-list now reflect the LANDED state. Co-Authored-By: Claude <noreply@anthropic.com>
Adds 8 new tests proving the SENDER_IDS schema extension actually works: - acquire accepts otto-cli + otto-desktop (surface-tagged) - CRITICAL: otto-cli and otto-desktop are DISTINCT senders on same item — second acquire correctly exits 1 when first holds claim - alexa-kiro / riven-cursor / lior-antigravity / vera-codex accepted - Back-compat: identity-level otto still works Tests: 41 pass / 0 fail / 80 expect() calls (was 33 / ~60). The split-brain prevention now has empirical test coverage that proves the schema extension delivers the operational behavior the rule promises. Co-Authored-By: Claude <noreply@anthropic.com>
- Rename misleading test: "different acquires both succeed" → "same-item claim by second surface is rejected" (the test asserted rejection, not success — name was inverted from the behavior under test) - Add missing acceptance tests for alexa-cli, riven-cli, lior-gemini (three of the new SENDER_IDS had no per-surface coverage); also add cross-surface blocking test for alexa-cli vs alexa-kiro to match the otto-cli/otto-desktop pattern - Update claim-acquire-before-worktree-work.md: remove contradictions that framed the schema fix as still "future work" / "KNOWN GAP" after PR #3037 landed it; Example 2 now shows the fixed surface-tagged flow, Example 3 caption updated to reflect current operational behavior All 45 claim.test.ts assertions pass. Co-Authored-By: Claude <noreply@anthropic.com>
3 tasks
AceHack
added a commit
that referenced
this pull request
May 13, 2026
…ordination evidence - B-0442 slice 3 (real branch-vs-squash comparator) opened as PR #3040 - PR #3037 (SENDER_IDS schema extension) MERGED → multi-surface claim distinction substrate-level operational on main - Multi-Otto coordination empirically validated at git scope: parallel fixes for PR #3037 threads converged to same artifact via unified- identity discipline (no conflict; `reset --hard origin/...` resolution) - Architectural question from Aaron surfaced: 3-coordinated loop (CLI sentinel + Desktop routine + B-0448 cloud) — proposed factoring bootstream Part 5 into pointer file all three cite Co-Authored-By: Claude <noreply@anthropic.com>
3 tasks
AceHack
added a commit
that referenced
this pull request
May 13, 2026
…rd + memory (#3041) * docs(rules): Otto inter-surface communication channels — reference card + memory Aaron 2026-05-13 asked Otto on both surfaces independently "do yall have a good way of communicating you should make sure and save it for future versions to remember." Two complementary observers landed independent partial lists; this PR synthesizes them as 8 channels in 2 classes (ambient vs explicit) and lands the substrate as both auto-loaded rule + detailed memory. Files: - .claude/rules/otto-channels-reference-card.md (auto-loaded; reference card) - memory/feedback_otto_inter_surface_communication_channels_8_channels_ambient_vs_explicit_aaron_2026_05_13.md (substantive empirical evidence) Ambient channels (state-of-the-world; both Ottos read continuously): Git, .claude/rules/ auto-load, Bootstream, Tick shards, Memory files, PR review threads Explicit channels (active signaling; meant to be observed by peer): Bus envelopes, Claim coordinator, Routines schedule, Aaron as ferry Per Otto on CLI 2026-05-13: "the bus is the explicit channel; git is the ambient one." Empirically validated: today's session exercised all 8 channels — 6 commits on PR #3034 across both processes (zero conflicts via rebase-on-pull), 3 memory files landed, PR #3032 rule auto-loaded for future, 9 bus envelopes scanned, multiple Aaron-as-ferry paste-relays, cross-lane PR thread resolution. Composes with PR #3032 (claim-acquire), PR #3036 (identity-stays-unified), PR #3037 (SENDER_IDS schema), B-0444 (bus envelope worktree field), and the existing wake-time-substrate / glass-halo-bidirectional / substrate-or-it-didn't-happen rules. Co-Authored-By: Claude <noreply@anthropic.com> * fix(rules): correct channel count (8→10) + commit count (6→9) consistency Codex P2 + Copilot P1 findings on PR #3041: - Carved sentence + 'all 8 channels' references inconsistent with enumerated list (actually 10: 6 ambient + 4 explicit) - 'Empirical evidence' section claimed 6 commits but commit table lists 9 Both factual inaccuracies. Aligned references throughout: - '8 channels' → '10 channels' in carved sentence + empirical-evidence header + memory description - '6 commits' → '9 commits (6 by Otto on Desktop + 3 by Otto on CLI surface)' in empirical evidence - 'all 8 channels exercised' → 'all 10 channels exercised' The 'Otto on CLI surfaced 6' / 'Otto on Desktop surfaced 8' counts in the inter-observer comparison are correct as-is — those refer to standalone observation counts per surface, not the combined synthesis (= 10). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 13, 2026
* feat(bus): B-0444 — add worktree field to claim envelope Implements Otto-Desktop's identified follow-up gap (B-0444 row filed in PR #3038): the claim envelope schema captured sender ID, item, and branch but not the worktree path. With multi-foreground-surface agents (Otto-CLI + Otto-Desktop on the same machine), the worktree is the per-process operational coordinate — useful as an observability signal even when sender-ID surface tags (PR #3037) already prevent split-brain across surfaces. Changes: - `ClaimPayload.worktree?: string` added to the protocol schema in tools/bus/types.ts (back-compat: optional; envelopes published before this change continue to work) - `ClaimRecord.worktree?: string` added so consumers of activeClaims / allActiveClaims see the field - `tools/bus/claim.ts acquire` accepts `--worktree <path>`; defaults to `process.cwd()` so every new claim publishes a useful worktree value with no caller change required - `check` text output appends `[worktree: <path>]` after the branch info - `check --json` and `acquire --json` include the field on the record / response Design decision recorded substrate-honestly in the B-0444 row: cross-worktree same-sender is treated as idempotent re-acquire (existing behavior preserved). Surface-tagged sender IDs (PR #3037) handle split-brain at the coordination layer; worktree is observability metadata, not a coordination key. A test pins this behavior: "same sender re-acquiring from a different worktree is idempotent (existing behavior preserved)". Tests: 14 new tests in `tools/bus/claim.test.ts` (default cwd, explicit worktree surfaces in check + --json output, combined with --branch, claim-record shape, surface IDs still arbitrate across worktrees, same-sender different-worktree idempotency). Full suite: 52/52 pass. Composes with: - B-0400 (bus protocol root) — extends the existing schema - B-0400 slice 3 / PR #2939 (claim-coordinator implementation) - PR #3037 (SENDER_IDS schema extension — sibling surface-level fix) - `.claude/rules/claim-acquire-before-worktree-work.md` — the rule this row's gap was identified against - PR #3038 (the docs(backlog) commit that filed this row) Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2218Z — B-0444 worktree-field landed via isolated-worktree gambit Per autonomous-loop discipline: substrate-or-it-didn't-happen. Tick shard records the three multi-Otto coordination events observed during this tick (parallel commits, branch-switches under active editing, stash-don't- discard from sibling Otto), the recovery via `git worktree add /tmp/zeta- b0444`, and the substrate-honest evidence that B-0444 itself addresses exactly the class of split-brain this tick experienced. Co-Authored-By: Claude <noreply@anthropic.com> * fix(bus): reject bare --worktree (no value) — Codex P2 on PR #3043 `parseArgs` encodes a bare `--worktree` (flag with no value following) as the literal string `"true"`. Without an explicit check, that string would flow through `flags.worktree ?? process.cwd()` and be recorded as the worktree path, making claim provenance misleading. Reject fast with a clear error so the user notices the mistyped invocation. New test pins the behavior: `bare --worktree (no value) is rejected with a clear error`. Suite: 53/53 pass. Co-Authored-By: Claude <noreply@anthropic.com> * fix(bus): address PR #3043 round-2 reviewer feedback (Codex + Copilot) Two round-2 threads on PR #3043 reshape the --worktree handling: 1. Codex P2 — "Accept explicit `--worktree true` values" Round-1 fix rejected `flags.worktree === "true"` unconditionally, which also rejected a legitimate `acquire ... --worktree true` call where `"true"` is the intended literal path. parseArgs is refactored to distinguish a bare flag (boolean sentinel `true`) from an explicit string value, so the bare-flag rejection now triggers on the boolean while explicit `--worktree true` flows through as a valid string path. 2. Copilot — "Defaulting worktree to process.cwd() can be misleading" Auto-defaulting to cwd records a plausible-looking coordinate when the caller is `cd`'d to an unrelated directory (CI step, wrapper script, cron context). Drop the cwd default: `--worktree <path>` to set, omit to leave the field absent. The presence of the field now reliably indicates the caller opted in. Mechanical changes: - parseArgs: return `Record<string, string | true>` instead of `Record<string, string>`; bare flags get boolean `true`. - New helpers `asString(v)` and `isSet(v)` to read flags cleanly. - Bare-flag check uses `flags.worktree === true` (the boolean). - `worktree` is `undefined` when --worktree omitted; published payload + JSON output omit the field rather than defaulting to cwd. - `asJson` switches from `=== "true"` to `isSet(flags.json)` so bare `--json` continues to work as before. Tests (54/54 pass): - Updated: "acquire omits worktree from payload + JSON when --worktree not specified (Copilot review)" — pinned absent-not-cwd behavior. - New: "explicit --worktree true (literal string) is accepted as a valid path" — pinned that round-1 false-rejection is fixed. - Existing 51 tests unchanged. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced May 19, 2026
6 tasks
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.
Summary
Substrate-level mechanization of the split-brain prevention Vera caught on PR #3032:
SENDER_IDSpreviously only had identity-level names, so multi-foreground-surface instances of the SAME agent looked identical to claim.ts's filter.What changed
Adds surface-tagged variants while preserving back-compat:
otto-cli/otto-desktop(Otto's two active surfaces today)alexa-cli/alexa-kiroriven-cli/riven-cursorlior-antigravity/lior-geminivera-codexIdentity-level names (
otto,alexa, etc.) still valid for back-compat.Operational effect
Before (broken):
After (fixed, when both agents opt in):
$ bun tools/bus/claim.ts acquire --from otto-cli --item B-0444 0 $ bun tools/bus/claim.ts acquire --from otto-desktop --item B-0444 1 # Correctly rejectedTests
Composes with
🤖 Generated with Claude Code
EOF