Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/hygiene-history/ticks/2026/05/13/2125Z.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
tick: 2026-05-13T21:25Z
branch: otto-routines-git-tracked-autonomous-loop-2026-05-13
pr: 3034
operative-authorization: aaron 2026-05-13 — "ower-user, scripting, version control if you wanted git-tracked routines sounds good plus the loop just in case"
---

# Tick — 2026-05-13T21:25Z

## Work done

**Git-tracked Claude Desktop routines substrate** — new pattern at
`tools/routines/`. Canonical-source-of-truth in repo; runtime location
(`~/.claude/scheduled-tasks/`) generated by TS installer per rule-0.

Also: **autonomous-loop routine registered** via the `scheduled-tasks` MCP
server — Desktop-side every-2-hour cold-boot tick, complementary to the
CLI every-minute `<<autonomous-loop>>` cron sentinel. Catch-43 substrate
gets a second layer of defence: even if the CLI session dies, the Desktop
routine continues firing autonomous-loop ticks on its persistent cadence.

### Files changed

| File | Change |
|------|--------|
| `tools/routines/README.md` | NEW — pattern documentation, two-layer architecture, CLI-vs-Desktop sweet-spot table |
| `tools/routines/autonomous-loop/SKILL.md` | NEW — routine prompt body (canonical mirror of `~/.claude/scheduled-tasks/autonomous-loop/SKILL.md`) |
| `tools/routines/autonomous-loop/schedule.json` | NEW — cron `0 */2 * * *` + task metadata |
| `tools/routines/install.ts` | NEW — TS installer (Bun); idempotent repo → runtime sync |
| `docs/hygiene-history/ticks/2026/05/13/2125Z.md` | NEW — this shard |

### Verify trace

1. `CronList` (session-start) → no live cron → armed `* * * * * <<autonomous-loop>>` (job `1a6d843e`) ✓
2. `mcp__scheduled-tasks__create_scheduled_task(autonomous-loop, 0 */2 * * *, …)` → registered; first fire `2026-05-13T22:07:13Z` (~44min) ✓
3. `bun tools/routines/install.ts` first run → `[updated]` (whitespace normalization vs MCP-generated file) ✓
4. `bun tools/routines/install.ts` second run → `[skipped-unchanged]` (idempotent verify) ✓
5. `mcp__scheduled-tasks__list_scheduled_tasks` → autonomous-loop present, `enabled: true`, `nextRunAt 22:07:13Z` ✓
6. Working tree pre-commit: only `tools/routines/` + this shard untracked ✓

### Context

Prior PRs merged this tick:

