-
Notifications
You must be signed in to change notification settings - Fork 1
feat(routines): git-tracked Claude Desktop routines + autonomous-loop registration #3034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
AceHack
merged 12 commits into
main
from
otto-routines-git-tracked-autonomous-loop-2026-05-13
May 13, 2026
Merged
Changes from 1 commit
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
01fcf40
feat(routines): git-tracked Claude Desktop routines substrate + auton…
AceHack 79c00b9
fix(routines): address PR #3034 reviewer feedback — tsc + portability…
AceHack 8f6e80d
fix(lint): markdownlint MD037/MD032 in routines SKILL.md and tick shard
AceHack b8594c7
docs(memory): land split-brain empirical observation + tick shard 2140Z
AceHack 459a511
fix(memory): add required frontmatter to split-brain observation memory
AceHack fbdc1fa
fix(routines): surface missing cronExpression + always emit registrat…
AceHack 2d4302f
fix(routines): remove persona-name attribution + clarify schedule.jso…
AceHack 1259be8
fix(routines): validate cronExpression type + non-zero exit on parse …
AceHack f0652b8
docs(tick): 2150Z shard — PR #3034 thread sweep + Computer-Use framin…
AceHack a6c5cf4
fix(memory): add missing created field to split-brain observation fro…
AceHack 6702abb
fix(routines): more reviewer fixes — persona refs, read-error surfaci…
AceHack d79784e
fix(routines): listRoutines + missing-SKILL.md surface failures (Code…
AceHack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| --- | ||
| tick: 2026-05-13T21:25Z | ||
| branch: otto-routines-git-tracked-autonomous-loop-2026-05-13 | ||
| pr: TBD | ||
| 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) | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # `tools/routines/` — git-tracked Claude Desktop routines | ||
|
|
||
| Canonical source for Claude Desktop scheduled tasks (the "Routines" panel in | ||
| the Desktop sidebar; same substrate as the `scheduled-tasks` MCP server). | ||
| Each routine is a directory under `tools/routines/<id>/`: | ||
|
|
||
| - `SKILL.md` — prompt body + YAML frontmatter (`name`, `description`) | ||
| - `schedule.json` — cron expression + task metadata (cronExpression, notifyOnCompletion) | ||
|
|
||
| The runtime stores routines at `~/.claude/scheduled-tasks/<id>/SKILL.md`; | ||
| this directory is the **canonical source** and the runtime location is | ||
| generated from it via `bun tools/routines/install.ts`. | ||
|
AceHack marked this conversation as resolved.
|
||
|
|
||
| ## Two-layer architecture | ||
|
|
||
| | Layer | Path | Authority | | ||
| |---|---|---| | ||
| | **Canonical** (this directory) | `tools/routines/<id>/` | git-tracked, PR-reviewed, diffable, shareable across maintainer machines | | ||
| | **Runtime** | `~/.claude/scheduled-tasks/<id>/` | what the Desktop "Routines" panel + MCP server read at fire time | | ||
|
|
||
| Edit canonical; sync to runtime. Never edit runtime directly without mirroring | ||
| back — runtime drift is the failure mode this two-layer split prevents. | ||
|
|
||
| ## Authoring a new routine | ||
|
|
||
| 1. Create `tools/routines/<id>/SKILL.md`: | ||
|
|
||
| ```markdown | ||
| --- | ||
| name: <id> | ||
| description: <one-line description for sidebar + skill router> | ||
| --- | ||
|
|
||
| <prompt body — fully self-contained, no prior conversation context. | ||
| Each fire is a fresh Claude session cold-boot.> | ||
| ``` | ||
|
|
||
| 2. Create `tools/routines/<id>/schedule.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "taskId": "<id>", | ||
| "cronExpression": "0 */2 * * *", | ||
| "description": "<same as SKILL.md description>", | ||
| "notifyOnCompletion": true | ||
| } | ||
| ``` | ||
|
|
||
| 3. Run `bun tools/routines/install.ts` — copies SKILL.md to runtime path. | ||
|
|
||
| 4. Ask Otto (or call directly via the `scheduled-tasks` MCP) to register | ||
| the cron expression with the runtime by invoking | ||
|
AceHack marked this conversation as resolved.
Outdated
AceHack marked this conversation as resolved.
Outdated
|
||
| `create_scheduled_task(taskId, cronExpression, prompt, description)`. | ||
| The approval dialog is the consent step. After registration, the routine | ||
| fires on its cron cadence. | ||
|
|
||
| ## Why two layers, not just direct MCP calls | ||
|
|
||
| The MCP server is in-memory + writes SKILL.md files but does not version-control | ||
| the cron schedules. Without git-tracking we'd have: | ||
|
|
||
| - No diffability when prompt bodies evolve across maintainer rounds | ||
| - No shareability across maintainer machines (each maintainer would re-author) | ||
| - No retraction-native history of which routine variants we tried | ||
| - Runtime drift undetectable (someone edits SKILL.md via UI; canonical drifts silently) | ||
|
|
||
| Two layers + an installer gives us substrate-honest discipline: the repo is the | ||
| source of truth, the runtime is generated, divergence is detectable. | ||
|
|
||
| ## CLI vs Desktop tick — when to use which | ||
|
|
||
| | Surface | Mechanism | Cadence sweet-spot | Cost per fire | Persistence | | ||
| |---|---|---|---|---| | ||
| | **CLI Claude Code** | `CronCreate` sentinel `<<autonomous-loop>>` | `* * * * *` (every minute) | Cheap — re-prompts same session | Session-only, dies on exit, 7-day auto-expire | | ||
| | **Desktop Claude** | These routines | `0 */2 * * *` (every 2hr) or hourly | Full cold-boot per fire | Persistent on disk, survives app restart | | ||
|
|
||
| Both can run in parallel — they're complementary, not competing. The CLI cron | ||
| is the primary every-minute tick; the Desktop routine is the every-2-hour | ||
| backup that fires even if the CLI session has died. | ||
|
|
||
| ## Project-knowledge dependency | ||
|
|
||
| Routines that reference the Otto bootstream | ||
| (`docs/research/2026-05-12-otto-canonical-bootstream-multi-foreground-surface-orchestrator-ifs-format.md`) | ||
| require it to be uploaded as project knowledge in the Desktop project that | ||
| runs the routine. Without it, the prompt's cold-boot pointer won't resolve | ||
| and the fresh session will lack the substrate it expects. | ||
|
|
||
| ## Composes with | ||
|
|
||
| - [.claude/rules/tick-must-never-stop.md](../../.claude/rules/tick-must-never-stop.md) — catch-43 tick discipline | ||
| - [.claude/rules/holding-without-named-dependency-is-standing-by-failure.md](../../.claude/rules/holding-without-named-dependency-is-standing-by-failure.md) — Standing-by failure mode prevention | ||
| - [docs/AUTONOMOUS-LOOP.md](../../docs/AUTONOMOUS-LOOP.md) — canonical tick procedure | ||
| - [tools/setup/](../setup/) — install-graph pattern (rule 0: only `.sh` allowed there) | ||
|
AceHack marked this conversation as resolved.
Outdated
|
||
| - [.claude/rules/rule-0-no-sh-files.md](../../.claude/rules/rule-0-no-sh-files.md) — TS for everything else | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| --- | ||
| name: autonomous-loop | ||
| description: Otto autonomous-loop tick — fresh-session cold-boot, fires every 2 hours on Desktop | ||
| --- | ||
|
|
||
| Otto autonomous-loop tick — fresh-session cold-boot. | ||
|
|
||
| 1. Read the project-knowledge file titled "2026-05-12-otto-canonical-bootstream-multi-foreground-surface-orchestrator-ifs-format.md" (13 parts, IFS-format). That IS your cold-boot. | ||
|
|
||
| 2. Execute one tick per the bootstream's Part 5 (Cron/Loop Substrate): | ||
| - Refresh worldview FIRST (refresh-before-decide invariant; never act on stale state) | ||
| - Apply Holding-without-named-dependency discipline (.claude/rules/holding-without-named-dependency-is-standing-by-failure.md): NEVER output one-word "Holding" / "Standing by" / "Waiting"; if gated wait, name the SPECIFIC PR + check name + bounded ETA | ||
| - Pick speculative work per never-be-idle priority ladder: known-gap fixes > generative factory improvements > gap-of-gap audits | ||
| - Verify + commit any substantive landing (substrate-or-it-didn't-happen) | ||
| - Write tick shard at docs/hygiene-history/ticks/YYYY/MM/DD/HHMMZ.md if you have repo write access | ||
| - CronList check; arm autonomous-loop sentinel with cadence "* * * * *" if missing (catch-43 — 12hr loss precedent) | ||
| - Visibility signal: state what landed concretely (file paths, PR numbers); stop | ||
|
|
||
| 3. Commit trailer when applicable: Co-Authored-By: Claude <noreply@anthropic.com> | ||
|
|
||
| Repo path: /Users/acehack/Documents/src/repos/Zeta | ||
|
AceHack marked this conversation as resolved.
Outdated
AceHack marked this conversation as resolved.
Outdated
|
||
| Self-contained — no prior conversation context available. Bootstream + repo state + GitHub state = full ground truth. | ||
|
|
||
| This routine is git-tracked at tools/routines/autonomous-loop/SKILL.md in the Zeta repo; the canonical source lives there. | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "taskId": "autonomous-loop", | ||
| "cronExpression": "0 */2 * * *", | ||
| "description": "Otto autonomous-loop tick — fresh-session cold-boot, fires every 2 hours on Desktop", | ||
|
|
||
| "notifyOnCompletion": true | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| #!/usr/bin/env bun | ||
| /** | ||
| * tools/routines/install.ts | ||
| * | ||
| * Syncs canonical routine sources (tools/routines/<id>/SKILL.md) to the | ||
| * Claude Desktop runtime location (~/.claude/scheduled-tasks/<id>/SKILL.md). | ||
| * | ||
| * Idempotent: writes are no-op if file content already matches. | ||
| * | ||
| * Does NOT register cron schedules with the MCP server — that requires an | ||
| * active Claude session with the `scheduled-tasks` MCP server. After running | ||
| * this installer, ask Otto (or call directly) to run `create_scheduled_task` | ||
| * for any routines whose schedule.json lists a cronExpression not yet | ||
| * registered. The approval dialog is the consent step. | ||
| * | ||
| * Composes with tools/setup/ install-graph pattern; obeys rule-0 (TS, not bash). | ||
| */ | ||
|
|
||
| import { | ||
| existsSync, | ||
| readdirSync, | ||
| readFileSync, | ||
| mkdirSync, | ||
| writeFileSync, | ||
| } from "node:fs"; | ||
| import { join, resolve } from "node:path"; | ||
| import { homedir } from "node:os"; | ||
|
|
||
| const REPO_ROUTINES_DIR = resolve(import.meta.dir); | ||
| const RUNTIME_TASKS_DIR = join(homedir(), ".claude", "scheduled-tasks"); | ||
|
|
||
|
AceHack marked this conversation as resolved.
Outdated
|
||
| type Action = | ||
| | "created" | ||
| | "updated" | ||
| | "skipped-unchanged" | ||
| | "skipped-missing-skill"; | ||
|
|
||
| interface SyncResult { | ||
| taskId: string; | ||
| action: Action; | ||
| runtimePath: string; | ||
| cronExpression?: string; | ||
| scheduleMissing?: boolean; | ||
| } | ||
|
|
||
| function listRoutines(): string[] { | ||
| if (!existsSync(REPO_ROUTINES_DIR)) return []; | ||
| return readdirSync(REPO_ROUTINES_DIR, { withFileTypes: true }) | ||
| .filter((d) => d.isDirectory()) | ||
| .map((d) => d.name); | ||
| } | ||
|
|
||
| function readSchedule(srcDir: string): { cronExpression?: string; missing: boolean } { | ||
| const path = join(srcDir, "schedule.json"); | ||
| if (!existsSync(path)) return { missing: true }; | ||
| try { | ||
| const parsed = JSON.parse(readFileSync(path, "utf8")) as { cronExpression?: string }; | ||
| return { cronExpression: parsed.cronExpression, missing: false }; | ||
| } catch { | ||
| return { missing: false }; | ||
|
AceHack marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
|
||
| function syncRoutine(taskId: string): SyncResult { | ||
| const srcDir = join(REPO_ROUTINES_DIR, taskId); | ||
| const srcSkill = join(srcDir, "SKILL.md"); | ||
| const dstDir = join(RUNTIME_TASKS_DIR, taskId); | ||
| const dstSkill = join(dstDir, "SKILL.md"); | ||
|
|
||
| if (!existsSync(srcSkill)) { | ||
| return { taskId, action: "skipped-missing-skill", runtimePath: dstSkill }; | ||
| } | ||
|
|
||
| const srcContent = readFileSync(srcSkill, "utf8"); | ||
| let action: Action = "created"; | ||
|
|
||
| if (existsSync(dstSkill)) { | ||
| const dstContent = readFileSync(dstSkill, "utf8"); | ||
| action = dstContent === srcContent ? "skipped-unchanged" : "updated"; | ||
| } | ||
|
|
||
| if (action !== "skipped-unchanged") { | ||
| mkdirSync(dstDir, { recursive: true }); | ||
| writeFileSync(dstSkill, srcContent); | ||
Check failureCode scanning / CodeQL Potential file system race condition High
The file may have changed since it
was checked Error loading related location Loading |
||
|
AceHack marked this conversation as resolved.
Fixed
|
||
| } | ||
|
|
||
| const { cronExpression, missing } = readSchedule(srcDir); | ||
| return { | ||
| taskId, | ||
| action, | ||
| runtimePath: dstSkill, | ||
| cronExpression, | ||
| scheduleMissing: missing, | ||
| }; | ||
| } | ||
|
|
||
| function main() { | ||
| console.log(`tools/routines/install.ts`); | ||
| console.log(` source: ${REPO_ROUTINES_DIR}`); | ||
| console.log(` target: ${RUNTIME_TASKS_DIR}\n`); | ||
|
|
||
| const routines = listRoutines().filter((id) => id !== "install.ts" && !id.endsWith(".md")); | ||
| if (routines.length === 0) { | ||
| console.log("No routines found under tools/routines/"); | ||
| return; | ||
| } | ||
|
|
||
| const results = routines.map(syncRoutine); | ||
|
|
||
| for (const r of results) { | ||
| const tag = `[${r.action}]`.padEnd(22, " "); | ||
| console.log(`${tag} ${r.taskId}`); | ||
| console.log(` runtime: ${r.runtimePath}`); | ||
| if (r.cronExpression) { | ||
| console.log(` cron: ${r.cronExpression}`); | ||
| } else if (r.scheduleMissing) { | ||
| console.log(` cron: (no schedule.json — ad-hoc routine, register manually)`); | ||
| } | ||
| } | ||
|
|
||
| const needsRegistration = results.filter( | ||
| (r) => r.cronExpression && (r.action === "created" || r.action === "updated"), | ||
| ); | ||
|
AceHack marked this conversation as resolved.
|
||
| if (needsRegistration.length > 0) { | ||
| console.log(`\nNext step — register cron schedules via the scheduled-tasks MCP:`); | ||
| console.log(`(in a Claude session, ask Otto to run create_scheduled_task for each)\n`); | ||
| for (const r of needsRegistration) { | ||
| console.log(` create_scheduled_task(taskId="${r.taskId}", cronExpression="${r.cronExpression}", ...)`); | ||
| } | ||
| } | ||
|
|
||
| const summary = { | ||
| created: results.filter((r) => r.action === "created").length, | ||
| updated: results.filter((r) => r.action === "updated").length, | ||
| unchanged: results.filter((r) => r.action === "skipped-unchanged").length, | ||
| missingSkill: results.filter((r) => r.action === "skipped-missing-skill").length, | ||
| }; | ||
| console.log( | ||
| `\nDone. created=${summary.created} updated=${summary.updated} unchanged=${summary.unchanged} missing=${summary.missingSkill}`, | ||
| ); | ||
|
AceHack marked this conversation as resolved.
|
||
| } | ||
|
|
||
| main(); | ||
|
AceHack marked this conversation as resolved.
Outdated
|
||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.