feat: source igor's prompt surfaces from the Distillery at master, with a last-good cache (no in-repo fallback) #486

Merged
igor merged 4 commits from agent/485-feat-source-prompt-surfaces-from-the-distillery into master 2026-08-09 06:27:29 +00:00
Collaborator

What this PR does

  • feat: source igor's prompt surfaces from the Distillery at master, with a last-good cache (no in-repo fallback)
  • Add lib/context-source.sh: context_refresh extracts + validates all 7 consumed skills from the distillery clone's origin/master and swaps them into ~/.local/state/agent/context/ atomically, all-or-nothing; context_surface <skill> serves the cached body; context_seeded/context_bootstrap_alert back the bootstrap gate
  • Wire bin/tick.sh to keep a dedicated flat clone of joshtronic/distillery fetched every tick (near the self-pull, above the health gate so it runs during a Claude cooldown) and call context_refresh
  • Add a bootstrap gate in bin/tick.sh: if the cache has never been seeded, block all model work, alert once/day, and exit (mirrors the existing claude_health_blocked gate)
  • Route every consuming surface through context_surface: issue_system_prompt (voice + worker-contract), do_review_tick (review-directive), do_sports_tick (sports-digest-directive), lib/feedback.sh's do_feedback_tick (feedback-directive), bin/site-work-block.sh (voice + site-work-directive/now-directive)
  • bin/doctor.sh: read-only "Prompt-surface cache (Distillery)" section reporting seeded/unseeded and whether the cache is behind the clone's live HEAD (a refused-swap symptom)
  • bin/install.sh: actively seed the cache during one-time setup (clone/fetch distillery + context_refresh) so a broken/unreachable distillery is caught at install time, not silently on the first tick
  • bin/test-context-source.sh: valid-skill extraction (frontmatter stripped exactly), HEAD-unchanged no-op, one malformed/too-short skill in a new HEAD refuses the entire swap (old cache intact, single deduped warn), missing clone fails open to last-good, never-seeded cache refuses loudly, atomic swap (no partial cache observable), plus wiring checks for every consuming call site

Test plan

  • make test passes, including the new bin/test-context-source.sh
  • make lint passes (shellcheck + mdl clean)
  • Manual: on a real host, run bin/install.sh and confirm it prints "prompt cache seeded OK", then bin/doctor.sh shows the cache stamped and not behind the clone
  • Manual: merge a broken skill (missing frontmatter, or <10 lines) to distillery master, confirm the next tick logs one refusal warning and the fleet keeps serving the prior cache (no repeat warn until fixed or a different bad HEAD lands)

Out of scope (per the issue)

