Skip to content

feat(bus): SENDER_IDS schema extension — multi-surface variants (substrate-level split-brain fix)#3037

Merged
AceHack merged 4 commits into
mainfrom
otto-cli-sender-ids-schema-extension-multi-surface-2026-05-13
May 13, 2026
Merged

feat(bus): SENDER_IDS schema extension — multi-surface variants (substrate-level split-brain fix)#3037
AceHack merged 4 commits into
mainfrom
otto-cli-sender-ids-schema-extension-multi-surface-2026-05-13

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 13, 2026

Summary

Substrate-level mechanization of the split-brain prevention Vera caught on PR #3032: SENDER_IDS previously 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-kiro
  • riven-cli / riven-cursor
  • lior-antigravity / lior-gemini
  • vera-codex

Identity-level names (otto, alexa, etc.) still valid for back-compat.

Operational effect

Before (broken):

$ bun tools/bus/claim.ts acquire --from otto --item B-0444   # Otto-CLI
0
$ bun tools/bus/claim.ts acquire --from otto --item B-0444   # Otto-Desktop
0   # ALSO succeeds — split-brain

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 rejected

Tests

bun test tools/bus/
 64 pass
 0 fail
 174 expect() calls

Composes with

🤖 Generated with Claude Code
EOF

…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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AgentId variants such as otto-cli, otto-desktop, and analogous IDs for other agents.
  • Expands SENDER_IDS so bus and claim CLIs accept the new sender variants while preserving unsuffixed IDs.

Comment thread tools/bus/types.ts
Comment thread tools/bus/types.ts
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>
AceHack and others added 2 commits May 13, 2026 17:52
…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>
Copilot AI review requested due to automatic review settings May 13, 2026 21:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread tools/bus/claim.test.ts Outdated
Comment thread .claude/rules/claim-acquire-before-worktree-work.md Outdated
Comment thread tools/bus/types.ts
- 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>
@AceHack AceHack merged commit a783fb1 into main May 13, 2026
25 checks passed
@AceHack AceHack deleted the otto-cli-sender-ids-schema-extension-multi-surface-2026-05-13 branch May 13, 2026 22:06
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>
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>
AceHack added a commit that referenced this pull request May 21, 2026
…tension + canonical cold-boot bootstream + agent-roster card row; per Aaron 2026-05-21 VSCode auto-mode + remembered-web-conversation enablement; composes with PR #3030 (Otto-Desktop precedent) + PR #3037 (SENDER_IDS schema extension) (#4556)
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.

2 participants