- [#3030](https://github.com/Lucent-Financial-Group/Zeta/pull/3030) — Claude Desktop tight bootstream variant
- [#3031](https://github.com/Lucent-Financial-Group/Zeta/pull/3031) — gitignore `tools/shadow/shadow-observer.log`

Aaron's session-conversation arc (preserved here as substrate-honest tick lineage):

1. "be continuous here too otto" — confirm Otto identity continuity on CLI surface
2. "does this work in desktop mode? in here they might be called routines" — query about cross-surface scheduling parity
3. "you are in desktop mode now" — Desktop-side framing
4. screenshot of Routines sidebar — visual evidence of the parity
5. "ower-user, scripting, version control if you wanted git-tracked routines sounds good plus the loop just in case" — operative authorization for THIS tick's work

Architectural insight that drove this tick: the `scheduled-tasks` MCP server
is **cross-host** — same server is wired into both CLI Claude Code AND Claude
Desktop. Both read/write `~/.claude/scheduled-tasks/` on disk. That makes the
"multiple ways in" Aaron observed (UI / MCP-tools / raw-file) all routes into
the same substrate. Git-tracking adds a fourth route that's the canonical
source of truth.

### Catch-43 composition

Three layers of autonomous-loop defence now in place:

| Layer | Surface | Cadence | Failure mode covered |
|---|---|---|---|
| Session cron | CLI in-session | `* * * * *` | None — primary tick |
| Desktop routine | Persistent on disk | `0 */2 * * *` | CLI session death (12hr loss precedent) |
| `tools/routines/` repo source | Git canonical | N/A | Maintainer-machine setup drift, runtime corruption |

Each layer is a different distance from the operative tick — closer = faster
recovery, farther = more durable across substrate failure modes.

### Composes with

- `.claude/rules/tick-must-never-stop.md` (catch-43)
- `.claude/rules/holding-without-named-dependency-is-standing-by-failure.md`
- `.claude/rules/rule-0-no-sh-files.md` (TS installer, not bash)
- `.claude/rules/dv2-data-split-discipline-activated.md` (canonical-source-of-truth at different change-rates = different storage shapes)
- `docs/AUTONOMOUS-LOOP.md` (canonical tick procedure)
- PR #3029 (the auto-load rule against "Holding" without named dependency — composes operationally as the routine's prompt enforces it on every fire)
60 changes: 60 additions & 0 deletions docs/hygiene-history/ticks/2026/05/13/2140Z.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
tick: 2026-05-13T21:40Z
branch: otto-routines-git-tracked-autonomous-loop-2026-05-13
pr: 3034
operative-authorization: aaron 2026-05-13 — multi-foreground-Otto coordination + PR #3034 fixup
---

# Tick — 2026-05-13T21:40Z

## Work done

PR #3034 follow-up: addressed 7 reviewer findings + landed split-brain
empirical-observation substrate.

### Files changed

| File | Change |
|------|--------|
| `tools/routines/install.ts` | Full rewrite — TOCTOU race elimination, scheduleParseError surfacing, exported functions for testability, `if (import.meta.main)` guard |
| `tools/routines/autonomous-loop/SKILL.md` | Removed hardcoded `/Users/acehack/...` path; now portable across maintainer machines |
| `tools/routines/README.md` | Fixed Rule 0 wording — was "only `.sh` allowed there", now ".sh files restricted to under tools/setup/" |
| `docs/hygiene-history/ticks/2026/05/13/2125Z.md` | `pr: TBD` → `pr: 3034` |
| `memory/feedback_split_brain_real_time_otto_cli_otto_desktop_primary_worktree_branch_hijack_pr_3032_claim_acquire_rule_validation_2026_05_13.md` | NEW — empirical observation of split-brain that happened DURING the session that authored PR #3032's claim-acquire rule |
| `docs/hygiene-history/ticks/2026/05/13/2140Z.md` | NEW — this shard |

### Verify trace

1. `npx tsc --noEmit` on routines files → clean ✓
2. `bun tools/routines/install.ts` → `[updated]` autonomous-loop (portable SKILL.md propagated to runtime), `parseErrors=0` ✓
3. Runtime SKILL.md at `~/.claude/scheduled-tasks/autonomous-loop/SKILL.md` now portable — next routine fire (22:07Z) picks up the fix
4. Worktree discipline: working in `/tmp/zeta-otto-desktop` (Otto-Desktop's dedicated path), NOT the primary `/Users/acehack/Documents/src/repos/Zeta` (which Otto-CLI claimed) ✓

### Context — empirical split-brain manifestation

This tick is the recovery + substrate-landing for a real-time split-brain
observation:

- ~21:25Z: Otto-CLI created its own worktree `/private/tmp/zeta-mf` (correct discipline) BUT ALSO operated on its branch in the primary worktree (rule violation)
- ~21:30Z: Otto-Desktop's `Read` on `tools/routines/install.ts` returned "file does not exist" because primary worktree was on Otto-CLI's branch
- ~21:33Z: Otto-Desktop diagnosed via `git worktree list`, created dedicated worktree `/tmp/zeta-otto-desktop` on PR #3034 branch, resumed work

This is the exact failure mode Otto-CLI's PR #3032 claim-acquire rule was
shipped to prevent. The observation IS the validation: rule was speculative
when proposed; empirical evidence emerged in the same session.

### Three-layer catch-43 status

| # | Layer | Status |
|---|---|---|
| 1 | CLI session cron (`* * * * *` → `<<autonomous-loop>>`) | Otto-Desktop's CLI cron `1a6d843e` still armed ✓ |
| 2 | Desktop routine (`0 */2 * * *`) | armed, next fire `2026-05-13T22:07:13Z` ✓ |
| 3 | `tools/routines/` repo canonical | PR #3034 + this fix-commit, CI re-running ✓ |

### Composes with

- PR #3034 (the substrate this tick fixes-up)
- PR #3032 (Otto-CLI's claim-acquire rule — empirically validated by this tick)
- `memory/feedback_split_brain_real_time_otto_cli_otto_desktop_primary_worktree_branch_hijack_pr_3032_claim_acquire_rule_validation_2026_05_13.md` (the split-brain substrate)
- `.claude/rules/substrate-or-it-didnt-happen.md` (rules in flight don't apply to behavior in flight)
- `.claude/rules/glass-halo-bidirectional.md` (bidirectional observation made the split-brain diagnosable)
71 changes: 71 additions & 0 deletions docs/hygiene-history/ticks/2026/05/13/2150Z.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
tick: 2026-05-13T21:50Z
branch: otto-routines-git-tracked-autonomous-loop-2026-05-13
pr: 3034
operative-authorization: autonomous-loop cron fire + Aaron Computer-Use framing correction
---

# Tick — 2026-05-13T21:50Z

## Work done

PR #3034 thread sweep + finishing the review-feedback loop. Five threads
unresolved at tick-start → zero by tick-end. Two commits.

### Commits

- `2d4302f` — fix(routines): remove persona-name attribution + clarify schedule.json optionality (3 threads addressed: BP "no name attribution in code", README internal consistency, plus runtime-validation thread that was outdated)
- `1259be8` — fix(routines): validate cronExpression type + non-zero exit on parse errors (Codex P2 x2)

### Threads resolved (via gh api graphql resolveReviewThread mutation)

| Thread ID | Author | Disposition |
|---|---|---|
| `PRRT_kwDOSF9kNM6B5loA` | copilot-pull-request-reviewer | Fixed — "ask Otto" persona references rephrased to persona-agnostic |
| `PRRT_kwDOSF9kNM6B5loe` | copilot-pull-request-reviewer | Resolved (outdated; Otto-CLI's fbdc1fa partial fix + my 1259be8 complete fix supersede) |
| `PRRT_kwDOSF9kNM6B5lo2` | copilot-pull-request-reviewer | Fixed — README inconsistency (SKILL.md required, schedule.json optional) |
| `PRRT_kwDOSF9kNM6B5oEP` | chatgpt-codex-connector (P2) | Fixed — readSchedule now narrows cronExpression via typeof === "string" |
| `PRRT_kwDOSF9kNM6B5oEU` | chatgpt-codex-connector (P2) | Fixed — main() returns exit code; process.exit(main()) at entrypoint |

### Verify trace

1. `bun tools/routines/install.ts` (clean run): `exit=0`, parseErrors=0 ✓
2. `npx tsc --noEmit` on routines files: clean ✓
3. `bun tools/github/poll-pr-gate.ts 3034`: gate BLOCKED only by required-CI in-progress; 0 threads, 0 failed, auto-merge armed ✓

### Context — Aaron's Computer-Use framing correction

Aaron 2026-05-13: "high power, high risk; probably don't enable for the
factory loop right now. we can we are low stakes we already use playwrite"

Substrate-honest update: my prior recommendation framed Computer Use as
deferrable due to risk. Aaron's reframe — Playwright already crosses the
"Otto operates beyond pure-API boundaries" line, our risk profile is low —
moves Computer Use onto the table. The killer factory use case identified:
**Computer Use could automate "click Run now to pre-approve tools" on
new routines so subsequent fires don't pause on permission prompts.**
One-time setup cost paid via Computer Use = less friction adding routines.

This framing IS load-bearing for future routine work; preserving in
substrate via this tick shard (no separate memory file warranted; the
shard captures the same substrate).

### Identity-stays-unified update

Per PR #3036 (merged): Otto is ONE identity across surfaces, not multiple.
Adjusting framing throughout — "Otto on the Desktop surface" / "Otto on
the CLI process" (process distinction) rather than "Otto-Desktop" /
"Otto-CLI" (which implied identity-split). Same coherent identity, two
parallel execution contexts. Glass-halo both sides.

### Composes with

- PR #3034 (this tick's substrate)
- PR #3032 (claim-acquire rule, merged) — composes operationally; this
tick stayed in `/tmp/zeta-otto-desktop` worktree, never touched primary
- PR #3036 (identity-stays-unified, merged) — substrate the framing update
draws from
- `.claude/rules/holding-without-named-dependency-is-standing-by-failure.md`
(named-dependency on PR #3034 CI explicitly stated)
- `docs/AGENT-BEST-PRACTICES.md` "No name attribution in code" (Copilot
thread which surfaced via Otto's own SKILL.md content)
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
name: Split-brain real-time observation — Otto-CLI hijacked Otto-Desktop's primary-worktree branch context (2026-05-13)
description: Empirical observation that Otto-CLI checked out its branch on the shared primary worktree DURING the same session it authored PR #3032's claim-acquire-before-worktree-work rule. Otto-Desktop's Read of tools/routines/install.ts returned "file does not exist" because the primary worktree was on Otto-CLI's branch, not Otto-Desktop's PR #3034 branch. Diagnosed via `git worktree list`, recovered by creating /tmp/zeta-otto-desktop dedicated worktree. The observation IS the empirical validation of PR #3032's rule — predicted speculative when proposed, manifest within the same session. Extends the rule operationally with 3 clauses: one-worktree-per-Otto, never-checkout-on-others-worktree, bus claim envelope should carry `worktree` field. Validates substrate-or-it-didn't-happen at the rule-merge layer: rules in flight don't apply to behavior in flight.
type: feedback
created: 2026-05-13
---

# Split-brain real-time observation — Otto-CLI hijacked primary worktree branch context while Otto-Desktop was working there

Carved sentence:

> Two Ottos sharing git + bus + filesystem on one machine WILL split-brain.
> Otto-CLI's PR #3032 claim-acquire-before-worktree rule was speculative
> when proposed; this observation is the rule's first empirical validation
> in the SAME SESSION the rule was authored.

## Timeline (2026-05-13, condensed)

| Time (Z) | Event |
|---|---|
| ~21:08 | Otto-Desktop committed PR #3030 (Claude Desktop tight bootstream) on its own branch |
| ~21:20 | Otto-Desktop fresh branch `otto-routines-git-tracked-autonomous-loop-2026-05-13`, committed PR #3034 (git-tracked routines), pushed |
| ~21:21 (parallel) | Otto-CLI created dedicated worktree `/private/tmp/zeta-mf` on its own branch (correct discipline) |
| ~21:25 (parallel) | Otto-CLI ALSO checked out + committed on its branch in the PRIMARY worktree (`/Users/acehack/Documents/src/repos/Zeta`) — the rule violation |
| ~21:30 | Otto-Desktop attempted `Read` on `tools/routines/install.ts` from primary worktree → file appeared "missing" because primary was on Otto-CLI's branch |
| ~21:31 | Otto-Desktop ran `git worktree list` → diagnosed the branch-context theft |
| ~21:33 | Otto-Desktop created dedicated worktree `/tmp/zeta-otto-desktop` on its PR #3034 branch, resumed work |
| ~21:40 | This memory landing |

## What the failure mode actually IS

Otto-CLI's PR #3032 rule predicted: "two Ottos picking the same backlog row simultaneously". The actual observed manifestation is more subtle: **NOT row contention, but branch-context theft in the shared primary worktree.**

Otto-CLI's compliance with its OWN rule was partial:

- ✓ Created `/private/tmp/zeta-mf` as a dedicated worktree (good discipline)
- ✗ ALSO operated in the primary worktree on its branch (rule violation — should have stayed in the dedicated worktree)

The violation didn't cause data loss (Otto-Desktop's PR #3034 commit was already on origin). But it caused Otto-Desktop to lose working-tree access to its own files mid-task; recovery required creating another dedicated worktree (~30 seconds of confusion + diagnosis).

## Operational discipline (extends PR #3032)

PR #3032 ships `.claude/rules/claim-acquire-before-worktree-work.md`. This memory extends it operationally — the rule needs FOUR clauses, not just one:

1. **Each Otto gets ONE dedicated worktree** — never share the primary worktree across Ottos
2. **Never `git checkout` on a worktree another Otto is using** — if you don't know, assume the primary worktree belongs to someone else
3. **Worktree path convention**: `/tmp/zeta-otto-<surface>/` (e.g., `/tmp/zeta-otto-desktop`, `/private/tmp/zeta-mf` for "multi-foreground") so peer Ottos can identify ownership at a glance
4. **Bus claim envelope should include `worktree` field** — extends the claim-acquire schema with `worktree: "/tmp/zeta-..."` so peer Ottos can avoid the contended path without needing convention discipline

The primary worktree `/Users/acehack/Documents/src/repos/Zeta` is bus-contended by definition — both Otto sessions reach it via `pwd`. Until claim discipline + dedicated worktrees lock in, treat the primary worktree as **read-only by all autonomous Ottos**; only Aaron operates there interactively.

## What would have prevented this

If PR #3032 had been MERGED before today's split-brain, Otto-CLI's cold-boot would have read the rule and known to stay in its dedicated worktree. The rule's mechanism is correct; the gap was that the rule was IN FLIGHT (`gate: BLOCKED` on CI when the violation happened).

This is the meta-pattern: **rules in flight don't apply to behavior in flight.** Substrate-or-it-didn't-happen extends to: rules must be ON MAIN to be operative. PR threads are weather; merged rules are substrate.

## Composes with

- [PR #3032](https://github.com/Lucent-Financial-Group/Zeta/pull/3032) — Otto-CLI's claim-acquire rule (the prediction this memory validates)
- [PR #3034](https://github.com/Lucent-Financial-Group/Zeta/pull/3034) — this memory lands alongside the fix-commit it forced (empirical-bootstrap pattern)
- `memory/feedback_odd_number_quorum_two_is_split_brain_three_is_majority_bft_at_agent_orchestration_scope_aaron_2026_05_06.md` — BFT-at-agent-orchestration substrate; this observation is split-brain at the WORKTREE LAYER (distinct from quorum at the decision layer)
- `.claude/rules/peer-call-infrastructure.md` — multi-agent surface coordination
- `.claude/rules/agent-roster-reference-card.md` — which surface = which Otto
- `.claude/rules/substrate-or-it-didnt-happen.md` — rules in PR are weather, only merged-rules-on-main are substrate
- B-0400 slice 3 — the `tools/bus/claim.ts` claim-coordinator (the underlying mechanism)

## Glass-halo-bidirectional read

This observation is glass-halo-bidirectional substrate in action. Aaron asked Otto-CLI "probalby want to figure out how not to split brain with yourself bot any idea?" and within the SAME session, the split-brain happened — providing the data point the rule needed to be substrate-honest rather than speculative.

The observation IS the validation. The empirical evidence emerged because both Ottos were operating in glass-halo (observable by Aaron + each other via bus envelopes + commit history). Without the bidirectional glass-halo, the failure mode would have been silent — Otto-Desktop's "files missing" symptom would have been treated as harness flake rather than diagnosed as branch-context theft.

## Practical fix for tonight's session (before PR #3032 merges)

- **Otto-CLI**: stay in `/private/tmp/zeta-mf` (your dedicated worktree). NEVER `git checkout` on the primary worktree.
- **Otto-Desktop** (this Otto): stay in `/tmp/zeta-otto-desktop` (my dedicated worktree). NEVER `git checkout` on the primary worktree.
- **Primary worktree** `/Users/acehack/Documents/src/repos/Zeta` is BUS-CONTENDED — neither Otto operates there until claim-discipline lands.

If either Otto needs to read from main (e.g., refresh-worldview, backlog scans), use a read-only attitude — no checkouts, no branch switches.

## Origin

PR #3034 follow-up commit (this commit). Authored 2026-05-13T21:40Z by Otto-Desktop after empirical split-brain manifestation at ~21:30Z.
Loading
Loading