docs(rules): claim acquire before worktree work (split-brain prevention for multi-Otto)#3032
Conversation
…it-brain prevention for multi-Otto) Aaron 2026-05-13 set up Otto on Claude Desktop alongside Otto-CLI, creating a real multi-foreground-surface split-brain risk. Both Ottos share git + bus on one machine and could pick the same backlog row simultaneously. The claim-coordinator (tools/bus/claim.ts, B-0400 slice 3) was built for this — atomic check/acquire/release with PID-liveness + 24h TTL. PR #2939 shipped it; PR #2959 added the gate integration. The infrastructure exists; this rule is the discipline-level mechanization that auto-loads at cold-boot so both Ottos read it + use it before worktree-creating backlog work. Applies to: starting work on a B-NNNN row, creating feature branch + worktree, opening a backlog-advancing PR. Does NOT apply to: CI fixes on already-claimed PRs, ad-hoc memory- file writes, conversation-driven substrate, hot fixes. Composes with: - backlog-item-start-gate.md (this rule adds zero-th step) - dont-ask-permission.md (acquire exit 0 IS the substrate permission) - never-be-idle.md (if acquire fails, pick another row) - B-0400 + slice 3 + slice 5 infrastructure - Otto Claude Desktop bootstream (PR #3030) - fetch-before-push memory (sibling coordination pattern at git scope) Future substrate-level mechanization: pre-commit hook that auto-calls 'claim check' and fails on no-held-claim. Today's rule is the discipline-level fix. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98df00a8b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a new agent-rule doc that mandates calling the existing bus claim coordinator (tools/bus/claim.ts acquire) before starting worktree-creating backlog work, to prevent split-brain between concurrent agent instances (Otto-CLI vs Otto-Desktop). This is a discipline-level rule that auto-loads at cold-boot; substrate-level enforcement (pre-commit hook) is explicitly deferred.
Changes:
- New rule file
.claude/rules/claim-acquire-before-worktree-work.mdwith carved sentence, applicability scope, examples, and composition notes - Documents when the rule applies (backlog work) vs. does not (CI fixes, hotfixes, conversation-driven memory writes)
- Cross-references B-0400 slices 3 + 5 (PRs #2939, #2959) as the substrate this rule operationalizes
…shed by same --from value (Vera P1) Vera caught (PR #3032 review): the original Example 2 showed two Ottos calling 'acquire --from otto' and the second exiting 1. That's WRONG per claim.ts line ~270 — same --from is filtered out as self-re- acquire (idempotent). Both calls succeed; split-brain not prevented. Fix: - Example 2 renamed to "KNOWN GAP" — shows actual behavior (both succeed) + names the architectural cause (SENDER_IDS doesn't distinguish multi-surface instances) - Added workarounds: lane-based convention, branch-prefix discipline, schema extension (otto-cli / otto-desktop) - New Example 3 shows TARGET behavior with schema fix (--from otto-cli vs --from otto-desktop) - Example 3 renamed to Example 4 (crash recovery) The architectural gap is a real follow-up: extending SENDER_IDS to support multi-surface instances of the same logical agent. Future work, not this PR. Operational consequence today: use the lane-based + branch-prefix workarounds. Claim-coordinator is INSUFFICIENT for multi-Otto without the schema extension. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d437b9eb22
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
P1 (PRRT_kwDOSF9kNM6B5VwU): carved sentence claimed split-brain is but claim.ts filters by c.from !== sender, soprevented same-sender instances both exit 0. Updated to state --from must differ and document the known gap explicitly. P2 (PRRT_kwDOSF9kNM6B5VwX): example 4 claimed PID-liveness can reclaim a claim mid- no such mechanism exists. Only TTLTTL expiry or explicit release ends a claim. Corrected comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nfirmation 2026-05-13 (#3035) Aaron 2026-05-13 activated Otto on Claude Desktop alongside Otto-CLI, running the multi-foreground-surface architecture from PR #3030 for the first time. Preserves operational discoveries: 1. Claude Desktop Routines are GIT-TRACKED (durable across sessions) vs Claude Code CronCreate session-only — supersedes 'every session MUST CronList' mechanism at Desktop scope 2. Two cadence layers in Desktop: Loop (minute, session-bounded) + Routine (configurable, git-tracked). Both fire simultaneously. 3. CRITICAL ARCHITECTURAL GAP: SENDER_IDS in tools/bus/types.ts doesn't distinguish multi-surface instances. claim.ts treats --from otto from CLI and Desktop as IDENTICAL — split-brain prevention fails without schema extension. Vera caught this on PR #3032 review. Workarounds: lane-based + branch-prefix + future schema extension. 4. Approval friction profile shapes lane split natively: - Otto-CLI: better at .claude/rules/ edits (auto-accept covers) - Otto-Desktop: better at substrate + conversation + routines - The lane split emerges from approval costs, not convention — more robust because friction enforces it Generalizable principle: multi-foreground-surface works because surfaces have DIFFERENT cost profiles, not because they're redundant clones. Future multi-instance work should match work to lowest- friction surface for that work shape. Composes with: - Otto canonical bootstream (full canonical + tight) - .claude/rules/tick-must-never-stop.md (CLI cron caveat) - .claude/rules/claim-acquire-before-worktree-work.md (the rule with the architectural gap named) - .claude/rules/holding-without-named-dependency-is-standing-by-failure.md - B-0400 slice 3 (PR #2939) + slice 5 (PR #2959) - fetch-before-push memory (sibling discipline at git scope) Co-authored-by: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 840c30254d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…aught it doesn't work) Vera caught: branch-prefix doesn't prevent split-brain because claim.ts only filters by 'from', not by 'branch'. Two Ottos with same --from otto but different --branch both succeed (both exit 0). The branch field is post-hoc metadata, not a coordination key. Real workarounds reduce to: 1. Lane-based convention (zero-code; ONLY real prevention today) 2. Schema extension (future SENDER_IDS additions) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 030513fb1b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…orkarounds (Vera P2) The body of the rule was already corrected (commit 030513f) to remove branch-prefix as a workaround. The carved sentence wasn't updated. Vera caught the inconsistency. Now carved sentence + body both say: lane-based convention is the ONLY real split-brain workaround today. Co-Authored-By: Claude <noreply@anthropic.com>
Real-time observation: Otto-CLI hijacked the primary worktree branch context while Otto-Desktop was working there, in the SAME session that Otto-CLI authored PR #3032's claim-acquire-before-worktree rule. The rule was speculative when proposed; this observation is its first empirical validation. Files: - memory/feedback_split_brain_real_time_otto_cli_otto_desktop_primary_worktree_branch_hijack_pr_3032_claim_acquire_rule_validation_2026_05_13.md - docs/hygiene-history/ticks/2026/05/13/2140Z.md The memory extends PR #3032's rule operationally — adds 3 clauses beyond "claim acquire before worktree work": 1. Each Otto gets ONE dedicated worktree (never share primary) 2. Never git checkout on a worktree another Otto is using 3. Bus claim envelope should include 'worktree' field Composes with PR #3032 (validates), substrate-or-it-didn't-happen (rules in flight don't apply to behavior in flight), glass-halo-bidirectional (observation enabled diagnosis). Co-Authored-By: Claude <noreply@anthropic.com>
…trate-level split-brain fix) (#3037) * feat(bus): extend SENDER_IDS schema with multi-surface variants (substrate-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> * fix(rules): SENDER_IDS schema extension landed (PR #3037) — update rule 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> * test(bus): claim.ts coverage for multi-surface sender IDs (Copilot P1) 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> * fix(bus/rules): resolve Copilot review threads on PR #3037 - 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> --------- Co-authored-by: Claude <noreply@anthropic.com>
… registration (#3034) * feat(routines): git-tracked Claude Desktop routines substrate + autonomous-loop registration Aaron asked for a power-user / scripting / version-control path for Claude Desktop scheduled tasks (the "Routines" sidebar panel), composed with the existing CLI <<autonomous-loop>> cron substrate. This introduces tools/routines/ as canonical-source-of-truth for Desktop routines and registers the first one (autonomous-loop) as a Desktop-side every-2-hour fallback for the CLI every-minute tick. Two-layer architecture: tools/routines/<id>/ — git-tracked canonical (SKILL.md + schedule.json) ~/.claude/scheduled-tasks/ — runtime location, generated from canonical tools/routines/install.ts — idempotent Bun TS installer (rule-0 compliant) The scheduled-tasks MCP server is cross-host — same server wired into both CLI Claude Code and Claude Desktop. Both surfaces read/write the same disk store. This PR makes the prompt-body authoring path git-canonical so routines are diffable, PR-reviewable, and shareable across maintainer machines. Three-layer catch-43 defence now in place: 1. CLI session cron — every minute, cheap re-prompt, dies on exit 2. Desktop routine — every 2 hours, persistent cold-boot, survives restart 3. tools/routines/ repo — git canonical, survives runtime corruption Composes with: - .claude/rules/tick-must-never-stop.md (catch-43 substrate) - .claude/rules/rule-0-no-sh-files.md (TS installer, not bash) - .claude/rules/holding-without-named-dependency-is-standing-by-failure.md (PR #3029) - .claude/rules/dv2-data-split-discipline-activated.md (canonical vs runtime change-rate split) - docs/AUTONOMOUS-LOOP.md (canonical tick procedure) Tick shard: docs/hygiene-history/ticks/2026/05/13/2125Z.md Co-Authored-By: Claude <noreply@anthropic.com> * fix(routines): address PR #3034 reviewer feedback — tsc + portability + testability Bundles all 7 review findings from PR #3034 into one commit: install.ts - Fix tsc exactOptionalPropertyTypes violations (was the BLOCKING required check failure) - Eliminate TOCTOU races: replace existsSync+readFileSync pairs with try/catch around readFileSync via readFileOrUndefined helper (addresses CodeQL alert) - Surface invalid schedule.json instead of silently swallowing parse failures: new scheduleParseError field + console.error + parseErrors summary count (Codex P2) - Export listRoutines/readSchedule/syncRoutine/main with directory parameters so tests can drive deterministically without touching real homedir() or import.meta.dir (Copilot test-coverage suggestion; actual test suite is a follow-up) - Gate main() under if (import.meta.main) — aligns with other tools/** scripts that use the importable/testable pattern (Copilot) autonomous-loop/SKILL.md - Remove hardcoded /Users/acehack/Documents/src/repos/Zeta path from routine prompt (Codex P1, Copilot duplicate). Now portable across maintainer machines via "typically ~/Documents/src/repos/Zeta" guidance + project-metadata fallback. Installer will sync the new prompt to the live runtime SKILL.md on next run; the next routine fire (22:07Z) picks up the portable version. README.md - Fix Rule 0 wording: was "only `.sh` allowed there" (implied tools/setup/ is .sh-only); now ".sh files are restricted to under tools/setup/; other formats also live there" (matches .claude/rules/rule-0-no-sh-files.md) (Copilot) docs/hygiene-history/ticks/2026/05/13/2125Z.md - Update pr: TBD → pr: 3034 (Copilot — placeholder breaks consumers that parse frontmatter) Verify trace: 1. npx tsc --noEmit on routines files: clean ✓ 2. bun tools/routines/install.ts: [updated] autonomous-loop, parseErrors=0 ✓ 3. Runtime SKILL.md at ~/.claude/scheduled-tasks/autonomous-loop/SKILL.md now portable Follow-up (not in this commit): add bun:test test suite for install.ts (Copilot suggestion; refactor for testability landed here so the suite can land cleanly). Co-Authored-By: Claude <noreply@anthropic.com> * fix(lint): markdownlint MD037/MD032 in routines SKILL.md and tick shard - SKILL.md:16 — wrap cron expression in backtick span to avoid MD037 (asterisks in "* * * * *" were parsed as emphasis markers) - 2125Z.md:43 — add blank line before list to satisfy MD032 (markdownlint requires blank lines surrounding lists) Co-Authored-By: Claude <noreply@anthropic.com> * docs(memory): land split-brain empirical observation + tick shard 2140Z Real-time observation: Otto-CLI hijacked the primary worktree branch context while Otto-Desktop was working there, in the SAME session that Otto-CLI authored PR #3032's claim-acquire-before-worktree rule. The rule was speculative when proposed; this observation is its first empirical validation. Files: - memory/feedback_split_brain_real_time_otto_cli_otto_desktop_primary_worktree_branch_hijack_pr_3032_claim_acquire_rule_validation_2026_05_13.md - docs/hygiene-history/ticks/2026/05/13/2140Z.md The memory extends PR #3032's rule operationally — adds 3 clauses beyond "claim acquire before worktree work": 1. Each Otto gets ONE dedicated worktree (never share primary) 2. Never git checkout on a worktree another Otto is using 3. Bus claim envelope should include 'worktree' field Composes with PR #3032 (validates), substrate-or-it-didn't-happen (rules in flight don't apply to behavior in flight), glass-halo-bidirectional (observation enabled diagnosis). Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory): add required frontmatter to split-brain observation memory The non-required CI check "check memory file frontmatter completeness" flagged the file. Added YAML frontmatter (name/description/type) matching the convention used by other memory/feedback_*.md files. Co-Authored-By: Claude <noreply@anthropic.com> * fix(routines): surface missing cronExpression + always emit registration hint P1: registration hint was gated on action=created|updated, so a schedule-only change (SKILL.md unchanged) silently skipped the create_scheduled_task reminder. Remove the action emitfilter the hint whenever cronExpression is present, regardless of action. P2: readSchedule returned { missing: false } with no parseError when schedule.json parsed but lacked cronExpression, producing a silent misconfiguration. Now surfaces a descriptive parseError so the caller prints the malformed-schedule warning. Addresses chatgpt-codex-connector review threads on PR #3034. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(routines): remove persona-name attribution + clarify schedule.json optionality Two Copilot review threads on PR #3034: install.ts (Copilot — BP "No name attribution in code"): - Doc comment said "ask Otto (or call directly) to run create_scheduled_task" - Console output said "(in a Claude session, ask Otto to run create_scheduled_task for each)" - Both replaced with persona-agnostic phrasing: "invoke create_scheduled_task from an interactive Claude session (or via direct MCP API call)" README.md (Copilot — internal consistency): - Top-level routine description said each routine has SKILL.md + schedule.json without noting schedule.json is optional - But installer explicitly supports missing schedule.json (ad-hoc routines) - Now: SKILL.md marked **required**, schedule.json marked **optional** with the ad-hoc clarification both in the bullet list and in the Authoring section Verify trace: - bun tools/routines/install.ts → [skipped-unchanged] (idempotent confirmed) - npx tsc --noEmit → clean on routines files Co-Authored-By: Claude <noreply@anthropic.com> * fix(routines): validate cronExpression type + non-zero exit on parse errors (Codex P2 x2) Two P2 findings from Codex on PR #3034: 1. Reject non-string cronExpression values (PRRT_kwDOSF9kNM6B5oEP): readSchedule now narrows parsed.cronExpression via typeof === "string" before accepting it. Non-string values (number, null, object) return a parseError with the offending type. Defends against schedule.json files that happen to round-trip through JSON but have semantically wrong types. 2. Fail installer when schedule parsing reports errors (PRRT_kwDOSF9kNM6B5oEU): main() now returns an exit code (0 on success, 1 if parseErrors > 0). The if (import.meta.main) entrypoint calls process.exit(main()) so CI catches malformed schedule.json files instead of silently counting them in the summary and exiting 0. Verify trace: - bun tools/routines/install.ts (clean run): exits 0, parseErrors=0 ✓ - npx tsc --noEmit: clean on routines files ✓ Co-Authored-By: Claude <noreply@anthropic.com> * docs(tick): 2150Z shard — PR #3034 thread sweep + Computer-Use framing correction Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory): add missing created field to split-brain observation frontmatter The memory-index-integrity CI gate requires all four frontmatter fields: name, description, type, created. The prior fix commit (459a511) added the other three fields but omitted created. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(routines): more reviewer fixes — persona refs, read-error surfacing, empty cron rejection Four threads addressed (3 P1 + 2 P2 from Codex/Copilot on PR #3034): install.ts: - readFileOrUndefined now distinguishes ENOENT (returns undefined → "missing file") from other errors (re-throws → installer fails loudly). Permission errors, IO failures no longer silently treated as missing. (Codex P2, Copilot P1) - readSchedule now rejects empty cronExpression strings with parseError "cronExpression must be a non-empty string". (Codex P2) autonomous-loop/SKILL.md + README.md: - Remove remaining "Otto" persona attribution per BP "No name attribution in code" on current-state surfaces under tools/. SKILL.md description + first paragraph now use "Autonomous-loop tick" without persona claim. README.md "Ask Otto" rephrased to direct MCP invocation; "Otto bootstream" rephrased to "canonical bootstream" with note that the literal filename is historical-surface substrate. (Copilot P1) Identity-stays-unified substrate (PR #3036) is preserved — the fresh-session that fires this routine identifies as Otto via the bootstream's Part 1, not via the routine prompt itself. Routine prompt is functional description; identity is established at cold-boot. Deferred to follow-up row: bun:test coverage for install.ts (Copilot P1 thread 5). Refactor for testability already shipped (exported pure functions accept directory params); actual test suite is its own task — will file as a separate backlog row. Verify trace: - bun tools/routines/install.ts: exits 0, parseErrors=0 ✓ - npx tsc --noEmit: clean on routines files ✓ Co-Authored-By: Claude <noreply@anthropic.com> * fix(routines): listRoutines + missing-SKILL.md surface failures (Codex P1 + P2) Same silent-failure-prevention pattern as readFileOrUndefined: 1. listRoutines now distinguishes ENOENT (returns empty array — the no --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…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>
Auto-load rule mandating before any worktree-creating backlog work. Prevents Otto-CLI vs Otto-Desktop split-brain by requiring atomic claim via the existing B-0400 slice 3 + 5 infrastructure (PRs #2939 + #2959 already shipped). Discipline-level fix today; substrate-level pre-commit hook is future work.
Per Aaron 2026-05-13: "probalby want to figure out how not to split brain with yourself bot any idea?" — this rule is the operationally-honest answer.
🤖 Generated with Claude Code