Sabbatical · self-improvement

Learning curriculum

Catching up on the system you operate — and the capabilities you're not yet using. Extended weekly.

Delivered
88 of 98
Day 1
Where your automation runs — and where failures go
Sun 5/31 · LaunchAgent vs cron vs /schedule vs claude -p
Concept
Day 2
Reading your own system's state
Mon 6/1 · JARBUS events, the TTR card lifecycle, 'blocked' vs 'error'
Concept
Day 3
Token economics
Tue 6/2 · What's expensive, the 5-min cache TTL, when Opus vs Haiku
Capability
Day 4
The Next.js Server/Client boundary
Wed 6/3 · 'use client', why ssr:false is forbidden in Server Components, CSS @import order
Concept
Day 5
deep-research — fan-out and adversarial verify
Thu 6/4 · When to reach for the research harness instead of hand-searching
Capability
Day 6
/loop and ScheduleWakeup vs fragile LaunchAgents
Fri 6/5 · Self-paced autonomous work the harness can actually see fail
Capability
Day 7
Worktrees and parallel agents
Sat 6/6 · Testing 3 versions of an idea cheaply, in isolation
Capability
Day 8
Memory hygiene — the absence-claim trap
Sun 6/7 · Why 'X doesn't exist' poisoned a session (#1776) and how to correct it
Concept
Day 9
Operating the machine vs building it
Mon 6/8 · Putting it together — steering the portfolio, not just the pipeline
Review
Day 10
Agent view — running many sessions at once
Tue 6/9 · Claude Code's new agent view, --bg background sessions, /resume, peeking at status
Capability
Day 11
Supabase — the database under Focaccia Vote
Wed 6/10 · What Postgres + Supabase are, tables vs rows, the votes table you filled last week
Concept
Day 12
Row-Level Security — why your data can't leak
Thu 6/11 · RLS policies, the rule that gated the Focaccia votes table, anon vs service keys
Concept
Day 13
Cloudflare Pages — how zorc.app goes live
Fri 6/12 · Pages vs Workers, wrangler deploy, _redirects, custom domains (the 404 you fixed)
Concept
Day 14
Dynamic Workflows — orchestrating many agents
Sat 6/13 · The Workflow tool, fan-out vs pipeline, when it beats a single agent
Capability
Day 15
DESIGN.md — why every project carries its own design system
Sun 6/14 · Per-project DESIGN.md, the Instrument Panel redesign, design tokens, /frontend-design
Concept
Day 16
Keeping the portfolio honest
Mon 6/15 · Why Runway Model stalled 13 days unnoticed, STATUS drift, the shepherd daemon
Review
Day 17
LaunchAgents & launchd — how your cron jobs actually run
Tue 6/16 · launchd, plists, why two of your jobs silently unloaded (the Jun 5 notification audit)
Concept
Day 18
Headless Claude — `claude -p` and the fallbackModel safety net
Wed 6/17 · How plan-day runs unattended, what --fallback-model does, the new June config that retries on overload
Capability
Day 19
Watchdogs & timeouts — why a 30-min cap killed a 34-min job
Thu 6/18 · Wall-clock watchdogs, gtimeout --kill-after, Automation Hygiene rule 2 (the May 30 plan-day overrun)
Concept
Day 20
Retry & backoff — the Plaud ingest resilience pattern
Fri 6/19 · The three-tier resilience you shipped this week: in-run retry, stuck-recording giveup, single-alert threshold
Capability
Day 21
Idempotency — why a pipeline must give the same result on re-run
Sat 6/20 · The render regression where a second run left briefings/task-boost/languages empty (#3354)
Concept
Day 22
The standards-checker gate — how bad reviews get blocked before they reach you
Sun 6/21 · The quality gate in report_quick_deliver, blocker verdicts, the shared review-standards rubric
Concept
Day 23
reporter.py — the one door JARBUS uses to write to PDB
Mon 6/22 · The load-bearing boundary: why JARBUS never writes PDB state directly, the four report_* functions
Review
Day 24
The drop flow — fswatch and the three-way router
Tue 6/23 · How anything in FOR TOM/DROP ZONE/ gets routed: video→media-digest, HTML+workflow→outbox, everything else→classify. The ~1s fswatch watcher + the hourly sweep safety net.
Concept
Day 25
Classify + Probe — how raw input becomes an S/M/L tier
Wed 6/24 · classify.py: the tiering brain that decides quick-deliver vs run-protocol vs spawn-project, and should_hold_silently() — why thin/empty captures wait instead of firing a card.
Capability
Day 26
route.py and the 7-stage pipeline
Thu 6/25 · capture → classify → route → process → review → build → publish. The one spine every input travels, and where each route handler hands off.
Concept
Day 27
The Broadside card — one look, every surface
Fri 6/26 · The 2026-06-10 lock: for_tom_html.render_review() is the ONE path; card types differ by content modules, never palette or layout. Why a per-workflow aesthetic is a regression.
Review
Day 28
The aesthetic curator — how your design library learns
Sat 6/27 · selector.py serves a fresh variant per review; design-ratings.jsonl records your Keep/Iterate/Discard; the curator promotes kept variants into Aesthetics/library/ and queues iterates.
Capability
Day 29
claude-mem — your cross-session memory
Sun 6/28 · How observations (🎯 sessions, ✓ changes, ○ discoveries) are captured and searched across sessions — the layer this very weekly review reads from. An underused capability.
Capability
Day 30
Automation hygiene — the five rules that keep unattended jobs honest
Mon 6/29 · The five load-bearing rules from CLAUDE.md (verify the side-effect not the trigger; proven watchdog; failures surface as a 🚨 Things task; upstream-aware fallback; LLM-runtime is the wrong layer for critical orchestration). This is the week you just lived: the 06-26 cascade left language/learning surfaces empty and needed a manual restore.
Review
Day 31
Fallback model chains + watchdogs — surviving an overloaded headless run
Tue 6/30 · Claude Code's --fallback-model (up to 3 models on overload/unavailable) and the wall-clock watchdog pattern. You ACCEPTED fallback chains on 06-09 but it was never wired into settings.json or the cron flags — this lesson closes that loop.
Capability
Day 32
Headless auth — why four mornings died silently
Mon 7/27 · OAuth session expiry on unattended `claude -p` runners: why the token refresh fails headlessly, what 'OAuth session expired and could not be refreshed' means, and the difference between interactive OAuth and API-key auth for cron jobs. This is the exact failure that killed plan-day Jul 22–26 while the dedup ledger correctly held it to one alert.
Capability
Day 33
The alert ledger you shipped on Jul 21
Tue 7/28 · Your dedup + auto-resolve layer (PDB commit d9b37fc): one 🚨 Things task per outage, incidents tracked open in data/alert-state.json with a repeat counter, auto-resolved on recovery. It absorbed 5 consecutive plan-day failures this week without spamming your inbox — the design working exactly as intended.
Review
Day 34
Dead man's switch — catching the job that vanished
Wed 7/29 · Failure alerts only fire when a job runs and fails. Three weekly-reviews (Jul 5, 12, 19) exited 1 with zero surface signal — the cadence just vanished. The heartbeat/witness pattern: a separate checker asks 'did X leave its artifact in the last N hours?' instead of trusting X to report its own death. This is RESET-2026's 'unwitnessed system' diagnosis made concrete.
Concept
Day 35
RESET-2026 — triaging the four JARBUS v2 upgrades
Thu 7/30 · The Jul 21 audit's roadmap: fail-loud defaults, declarative routing, a witness layer, and three-channel signal architecture (mapping LangChain human-in-the-loop patterns to your flows). With 16 days of sabbatical left, at most one gets built — this lesson is the triage.
Review
Day 36
Fallback model chains — third review closing the 06-09 loop
Fri 7/31 · --fallback-model on headless runs: up to 3 models tried on overload/unavailable. Accepted 06-09, still unwired. New context: Opus 5 (1M context) is now Claude Code's default Opus, and Fable 5 was redeployed Jun 30 — your 'opus[1m]' alias inherits upgrades, but your cron wrappers still have no net when the primary is down.
Capability
Day 37
The wind-down playbook — what survives August 15
Sat 8/1 · Two weeks out. The Jul 21 languages decommission (retire the LaunchAgent, retire the email, clean the output repo, one commit per layer) is your template for clean retirement. Keep/kill/mothball triage across the ~20 app.zorc.* LaunchAgents: anything you keep needs a witness; anything unwitnessed gets killed or mothballed.
Concept
Day 38
Retry ≠ fallback — the morning that died three times
Mon 8/10 · Thu Aug 6, plan-day fired at 06:15 and made three attempts over two hours — 06:21, 06:45, 07:23 — every one killed by 'API Error: Connection closed mid-response'. Same model, same endpoint, three tosses of the same coin. Retries absorb transient faults; only a fallback (a *different* resource) absorbs systemic ones. The --fallback-model flag you accepted on 06-09 is still unwired — this is its fourth appearance in the curriculum, now with a fresh corpse.
Concept
Day 39
The permission wall — why the job sweep starved
Tue 8/11 · Sun Aug 3, the JobHunt sweep loaded your profile and dedup state, then hit a wall: in a headless session, any tool not pre-allowlisted in settings.json auto-denies — no human to click Approve. All six web paths (WebSearch, WebFetch, Firecrawl, Playwright, browse, network Bash) failed; it couldn't even create its own 🚨 task. The wrapper's verify leg caught it (exit=1 → deduped alert). Bonus find: six Glob() deny rules in settings.json are malformed and print warnings in every headless log.
Capability
Day 40
ENOTFOUND — hardening one runner isn't hardening the fleet
Wed 8/12 · Sun Aug 2 at 18:08, weekly-review died on 'Unable to connect to API (ENOTFOUND)' — DNS was down. The irony: plan-day's wrapper gained network-wait + retry on Jul 26 (commit a727e1b) for exactly this failure mode. The fix was applied to the instance, not the class — weekly-review and job-sweep never inherited it. The pattern: when two wrappers share a failure mode, the hardening belongs in a shared runner library, not copy-pasted per job.
Review
Day 41
Reading the alert ledger as ops history
Thu 8/13 · Your alert-state.json right now holds two open incidents: jobhunt-sweep (opened Jul 27, re-fired Aug 3, count 2) and weekly-review-fail (Aug 2, count 1). The Aug 6 plan-day failure opened an incident too — and auto-resolved when Aug 7 succeeded, which is why you don't see it. Open entries are your infrastructure to-do list; the count field is how loud each one is shouting; a clean ledger means the fleet is healthy. This file is the closest thing your system has to a pager.
Review
Day 42
Designing for the absent operator
Fri 8/14 · The Jul 26 weekly-review card sat 14 days in TTR unanswered — the second in a row (the 06-28 card sat four weeks). Every stalled card silently stalls its downstream: unauthored curriculum, unresolved decisions. Your cards already carry 'if you don't answer' defaults; the missing piece is an expiry after which the default *executes*. That's the shift from human-in-the-loop (system waits) to human-on-the-loop (system proceeds, human can veto). Post-sabbatical, you're a weekly operator, not a daily one — the system has to survive your absence.
Concept
Day 43
Day 182 — what the system taught
Sat 8/15 · Sabbatical ends today. Six months of infrastructure lessons compress to four: (1) verify the side-effect, never the trigger; (2) one door between systems — reporter.py is why JARBUS/PDB never corrupted each other; (3) dedup before you alert — the ledger turned 5-failure weeks into single tasks; (4) unwitnessed cadences die silently — three vanished weekly-reviews proved it. The consulting-ready artifact isn't any single prototype — it's the operating pattern: pipelines with quality gates, one review surface, failures that surface where you read.
Review
Day 44
The config that failed open
Mon 8/17 · Today the weekly review found ~/.claude/settings.json unparseable — one trailing comma on line 113, left behind when the six malformed Glob() deny rules were rewritten as Read(...) (the Aug 11 lesson's own hands-on). The whole file was being dropped: 53 allow rules, 52 deny rules including every secret-file guard, all three hook types, and the default model. `claude doctor` had been saying 'Invalid or malformed JSON' the entire time; nothing else did. Config that fails to parse fails SILENTLY OPEN, and the fix that broke it was a fix.
Concept
Day 45
Auto-update is a claim, not a guarantee
Tue 8/18 · `claude doctor` reports 'Auto-updates: enabled' and 'Last update attempt: success → 2.1.221 (2026-08-04)'. That was twelve days ago; the current release is 2.1.233 (Aug 14). Both statements are true at once — enabled means the mechanism exists, not that it ran. What you missed matters: 2.1.233 fixes bundled skill aliases reporting 'Unknown command' in -p mode when a user skill shadows them, which is exactly your headless crons plus your deliberate gstack /review shadow.
Capability
Day 46
Done-when has a clock in it
Wed 8/19 · Phase 1 of RESET-2026 shipped Saturday: gen_brief.py, ntfy decision pushes, the relay drain loop. But its acceptance bar reads 'brief arrives 7 consecutive mornings including LLM-failure days.' You have two (Aug 15, 16). The code is merged and the test is still running — it finishes Aug 21. Most acceptance criteria you can check at merge; the ones with a time dimension you cannot, and those are precisely the ones that quietly go unverified because the commit felt like the finish line.
Review
Day 47
False green — the sweep that reads OK while it's dead
Thu 8/20 · job_health_check.py prints 'OK jobhunt-sweep: artifact fresh (2026-08-10)'. Meanwhile alert-state.json holds an open jobhunt-sweep incident, count 3, and `launchctl list` shows app.zorc.jobhunt-sweep with exit status 1. Three sources, one dead job, one of them green. The freshness window for a weekly job is wide enough to swallow a total failure, and the checker never consults the ledger that already knows. A monitor that disagrees with your alert ledger is worse than no monitor — it launders a failure into a pass.
Hands-on
Day 48
Make the default execute
Fri 8/21 · The Aug 9 weekly-review card sat seven days in TTR unanswered — the third consecutive stalled card (Jun 28 sat four weeks, Jul 26 sat fourteen days). Every card already carries an 'if you don't answer' default; none of them ever fires. That's the gap between human-in-the-loop, where the system waits forever, and human-on-the-loop, where the system proceeds on the stated default and you retain a veto. Now that the sabbatical is over and you're a weekly operator, waiting-forever is the same as never.
Concept
Day 49
Phase 3 — what a dashboard does that STATUS.md can't
Sat 8/22 · Phases 0, 1 and 2 all landed Saturday, leaving Mission Control (3–5 sessions) as the last piece of RESET-2026. STATUS.md is already a generated view over the same data, so the honest question is what a dashboard adds. Three things a flat file cannot do: act (approve a decision in place), reconcile (show alert ledger and health check side by side so today's false-green is visible), and compress time (a heartbeat sparkline, not a single timestamp). Scope it to those three or it's a prettier STATUS.md.
Review
Day 50
The interpreter is part of the program
Mon 8/24 · Twice this week the same bug class: an f-string on remarkable.py line 89 silently killed the morning brief four days straight, then 8/23 turned up 78 repeated failures from another f-string crash in the pull pipeline. The code never changed - the Python did. Nested quotes inside f-strings are legal on modern Python and a SyntaxError on the macOS system 3.9, and a LaunchAgent runs whatever `python3` its PATH resolves. The rm-render venv incident (cairosvg importable only from the venv python) is the same lesson from the dependency side: a script is not just its text - the interpreter that executes it is part of the program.
Concept
Day 51
Exit codes are a language, not a verdict
Tue 8/25 · rm_composite.py exits 2 to say 'no annotations found'. rm_pull.py read any nonzero as fatal - and, worse, marked the failed pull as seen, which silently dropped returns until the 8/18 fixes. A Unix exit code is a one-byte protocol the author designs: 0 means success and everything else means whatever the callee documents (grep exits 1 for 'no match', which is not an error). Both 8/18 bugs share one root: caller and callee weren't speaking the same dictionary, and the state file believed the caller.
Concept
Day 52
A timeout you don't catch is just a slower crash
Wed 8/26 · rm_pull.py already had a 90-second timeout on the bun bridge call - and still crashlooped 78 times between 8/20 and 8/23, because subprocess.run(timeout=) doesn't return on expiry, it RAISES TimeoutExpired, and nothing caught it. Setting a timeout without a handler trades a hang for a traceback. This is the missing half of the Automation-hygiene watchdog rule: prove the watchdog fires, and prove the code above it survives the firing. The 8/23 fix added the handler and a failure-test; today's lesson is walking that pattern so it's yours.
Hands-on
Day 53
npx is a fallback, not a strategy
Thu 8/27 · Every single deploy leg this week logged 'wrangler CLI not found' and then quietly succeeded via npx. What npx does: resolve the package into a per-user cache, run its binary, add seconds and a network dependency to every 06:15 deploy - after first logging a failure line you've learned to skim past. That skim reflex is the same alarm-fatigue mechanism as the 8/20 false-green: noise that reads like failure trains you to ignore failure. Three honest options: install wrangler globally, make npx the documented first choice and delete the probe, or pin it as a devDependency of PDB/output.
Capability
Day 54
Zero-trust 302s - teaching your own automation to knock
Fri 8/28 · Every morning the verify leg declares pdb.zorc.app 'failed' on a 302. That's Cloudflare Access doing its job against an unauthenticated curl - while the pages.dev preview URLs serve the same content to anyone with the link. The fence has a gap and the alarm points at the gate. Access service tokens exist exactly for this: a client-id/secret header pair a script presents to satisfy policy without a browser login. Also new this month: Cloudflare can attach an Access policy directly to a Worker and make preview URLs private by default - which closes the preview-side gap the current setup leaves open.
Capability
Day 55
Reconciliation - when two ledgers disagree, one is lying to someone
Sat 8/29 · The 8/20 audit found the reMarkable outbox holding 62 sent PDFs while remarkable-manifest.jsonl records 26 and rm-pull-state.json tracks 43. Three records of one reality. Every automation that reads the manifest - naming lint, retention, returns detection - is operating on a 42% view and doesn't know it. The fix pattern is a reconcile pass: cheap, read-only, diffable, run on a schedule. It's the 8/20 false-green lesson at the data layer: a system that never cross-checks its own ledgers launders drift into confidence. This week's hand-run device survey (32 docs, zero true duplicates) was exactly this pass - the step left is making it a job, not a heroic afternoon.
Review
Day 56
Rate limits and backoff - the 3,116-error storm and the fix
Mon 8/31 · For days in August the reMarkable cloud returned 3,116 HTTP 429s: the old rm_pull design fetched a FULL document archive per held doc every 20-min poll, and at 24 held docs on a device carrying 112 that hammered past the account's rate limit. A 429 means 'too many requests, slow down' - the correct response is exponential backoff, not immediate retry. The rebuilt poller does three things: one light `tags` probe covers ALL due docs (no archives), per-doc cooldowns bound the traffic (20 min normal, 6 h for REFERENCE docs), and a self-expiring rm-quiet-until file silences the poller with ZERO API calls while the throttle drains. The subtlest fix: the bun timeout was raised to 780s because it must EXCEED the bridge's ~10.5-min 429-backoff window - a 300s cap was killing bun mid-backoff, burning budget and returning nothing.
Concept
Day 57
Two Claudes at once - git is the referee
Tue 9/1 · You routinely run more than one Claude Code session at the same time - today alone the event feed logged two feedback_processed events at the exact same second (13:01:51, skill-audit + screens-batch). Parallel sessions share ONE working tree, so two sessions editing the same file, or both running `git add -A`, silently clobber each other. This is the real reason CLAUDE.md's commit-autonomy rule splits by repo: a dedicated project repo (PDB, JobHunt) auto-commits small and often, but the home repo /Users/tom demands 'ask first, stage explicit paths only' - it holds secrets (.claude.json) AND is the tree two sessions are most likely to fight over. The clean isolation primitive is a git worktree (Thursday's lesson): one checkout per session, zero collisions.
Capability
Day 58
Inside the 05:15 job-hunt run - six legs, one LLM
Wed 9/2 · Your restarted job search (2026-08-24) runs as app.zorc.jobhunt-nightly at 05:15, and nightly_wrapper.sh is the cleanest example of automation-hygiene rule 5 in your system: the LLM does CONTENT, deterministic scripts do everything verifiable. Six legs in order: (0) network-settle wait, (1) content - `claude -p /jobhunt-nightly` under a 30-min watchdog, writes leads/briefings/YYYY-MM-DD.md, (2) render - brief_render.py --send pushes the paper doc to the reMarkable 'Job Applications' folder, (3) verify - verify_nightly.py ALWAYS runs and fires a deduped 🚨 on any gap, (4) watch - lead lifecycle alarms, (5) pursuit - pursuit.py --max 2 drains the GO-inked queue into staged packages. The wrapper returns the VERIFY leg's exit code, not the content leg's - because a briefing that didn't get delivered is the failure that matters. ink_apply.py is the ONLY writer of briefing decisions.
Capability
Day 59
Fan-out - 19 agents in one afternoon
Thu 9/3 · On 8-17 you said 'move forward with them all' and the system ran 19 parallel agents, one per revived priority, all completing into staged drafts - the capability you reach for least and that pays a portfolio operator the most. A subagent is a fresh Claude with its own context window and tool set that runs a bounded task and reports back only its conclusion, keeping your main thread clean. You already own specialist ones: standards-checker (grades an artifact, returns JSON) and tom-review-judge (same rubric, returns Markdown) are two agents sharing one rulebook. The superpowers:dispatching-parallel-agents skill is the discipline - independent tasks fan out in ONE message so they run concurrently; dependent ones must wait.
Capability
Day 60
Worktrees - three versions of an idea, zero collisions
Fri 9/4 · A git worktree is a second (third, fourth) working copy of the SAME repo, each on its own branch, each in its own folder - sharing one history but never touching each other's files. For someone testing many cheap prototypes, this is the isolation you want: build variant-A and variant-B in parallel worktrees, compare, keep one, delete the rest - and main never sees a half-finished experiment. It's also the clean answer to Tuesday's two-Claudes hazard: point each session at its own worktree and they physically cannot clobber each other. The superpowers:using-git-worktrees skill wires this in; some agents can even launch with isolation:'worktree' for an auto-cleaned scratch copy.
Hands-on
Day 61
The event feed - what your pipeline is doing right now
Sat 9/5 · Every time JARBUS crosses a milestone it appends one JSON line to PDB/data/jarbus-events/YYYY-MM-DD.jsonl - {ts, type, slug, summary, next_move, source}. pipeline_view.py reads that stream into the 'Recently processed' lane of the local live dashboard at http://localhost:8090, alongside 'Your move' (FOR TOM/TTR/), in-flight build PIDs, and the PRD->asset->live columns. That's the difference a dashboard makes over STATUS.md: STATUS is a snapshot you regenerate, the event feed is an append-only tape of what actually happened and when. The published cousin is Mission Control (RESET-2026 Phase 3) at PDB.zorc.app - same data, plus the reconcile view that sets the alert ledger beside the health check so a false-green can't hide (the 8-20 lesson).
Concept
Day 62
Trust the side-effect, never the trigger
Mon 9/7 · Tie the week together with automation-hygiene rule 1, the one every incident in this curriculum keeps re-teaching: a job that RAN is not a job that WORKED. `last_fired_at` and `exit 0` prove the runner started; only querying the downstream system proves the work landed. This week's lessons are all rule 1 in different clothes - the reMarkable poller verifies against the CLOUD listing, the JobHunt wrapper returns its VERIFY leg's exit code (not the LLM leg's), the event feed records what actually happened, and Mission Control reconciles the health check against the alert ledger so a false-green surfaces. As a weekly operator now, your fleet has to witness itself: every unattended job needs a separate check that asks 'did the artifact appear?' and fires a 🚨 when it didn't (the 7-29 dead-man's-switch).
Review
Day 63
/skill-doctor - what your loaded skills actually cost
Tue 9/8 · Claude Code 2.1.261 (installed this week) added /skill-doctor: it lists every skill loaded into the session, flags the ones that never fire, and shows what each costs in context tokens per turn. This is the 8-28 skill audit turned into a built-in instrument - you shelved 50 skills by hand from a transcript study; now the harness measures the same thing live. The anti-bloat axis in your weekly review ('installed skills cost tokens every turn') finally has a meter.
Capability
Day 64
The pursuit queue - why four GOs became a red alert
Wed 9/9 · On 9-6 you inked four briefings in one sitting and queued six GO leads. ink_apply.py wrote them to JobHunt/leads/pursuit-queue.json; pursuit.py drains that queue at --max 2 per nightly run and fires '🚨 JobHunt pursuit incomplete' whenever leads are left over - which is exactly what happened at 15:40. The alert is not a failure; it is the queue telling you its drain rate (2/night) is slower than your ink rate (6/sitting). Understanding the queue file, the max flag, and the difference between 'queued', 'packaged' and 'sent_on' is what lets you read the JobHunt ledger without asking.
Concept
Day 65
The device budget - 194 docs, one 90-second timeout
Thu 9/10 · job_health_check.py has been red on device-doc-count since 8-28: 194 documents on the reMarkable against a budget of 70. Tonight it bit: the weekly-review retention leg's cloud listing (bridge.ts list 500) timed out at 90 seconds and swept nothing. Every rm_pull poll (every 5 minutes) pays that same listing cost - the 8-26 rate-limit crisis was the same shape. The 42 FOR REVIEW docs older than four days are mostly application packages from 8-24 to 8-27 that were never tagged done. Hold-by-default is your rule, so Claude cannot prune them without your word - which is why it is D2 on this week's paper.
Concept
Day 66
Headless permissions - skip-all vs deny-all-prompts
Fri 9/11 · Every unattended job in your fleet (plan-day, weekly-review, jobhunt-nightly, dropzone-router) runs `claude -p` with --dangerously-skip-permissions: anything the model wants to do is approved. Claude Code 2.1.259 added the opposite posture for headless hosts: --permission-prompts none, which DENIES anything that would have prompted and lets an allowlist (settings.json permissions.allow) define what runs. Skip-all is convenient; deny-all-plus-allowlist is what a security reviewer would ask for, because a prompt-injected web result (the weekly review reads live web pages) can at worst be denied, not executed. The --strict-mcp-config fence you already added on 8-18 is the same instinct applied to MCP servers.
Concept
Day 67
The ask-map - how a checkbox becomes a ledger row
Sat 9/12 · Every FOR REVIEW paper doc ends with a hidden <script id="ask-map"> JSON block: one entry per checkbox strip or write-in, each naming what a mark MEANS and which ledger file it TARGETS. When you tag the doc done, rm_pull.py pulls the ink, rm_process.py composites it over the page, and the ask-map is what turns 'a pen stroke in the second box on p.3' into a row in PDB/data/decisions/. The v3 rule you inked on 8-31 - blank = re-ask, never auto-resolve to the printed REC - lives in those entries too. Four JobHunt briefings became eight ledger rows on 9-6 through exactly this path.
Hands-on
Day 68
An alert that fires 13 times is a to-do, not an alert
Mon 9/14 · Review week. Your alert ledger (PDB/data/alert-state.json) dedups by signature: the same failure updates one Things task and bumps a count instead of spamming. Good design - and it produced a quiet failure of its own. 'Automation health: 4 job(s) didn't run' has been open since 8-28 with count 13; the four are learning-runway (fixed by this week's review), device-doc-count, waiting-rot, and plaud-ingest. Dedup stopped the noise and, with it, the pressure to fix. The rule this week's lessons converge on: a deduped alert with a rising count is a standing to-do that needs an owner, a deadline, or a threshold change - and the weekly review is where those three get decided.
Review
Day 69
A usage limit is not an outage - why three retries hit the same wall
Tue 9/15 · On 9-11 run_plan_day.sh retried the LLM leg three times in 23 minutes against 'You've reached your Fable limit' and published the data-only fallback brief; the JobHunt nightly died the same morning after 78 minutes under a sleeping Mac. Retry is for transient errors; a usage cap is a state that clears on the clock, not on the next attempt. The right response is to degrade to another model: --fallback-model. On 9-13 nightly_wrapper.sh was pinned (fable, fallback sonnet,haiku); run_plan_day.sh and run_weekly_review.sh still pass no --model at all. That is D2 on the 9-13 paper.
Concept
Day 70
Twenty-three leads waiting for a pen - the briefing return loop
Wed 9/16 · Five JobHunt briefings (9-8, 9-9, 9-10, 9-12, 9-13) sit on the device untagged: 23 leads in briefing-state.json with decision null. The one you did return (9-7, tagged by 13:28) became 2 GOs and 3 NOs the same afternoon via ink_apply.py. The loop is sweep -> findings -> briefing -> done tag -> rm_pull -> ink_apply -> pursuit queue (2 per night) -> package. Production is set by cron at 4-5 leads a night; decisions this week were 5, all on one day. The jobhunt-briefing-return alert (open since 9-11, count 4) measures exactly that gap.
Concept
Day 71
The sweep gitleaks stopped - three red mornings, one URL
Thu 9/17 · git-sweep has been red since 9-11 06:46 (alert count 3) because the gitleaks pre-commit hook blocks the JARBUS commit: a news-image URL with an auth= token inside projects/el-nino-atlas/drafts/build-2026-09-10-backup-ba8cgkmz/public/data/coverage.json trips the generic-api-key rule. PDB kept sweeping fine. The hook is doing its job; the question is whether a random-suffixed build backup belongs in the repo at all (it does not) versus a path-scoped .gitleaks.toml allowlist. Never --no-verify.
Hands-on
Day 72
Reading a changelog like a maintainer - 2.1.264 to 2.1.270 in ten minutes
Fri 9/18 · You are on 2.1.268; 2.1.270 shipped this week. The Sunday review reads the Claude Code changelog with a four-line filter: flags that touch headless runs (-p, permissions, fallback, sandbox); anything that changes what a skill or hook sees (2.1.267 fixed effort: frontmatter being ignored on Fable 5); network deadlines (2.1.268 capped WebFetch at 300 s - this review reads live pages and had no cap); and regressions plus their fixes (2.1.269 made read-only git commands prompt; 2.1.270 fixed it a day later - under the allowlist the hardening doc proposes, a prompt becomes a denial). Gateway, OTEL, Windows and terminal-key lines are noise for a sole operator.
Capability
Day 73
Why AegisAI is on the device twice - idempotency and the send record
Sat 9/19 · AegisAI and Groundwork each have two full five-PDF packages on the reMarkable (9-7 and 9-8). pursuit.py re-ran on 9-8 because the 9-7 queue was never marked drained; it correctly left the package files untouched, but package_send.py fired again and send names carry the SEND date, so '9-8 AegisAI Research' is a brand-new document to the device. The build step is idempotent (it checks for files); the send step is not (its identity key includes the date). 10 of the 68 documents added this week are these duplicates. The manifest (PDB/data/remarkable-manifest.jsonl) is the record that shows it.
Concept
Day 74
Review week - the pen is the throttle
Mon 9/21 · The five lessons share one number: produced versus decided. 29 packages staged, 1 sent. 28 leads presented this week, 5 decided. 12 lessons on the device unread (8-28 to 9-12). The 9-6 weekly doc came back five days late; the 9-1 (v2) never did. Cron sets the machine's rate; sittings set yours. The two honest levers are lower production (D3 on the 9-13 paper: pause new briefings until returns catch up) or a bigger decision batch (one sitting, all five briefings). 'Work harder' is not on the list.
Review
Day 75
Seen is not done - the orphan sweep that recovered nine documents
Tue 9/22 · rm_pull.py marked a document 'seen' the moment its PDF was pulled and composited, then fired rm_process detached with output to /dev/null. When the processor died on arrival (usage limit 9-11, a held lock 9-17) the ink was never offered again. Nine documents of your ink - Screens Batch 10 (15 days), the 9-8/9-9/9-10 briefings (6 days), 9-12 to 9-16 (hours) - were recovered by hand on 9-17: 33 lead decisions and 21 cards. The fix (PDB commit 38a3cbb) sweeps each poll for return PDFs still outside _processed/, redispatches them, caps retries at 3, then raises one deduped task. Hygiene rule 1 in one sentence: verify the side effect, not the trigger.
Concept
Day 76
When the Mac cannot spell github.com - one DNS outage across five logs
Wed 9/23 · Sunday 9-20, 05:19 to past 11:14: 'ENOTFOUND' and 'nodename nor servname provided, or not known' appear in the nightly (no briefing), plan-day (data-only brief), git sweep (push failed), the paper-brief upload, ntfy and rm-pull (166 'cloud unreachable' polls). Five red alerts, one cause: the machine could not resolve hostnames. The retry loops and fallbacks did what they were built for - the API error was never an API error. Reading the error string tells you which of three worlds you are in: network (ENOTFOUND, nodename), provider (429, 5xx, overloaded), or account (usage limit).
Hands-on
Day 77
Eight packages that died unsent - the deadline nobody was watching
Thu 9/24 · On 9-17 you closed eight leads as 'missed deadline': aiEDU, Anthropic Economic Mobility, EDF, LifeStance, New Era, Synthesia, C2ES and Coursera - the last one captured live in the morning and 404 by afternoon. Every one had a full package staged and none was sent; DC Government (8-31) is still the only submission. The nightly watch leg flags a 'staged package rotting' item per lead and dedups it (Upside is at x24) but a deduped task is invisible after the second day. The tracker records stage and days-in-stage; what no field records is a send-by date you chose.
Capability
Day 78
One standard, two agents - what moved to /Users/tom/Codex/remarkable on 9-17
Fri 9/25 · The paper authoring standard (INSTRUCTIONS.md, version 2026-09-17) and transport rules (TRANSPORT.md) now live under /Users/tom/Codex/remarkable/ and both the Claude paper-doc and remarkable skills point there instead of carrying their own copies. Two things changed with it: the look (plain sans-serif, 10.5 pt body, 14 pt headings - the serif title preference is revoked) and the routing. Seven Codex documents are registered in PDB/config/remarkable-routing.json as manual_hold: the poller will not pull them, 'done' does nothing to them, and no unattended Codex worker exists. The weekly review you are reading this week was the first Claude-generated doc rendered with the canonical stylesheet.
Concept
Day 79
Skills you did not install - claude.ai sync arrived in 2.1.275
Sat 9/26 · Since Claude Code 2.1.275 the skills and plugins enabled on your claude.ai account sync into terminal sessions signed in with it; opt out with syncClaudeAiSkills: false and syncClaudeAiPlugins: false in ~/.claude/settings.json. Every synced skill is per-session context load you did not choose - the anti-bloat axis the 8-28 audit shelved 50 skills to protect. You are on 2.1.276; 2.1.277 also fixed claude -p sessions that could hang with no result after an internal error (they now exit 1, which your watchdog wrappers already treat as a failure) and stopped headless start-up waiting on the CLAUDE.md lookup.
Capability
Day 80
Review week - 36 staged, 1 sent, 8 expired
Mon 9/28 · The pen came back this week: 33 lead decisions and 21 screen cards applied on 9-17, 13 new packages built the same day. The count that did not move is submissions - one since 8-31 - while eight packages expired unsent. Production is not the constraint and, after 9-17, neither is the decision step. The remaining lever is the submit step, which only you can press; D3 on the 9-20 paper asks for three names and a date each. A package that never leaves the tablet costs the same tokens as one that gets an interview.
Review
Day 81
Opus 5.5 vs Fable - what the model pin in your three wrappers actually buys
Tue 9/29 · On 9-22 Anthropic shipped Claude Opus 5.5 (claude-opus-5-5): 1M context, $4 in / $20 out per million tokens against Fable 5.1's $10 / $50, and Anthropic's own claim is Fable-level output on most work. Your three headless wrappers (run_plan_day.sh, run_weekly_review.sh, JobHunt nightly_wrapper.sh) pin --model fable with a sonnet,haiku fallback since the 9-13 ink. Fable is the model whose usage limit killed the 9-11 runs. This lesson explains what a model pin is, why the pin lives in an environment variable with a default, and how to trial a cheaper model on one morning without editing code.
Capability
Day 82
353 to 97 in one night - release-and-remove on the reMarkable
Wed 9/30 · On 9-22 the device held 353 documents against a budget of 70. On 9-23 it held 97. Nothing was pruned by hand: commit 76148e6 taught rm_pull.py to trash the device copy of any document whose return has already been processed (the file sitting in returns/<date>/_processed/ is the proof), ten per poll, never a manually held one. Before that, 246 finished application packages sat on the tablet because no step ever ended a document's life. The lesson is the idea of a lifecycle with an explicit last step, and why the removal is gated on the processed file and not on the done tag alone.
Concept
Day 83
Manual hold - why your ink on the 9-23 System Audit has done nothing yet
Thu 10/1 · The 9-23 System Audit decision memo (10 pages, 7 batches, about 150 findings) is registered in PDB/config/remarkable-routing.json as owner claude, mode manual_hold. That routing fence means: the poller never pulls it, tagging it done has no effect, and Claude reads the ink only when you say so in chat. Ten documents are held this way (seven Codex design-study docs, the Screens Plan, two Instagram Bank docs, the audit). Everything else, including the weekly review and the nightly briefings, takes the legacy route where done starts a headless processor that edits files. The lesson is the difference between a fence and a queue, and what sentence releases the audit.
Concept
Day 84
The alarm that fires every day is not an alarm - automation-health at count 36
Fri 10/2 · The Things task 'Automation health: N job(s) didn't run' has been open since 8-28 and re-fired 36 times. This week's job-health.log shows why: device-doc-count fails every day (107 docs against a budget of 70), waiting-rot fails every day (FOR REVIEW docs untagged more than 4 days), and plaud-ingest trips because its 2-hour threshold equals its run interval. None of those is a job that did not run. The 9-23 audit calls this finding A1 and proposes splitting the to-do checks out of the alarm. The lesson is signal versus noise in unattended systems: a check you have learned to skip protects nothing, and the fix is a different task shape, not a quieter alarm.
Concept
Day 85
/doctor prompt-audit - Claude Code 2.1.283 checks your CLAUDE.md for old-model habits
Sat 10/3 · Claude Code 2.1.283 (you are on 2.1.282) adds /doctor prompt-audit, which reads your CLAUDE.md files, skills, agents and commands and reports prompting patterns written for older models: stale paths, stale commands, contradicting instruction files first, then wording that newer models no longer need. Your CLAUDE.md imports a 200-line file every session, plus 60-odd skills; the 9-23 audit found stale README and CLAUDE.md lines by hand (batch 6). This is the same check, run by the tool, for free. The lesson covers what the report does and does not do (it proposes, never edits), and how to read confidence on each finding.
Capability
Day 86
Review week - 42 staged, 1 sent, three submit tasks past their date
Mon 10/5 · The week's pen work landed: seven documents returned 9-22, thirteen decision records written, the JARBUS sweep green, the device drained from 353 to about 100. Seven new packages were built from 9-21/9-22 GO ink. The number that did not move is submissions: one since 8-31. The three Submit tasks from the 9-20 paper (Change.org, Groundwork, RCVRC) were created 9-22 with a 9-24 deadline and are still open; Narrative Initiative's 9-24 apply-by passed with a finished package; Coursera closes 9-30, Groundwork 10-06. Five briefings (9-23 to 9-27) sit unanswered. Production and decisions are not the constraint; the send step is.
Review
Day 87
Mods - a plugin can now watch your session (Claude Code 2.1.287)
Tue 10/6 · Claude Code 2.1.287 added mods: TypeScript that runs inside the harness and can draw panes, bands and status lines, intercept tool calls or replace features. The first built-in mod, 'You should know', puts a side agent on every first-party interactive session to post notes on what you or Claude may be missing; it flags, never blocks, and spends tokens on the usage meter the headless jobs share. The 10-1 Scott Memo security pass (three ungated preview URLs, 86 old deployments, a pay-detail leak) is the kind of thing it would have flagged. D2-B on the 10-4 weekly asks whether to try it for a week. The lesson explains what a mod is next to skills, hooks and MCP, the bloat axis, and the on/off switch.
Capability
Day 88
The Send Desk - 55 staged, one sent, and the page that owns the send step
Wed 10/7 · Built 10-1 (JobHunt/_docs/send-desk-spec.md): a local workbench at 127.0.0.1:8780 served by the app.zorc.jobs LaunchAgent, where each application moves through four visible gates - Live (posting re-verified), Warm path (contact or skipped on purpose), My words (own-words meter, 85 percent confirm, resume exempt), Submitted (real date, frozen files). Active = the paper table's priority 1, warn above five. Phases 1-3a live; waiting on Tom: 3-5 writing samples, Connections.csv, three outreach templates. The lesson is what a localhost workbench is, why the gates exist (Tom's three named blockers), and what only he can supply.
Capability
Day 89
Untrusted text in a headless run - the Term-of-the-day quarantine (commit 8df3f41)
Thu 10/8 · Plan-day's Term of the day scrapes nontechnical.dev and hands the page text to an unattended claude -p run. Commit 8df3f41 (10-3) adds three layers: label the field text_untrusted with an untrusted_notice, cap it at 7,000 characters, and drop sentences matching an injection pattern (ignore previous instructions, you are now, system prompt, script tags, run/delete/send near command/file). The fourth layer is the wrapper's --strict-mcp-config fence. The lesson is prompt injection for a non-engineer: the model cannot tell data from instructions, an unattended run has nobody to notice, and defense is label + cap + filter + fence.
Concept
Day 90
models.env - one file, four model slots, and where Sonnet 5.5 would go
Fri 10/9 · Audit session 6 (9-28) moved every headless model pin into /Users/tom/Claude/_config/models.env: NIGHTLY (claude-opus-5-5), JOB (fable), WORK (claude-sonnet-4-6: standards-checker, media digest, build dispatch, media garden), FAST (claude-haiku-4-5), FALLBACK (sonnet,haiku). Shell wrappers source it; Python resolves through JARBUS/scripts/models.py with env var > file > default precedence. Sonnet 5.5 shipped 9-28 at $2/$10 against Sonnet 4.6's $3/$15; D2-A on the 10-4 weekly is a one-line edit to the work slot. The lesson is shared configuration, precedence, and the one-run override as a free trial.
Concept
Day 91
The wrong witness - dropzone-sweep red for a week while running every hour
Sat 10/10 · Since 9-28 the day page has shown dropzone-sweep as a stale artifact. launchctl print says runs=77, last exit 0; JARBUS/logs/dropzone-router.log is modified today. The 9-28 router fix (021aa2d) moved the sweep's output to its own log; PDB/scripts/job_health_check.py line 349 still watches logs/dropzone-sweep.out (last written 9-28 18:44). Same bug as jobhunt-nightly on 8-15. The lesson is Hygiene Rule 1 in practice: an artifact check is only as good as its witness, three independent ways to prove a job ran, and the one-line fix.
Hands-on
Day 92
Review week - 55 staged, 1 sent, 232 documents on the device
Mon 10/12 · The 10-4 numbers: 55 staged (16 drafted, 39 packaged), 1 sent since 8-31, 12 closed; six briefings inked six nights running (9 GO, 8 NO); 15 package sets (75 papers) and 37 untagged lessons on a device at 232 documents; the Send Desk live and waiting on three inputs; Groundwork closed 10-06; Gavel kill-or-revive 10-14. Production, decisions and the audit all moved; a send did not. The lesson closes the week with two counts and one commitment.
Review
Day 93
The job that died with its parent - why the 10-4 weekly review never reached the tablet
Tue 10/13 · On Sunday 10-4 the weekly review ran, built a five-page paper doc, wrote its critique, staged six lessons - and then the headless run handed the paper gate (reporter.report_quick_deliver) to a background shell and ended its turn at 18:26. The claude -p process exited, macOS killed its children, and the standards-checker that needed three more minutes died mid-flight. reporter.log shows the paths verified at 18:25:45 and nothing after; there is no data/jarbus-events/2026-10-04.jsonl, no outbox PDF, no Things task. The doc sat on disk for a week and its three decisions were never asked. The lesson is process lifetime: a background child lives only as long as its parent, Hygiene Rule 1 means waiting for the side-effect, and the fix is a foreground call with a long timeout.
Concept
Day 94
Threads - how your two-line turn stamp becomes the Desk at pdb.zorc.app/desk
Wed 10/14 · Built 10-11 from the Threads Briefing ink (D4: Scan + Desk first). threads_mine.py reads every session transcript and takes the last STATE: / NEXT [YOU|ME|PARKED]: stamp; threads_scan.py merges that with claude-mem, the tablet listing, Things next steps and the project ledger into data/threads/ledger.json; threads_apply.py is the only writer of fates (seeded from your 10-11 ink); gen_desk.py renders the Desk page; run_threads.sh (app.zorc.threads-scan, 05:45) scans, renders, deploys and heartbeats. First ledger: you 56, claude 14, parked 1, closed 7. The lesson is a derived view: nothing here is typed twice, and the stamp you asked for on 10-11 is now machine-read.
Capability
Day 95
The hook that refuses - gitleaks guarding the home-dir repo (commit 02af8fc)
Thu 10/15 · On 10-11 commit autonomy was extended to the home-dir repo (/Users/tom on main), and the ask-first gate was replaced by two mechanical guards: an allow-list .gitignore (the first rule is /* - everything ignored - followed by ! lines that let in only CLAUDE.md, its docs, the r3f skill and the particle-globe source) and a pre-commit hook at /Users/tom/.git/hooks/pre-commit that runs gitleaks git --pre-commit --staged and exits non-zero on any recognizable secret. The lesson is a git hook as a gate that runs before the commit exists, what gitleaks pattern-matches (AWS keys, GitHub tokens, Anthropic keys, private keys), why an allow-list beats a deny-list in a home directory, and the two rules that still need you: never widen the allow-list, never git add -f.
Hands-on
Day 96
Haiku 5.5 and the 100K price step - what the fast slot would pay
Fri 10/16 · Claude Haiku 5.5 shipped 10-7 (claude-haiku-5-5): $0.10 in / $0.50 out per million tokens for prompts up to 100K, $0.50 / $2.50 beyond, cache reads $0.01, 1M context. Haiku 4.5, your CLAUDE_FAST_MODEL, is $1 / $5. Claude Code 2.1.293 made Haiku 5.5 the default haiku, so the fallback alias in CLAUDE_FALLBACK_MODELS=sonnet,haiku already resolves to it; only the pinned id in models.env is still on 4.5. Caveats: the newer tokenizer counts about 30% more tokens for the same text, adaptive thinking is on by default at effort medium, and Haiku 4.5-style budget_tokens calls return 400 - JARBUS's _claude_client.py sets none of those, so the swap is one line. The lesson is a price with a step in it: cheap below the step, five times dearer above, and how to know which side your calls land on.
Concept
Day 97
The dark preview - host-check middleware for zorc-home and 1413designer (fa72c94)
Sat 10/17 · On 10-10 the audit follow-up found that the Cloudflare Pages default domain and every hash/branch preview URL served zorc-home and 1413designer content without the Access login - the same hole the Scott Memo closed on 10-1. The fix is 17 lines of functions/_middleware.js: a Set of allowed hostnames, and any other hostname gets a 404 with Cache-Control: no-store and X-Robots-Tag: noindex. It mirrors PDB's CG-3 in PDB/output/functions/_middleware.js. The lesson is why Access on a custom domain is not Access on the project: every Pages deploy has several hostnames, only the one you configured is gated, and a middleware that checks the Host header is the cheapest way to make the rest go dark.
Hands-on
Day 98
Review week - 1 sent, 60 staged, one Sunday doc that never arrived
Mon 10/19 · The 10-11 numbers: tracker 1 sent, 42 packaged, 18 drafted, 12 closed; seven briefings inked seven nights running (9 GO, 6 NO); nine package sets to the device, four in one Sunday afternoon; the tablet at 289 documents (145 FOR REVIEW, 42 lessons); 56 threads waiting on you per the first Desk; the 10-4 weekly review built but never delivered, its three asks folded into 10-11. Threads, the Federalist pages, the Desk, the gitleaks hook and the dark-preview fix all shipped. The lesson closes the week with two counts and one commitment.
Review
Authored daily · surfaced in /plan-day · emailed 10:30am