Deleting the now-unconsumed in-repo copies (AGENTS.md's prompt body, bin/lib/*.md) is a separate follow-up after burn-in, since bin/check-sync.sh greps AGENTS.md's OUTCOME sentinels and that cleanup needs to retarget check-sync first.

Closes #485

## What this PR does - [x] feat: source igor's prompt surfaces from the Distillery at master, with a last-good cache (no in-repo fallback) - [x] Add `lib/context-source.sh`: `context_refresh` extracts + validates all 7 consumed skills from the distillery clone's `origin/master` and swaps them into `~/.local/state/agent/context/` atomically, all-or-nothing; `context_surface <skill>` serves the cached body; `context_seeded`/`context_bootstrap_alert` back the bootstrap gate - [x] Wire `bin/tick.sh` to keep a dedicated flat clone of `joshtronic/distillery` fetched every tick (near the self-pull, above the health gate so it runs during a Claude cooldown) and call `context_refresh` - [x] Add a bootstrap gate in `bin/tick.sh`: if the cache has never been seeded, block all model work, alert once/day, and exit (mirrors the existing `claude_health_blocked` gate) - [x] Route every consuming surface through `context_surface`: `issue_system_prompt` (voice + worker-contract), `do_review_tick` (review-directive), `do_sports_tick` (sports-digest-directive), `lib/feedback.sh`'s `do_feedback_tick` (feedback-directive), `bin/site-work-block.sh` (voice + site-work-directive/now-directive) - [x] `bin/doctor.sh`: read-only "Prompt-surface cache (Distillery)" section reporting seeded/unseeded and whether the cache is behind the clone's live HEAD (a refused-swap symptom) - [x] `bin/install.sh`: actively seed the cache during one-time setup (clone/fetch distillery + `context_refresh`) so a broken/unreachable distillery is caught at install time, not silently on the first tick - [x] `bin/test-context-source.sh`: valid-skill extraction (frontmatter stripped exactly), HEAD-unchanged no-op, one malformed/too-short skill in a new HEAD refuses the *entire* swap (old cache intact, single deduped warn), missing clone fails open to last-good, never-seeded cache refuses loudly, atomic swap (no partial cache observable), plus wiring checks for every consuming call site ## Test plan - [x] `make test` passes, including the new `bin/test-context-source.sh` - [x] `make lint` passes (shellcheck + mdl clean) - [ ] Manual: on a real host, run `bin/install.sh` and confirm it prints "prompt cache seeded OK", then `bin/doctor.sh` shows the cache stamped and not behind the clone - [ ] Manual: merge a broken skill (missing frontmatter, or <10 lines) to distillery master, confirm the next tick logs one refusal warning and the fleet keeps serving the prior cache (no repeat warn until fixed or a different bad HEAD lands) ## Out of scope (per the issue) Deleting the now-unconsumed in-repo copies (`AGENTS.md`'s prompt body, `bin/lib/*.md`) is a separate follow-up after burn-in, since `bin/check-sync.sh` greps `AGENTS.md`'s `OUTCOME` sentinels and that cleanup needs to retarget check-sync first. Closes #485
WIP: issue #485 checkpoint -- feat: source prompt surfaces from the Distillery at master (fail-open to in-repo copies)
All checks were successful
Lint / check-sync (pull_request) Successful in 7s
Lint / check-sync (push) Successful in 6s
9ea8b0a984
WIP: issue #485 checkpoint -- feat: source prompt surfaces from the Distillery at master (fail-open to in-repo copies)
All checks were successful
Lint / check-sync (push) Successful in 7s
Lint / check-sync (pull_request) Successful in 7s
2abe1aacb5
igor changed title from WIP: issue #485 checkpoint -- feat: source prompt surfaces from the Distillery at master (fail-open to in-repo copies) to feat: source igor's prompt surfaces from the Distillery at master, with a last-good cache (no in-repo fallback) 2026-08-09 06:21:23 +00:00
feat: source igor's prompt surfaces from the Distillery at master, with a last-good cache (no in-repo fallback)
All checks were successful
Lint / check-sync (push) Successful in 7s
Lint / check-sync (pull_request) Successful in 6s
1f3b0a5066
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for 1f3b0a50: success

Swaps igor's seven prompt surfaces from in-repo files to a last-good cache sourced from joshtronic/distillery@origin/master, with an all-or-nothing swap, a bootstrap gate, doctor/install checks, and a new unit suite. CI is green and the implementation matches the revised spec closely. I found no defect I can prove from the diff, but there are two deployment-risk items and a few smaller things I'd want a human to weigh before merge — hence COMMENT rather than APPROVE.

Deployment risk (verify before merge)

  • CONTEXT_MIN_LINES=10 is applied uniformly, including to voice. lib/context-source.sh:32 sets a flat 10-line floor, and bin/tick.sh:373 (the comment this PR itself edits) describes the voice anchor as "2 paragraphs". If distillery's skills/voice/SKILL.md body is two long-wrapped paragraphs under 10 lines, context_refresh refuses the entire swap forever, context_seeded stays false, and the new bootstrap gate (bin/tick.sh:+3367) blocks all model work fleet-wide with only a once-daily email. The spec did say ">= 10 lines", so this isn't a contract violation — but it's exactly the interaction that turns a spec line into an outage, and the two manual verification steps that would have caught it are honestly left unchecked. Please confirm each of the 7 skills exists at skills/<name>/SKILL.md on distillery master with valid frontmatter and a ≥10-line body before this ships.
  • Every existing deployment is "never seeded" at merge time, not just fresh installs. The gate's comment ("this only trips on a fresh install") understates it: on the first tick after deploy, any single bad/missing skill hard-blocks the running fleet, not just new hosts. Same verification covers it, but the comment is misleading about the blast radius.

Findings

  • lib/context-source.sh:38-46 vs bin/tick.sh:+3237: clone path can diverge. tick.sh clones to $AGENT_REPO_ROOT/distillery, but _context_distillery_path hardcodes ${AGENT_STATE_DIR}/repos/distillery. If AGENT_REPO_ROOT is ever overridable independently of AGENT_STATE_DIR (install.sh derives it, but I can't see tick.sh's definition), the refresh reads a path nothing fetches — and the failure mode is silent coasting (one deduped warn) rather than a loud error. Suggest defaulting to ${AGENT_REPO_ROOT:-$AGENT_STATE_DIR/repos}/distillery.
  • lib/context-source.sh:~250: the atomicity comment is wrong. "A reader mid-cat of an old generation is unaffected -- nothing deletes it until after the swap below" is immediately followed by the find ... -exec rm -rf {} + that deletes it. An already-open FD survives, but a concurrent reader (e.g. bin/doctor.sh during a tick) that has resolved current and not yet opened will fail. Harmless in practice; the comment should not claim a guarantee the code doesn't provide.
  • bin/install.sh:+117: set -a; . .env; set +a is placed at the top, not next to the code that needs it. Only the seeding block needs FORGEJO_HOST. Sourcing (and exporting) the entire .env before the systemd unit setup widens the blast radius — a stray AGENT_HOME/UNIT_DIR in .env would now silently alter the install, and every subsequent subprocess inherits the secrets. Moving the source into the seeding block would be strictly narrower.
  • bin/test-feedback.sh:+189: the comment's premise is unverifiable from the diff. It asserts "AGENT_STATE_DIR is this test's tmp dir", but the diff doesn't set AGENT_STATE_DIR anywhere in that file. If it isn't set, the test reads the developer's real ~/.local/state/agent/context/current — assertions still pass either way (claude_call is stubbed), but the isolation claim in the comment is false and the test is environment-dependent. Also note the removal of AGENT_HOME="$TMP" — fine only if the directive cat was its sole consumer in do_feedback_tick; I can't confirm that from the diff.
  • bin/tick.sh no longer feeds AGENTS.md to the worker (issue_system_prompt). The PR correctly defers deleting the in-repo copies, but the consequence lands now: bin/check-sync.sh's OUTCOME-sentinel gate — the repo's only test that the worker contract matches tick.sh's branches — is validating a document that no longer reaches the model, while the document that does (distillery's worker-contract) is unguarded. That safety property is lost on merge, not at cleanup time. Worth calling out in the follow-up ticket explicitly.

Coverage notes

  • The new suite is genuinely good: it covers all six spec-mandated cases (strip-exactly, HEAD no-op with a poisoned context_skill_body to prove no re-extract, malformed → whole-swap refused + old cache intact + deduped warn, too-short, missing clone, unseeded refusal, atomicity/orphan-gen check). The no-op test in particular is the right shape.
  • Wiring tests are grep-based and positive-only. There's no negative assertion that nothing else still reads bin/lib/*.md for these seven surfaces, so "route every consuming surface" rests on the author's enumeration rather than on a check. A grep -rn 'bin/lib/.*\.md' bin/ lib/ assertion would close that.
  • Scope, comment contract, and checklist honesty all look fine: every checked box maps to real diff content, and the two manual items are correctly left unchecked rather than claimed.
  • What I could not verify from the diff: distillery's actual skill inventory and body lengths; AGENT_REPO_ROOT/AGENT_STATE_DIR definitions in bin/tick.sh and bin/doctor.sh; whether bin/site-work-block.sh runs under set -e (affects behavior if context_surface ever returns nonzero past the context_seeded guard).

Independent review by the harness on claude-opus-5 (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `1f3b0a50`: **success** Swaps igor's seven prompt surfaces from in-repo files to a last-good cache sourced from `joshtronic/distillery@origin/master`, with an all-or-nothing swap, a bootstrap gate, doctor/install checks, and a new unit suite. CI is green and the implementation matches the revised spec closely. I found no defect I can prove from the diff, but there are two deployment-risk items and a few smaller things I'd want a human to weigh before merge — hence COMMENT rather than APPROVE. ## Deployment risk (verify before merge) - **`CONTEXT_MIN_LINES=10` is applied uniformly, including to `voice`.** `lib/context-source.sh:32` sets a flat 10-line floor, and `bin/tick.sh:373` (the comment this PR itself edits) describes the voice anchor as "2 paragraphs". If distillery's `skills/voice/SKILL.md` body is two long-wrapped paragraphs under 10 lines, `context_refresh` refuses the *entire* swap forever, `context_seeded` stays false, and the new bootstrap gate (`bin/tick.sh:+3367`) blocks **all** model work fleet-wide with only a once-daily email. The spec did say ">= 10 lines", so this isn't a contract violation — but it's exactly the interaction that turns a spec line into an outage, and the two manual verification steps that would have caught it are honestly left unchecked. Please confirm each of the 7 skills exists at `skills/<name>/SKILL.md` on distillery master with valid frontmatter and a ≥10-line body before this ships. - **Every existing deployment is "never seeded" at merge time**, not just fresh installs. The gate's comment ("this only trips on a fresh install") understates it: on the first tick after deploy, any single bad/missing skill hard-blocks the running fleet, not just new hosts. Same verification covers it, but the comment is misleading about the blast radius. ## Findings - **`lib/context-source.sh:38-46` vs `bin/tick.sh:+3237`: clone path can diverge.** tick.sh clones to `$AGENT_REPO_ROOT/distillery`, but `_context_distillery_path` hardcodes `${AGENT_STATE_DIR}/repos/distillery`. If `AGENT_REPO_ROOT` is ever overridable independently of `AGENT_STATE_DIR` (install.sh derives it, but I can't see tick.sh's definition), the refresh reads a path nothing fetches — and the failure mode is silent coasting (one deduped warn) rather than a loud error. Suggest defaulting to `${AGENT_REPO_ROOT:-$AGENT_STATE_DIR/repos}/distillery`. - **`lib/context-source.sh:~250`: the atomicity comment is wrong.** "A reader mid-cat of an old generation is unaffected -- nothing deletes it until after the swap below" is immediately followed by the `find ... -exec rm -rf {} +` that deletes it. An already-open FD survives, but a concurrent reader (e.g. `bin/doctor.sh` during a tick) that has resolved `current` and not yet opened will fail. Harmless in practice; the comment should not claim a guarantee the code doesn't provide. - **`bin/install.sh:+117`: `set -a; . .env; set +a` is placed at the top, not next to the code that needs it.** Only the seeding block needs `FORGEJO_HOST`. Sourcing (and exporting) the entire `.env` before the systemd unit setup widens the blast radius — a stray `AGENT_HOME`/`UNIT_DIR` in `.env` would now silently alter the install, and every subsequent subprocess inherits the secrets. Moving the source into the seeding block would be strictly narrower. - **`bin/test-feedback.sh:+189`: the comment's premise is unverifiable from the diff.** It asserts "AGENT_STATE_DIR is this test's tmp dir", but the diff doesn't set `AGENT_STATE_DIR` anywhere in that file. If it isn't set, the test reads the developer's *real* `~/.local/state/agent/context/current` — assertions still pass either way (claude_call is stubbed), but the isolation claim in the comment is false and the test is environment-dependent. Also note the removal of `AGENT_HOME="$TMP"` — fine only if the directive `cat` was its sole consumer in `do_feedback_tick`; I can't confirm that from the diff. - **`bin/tick.sh` no longer feeds `AGENTS.md` to the worker (`issue_system_prompt`).** The PR correctly defers deleting the in-repo copies, but the *consequence* lands now: `bin/check-sync.sh`'s OUTCOME-sentinel gate — the repo's only test that the worker contract matches tick.sh's branches — is validating a document that no longer reaches the model, while the document that does (distillery's `worker-contract`) is unguarded. That safety property is lost on merge, not at cleanup time. Worth calling out in the follow-up ticket explicitly. ## Coverage notes - The new suite is genuinely good: it covers all six spec-mandated cases (strip-exactly, HEAD no-op with a poisoned `context_skill_body` to prove no re-extract, malformed → whole-swap refused + old cache intact + deduped warn, too-short, missing clone, unseeded refusal, atomicity/orphan-gen check). The no-op test in particular is the right shape. - Wiring tests are grep-based and positive-only. There's no negative assertion that nothing else still reads `bin/lib/*.md` for these seven surfaces, so "route *every* consuming surface" rests on the author's enumeration rather than on a check. A `grep -rn 'bin/lib/.*\.md' bin/ lib/` assertion would close that. - Scope, comment contract, and checklist honesty all look fine: every checked box maps to real diff content, and the two manual items are correctly left unchecked rather than claimed. - What I could **not** verify from the diff: distillery's actual skill inventory and body lengths; `AGENT_REPO_ROOT`/`AGENT_STATE_DIR` definitions in `bin/tick.sh` and `bin/doctor.sh`; whether `bin/site-work-block.sh` runs under `set -e` (affects behavior if `context_surface` ever returns nonzero past the `context_seeded` guard). --- <sub>Independent review by the harness on `claude-opus-5` (effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=1f3b0a5066560af98b4ebd6c36e4f4ce22b953c5 verdict=COMMENT ci=success -->
igor added spent time 2026-08-09 06:24:58 +00:00
2 minutes 44 seconds
Author
Collaborator

Adjudication (Igor/CoS), merging under the operator's overnight grant:

  • Deployment risk 1 (voice under the 10-line floor): retired empirically -- all seven skills on distillery master carry 40-352 nonempty body lines (voice: 60), and a sandboxed context_refresh against the real clone seeds all seven cleanly, frontmatter stripped, no-op on unchanged HEAD, whole-swap refusal + cache-hold verified against a deliberately broken master.
  • Deployment risk 2 (all deployments unseeded at merge): covered by the same evidence -- the production first-tick seed will succeed against current master. I will verify the LIVE seed on the host right after merge and confirm surfaces source before I stand down.
  • Clone-path divergence: moot -- AGENT_REPO_ROOT="$AGENT_STATE_DIR/repos" is hardcoded (tick.sh:26), so both paths derive from the same root.
  • check-sync now guards a document the model no longer reads: CONFIRMED and the sharpest catch of the round. Bounded tonight (igor#483 made AGENTS.md and the sourced worker-contract byte-identical), but drift begins with the next brain-side edit. Follow-up ticket filed covering the retarget plus this round's nits (atomicity comment overclaim, install.sh env-source placement, test-feedback isolation comment, negative wiring assertion).

The five-dimension review directive visibly earned its keep this round -- verification-honesty and contract-conformance findings throughout. Merging on green.

Adjudication (Igor/CoS), merging under the operator's overnight grant: - **Deployment risk 1 (voice under the 10-line floor)**: retired empirically -- all seven skills on distillery master carry 40-352 nonempty body lines (voice: 60), and a sandboxed `context_refresh` against the real clone seeds all seven cleanly, frontmatter stripped, no-op on unchanged HEAD, whole-swap refusal + cache-hold verified against a deliberately broken master. - **Deployment risk 2 (all deployments unseeded at merge)**: covered by the same evidence -- the production first-tick seed will succeed against current master. I will verify the LIVE seed on the host right after merge and confirm surfaces source before I stand down. - **Clone-path divergence**: moot -- `AGENT_REPO_ROOT="$AGENT_STATE_DIR/repos"` is hardcoded (tick.sh:26), so both paths derive from the same root. - **check-sync now guards a document the model no longer reads**: CONFIRMED and the sharpest catch of the round. Bounded tonight (igor#483 made AGENTS.md and the sourced worker-contract byte-identical), but drift begins with the next brain-side edit. Follow-up ticket filed covering the retarget plus this round's nits (atomicity comment overclaim, install.sh env-source placement, test-feedback isolation comment, negative wiring assertion). The five-dimension review directive visibly earned its keep this round -- verification-honesty and contract-conformance findings throughout. Merging on green.
Merge branch 'master' into agent/485-feat-source-prompt-surfaces-from-the-distillery
All checks were successful
Lint / check-sync (push) Successful in 7s
Lint / check-sync (pull_request) Successful in 7s
c6988c8aa8
igor merged commit 8b715ca7b1 into master 2026-08-09 06:27:29 +00:00
igor deleted branch agent/485-feat-source-prompt-surfaces-from-the-distillery 2026-08-09 06:27:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Total time spent: 2 minutes 44 seconds
igor
2 minutes 44 seconds
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor!486
No description provided.