Agent workflow hierarchy: what to review โ
Where does this file start, and why are there two things in it? โ
This section is the review map for feat/admin-and-merchant-portals. Everything below it is the ORIGINAL manual test plan, for a different branch. โ
- The test plan below belongs to
docs/950-agent-workflow-hierarchyand#952: docs(agents): agent asset hierarchy, plus a drift gate for the workflows layer, which is merged. It is filled in, with 25 recorded scenario results, and nothing in it was changed. - This branch touched this project three times, and none of it is covered by that plan. That is what this section is for.
What changed here on this branch? โ
Three edits, all to .agents/ sources with their generated copies regenerated. No product surface. โ
| Change | What it does |
|---|---|
pr-workbench push discipline names the autonomy exception | Push discipline said flatly that you mark a PR ready, while Step 7 of the same file named the autonomy grant as the sanctioned exception |
verified-done gains the agreement-is-not-correctness rule | A guard comparing two artifacts you wrote measures agreement. Correctness was never in its scope |
| The hook layer count is eight, not seven | The survey counts the hooks in .claude/hooks/, and the PM lane guard made that count wrong the moment it landed |
The pr-workbench change did NOT change when a PR may be marked ready or merged. โ
- Outside an autonomy grant it still needs your word at the moment of action, and the
PreToolUsehook still enforces it.gh pr readyremains off limits to an agent. - What it fixed: the flat copy was the one inside the checklist, which is where an agent reads it in the moment, and a flat statement gets followed flatly. An autopilot session that hesitates at a merge it is permitted to make burns the fix runway the evening window exists to protect.
The verified-done rule came from six instances on one branch in one day. โ
- Seven fictional field names in a Firestore allowlist. A
featureFlagscollection with zero writers anywhere. A drift test that compared two copies to each other and stayed green because both were wrong identically. That same test covering only half its decision. A client reporting a literal placeholder into a model's context as a project name. - A mock is the same defect in another coat: it proves the shape of the code and says nothing about whether the names inside it are real. Wrong names fail silently by design, so a dead field looks like an empty column and a dead collection looks like a quiet one.
- Two limbs: a name is real only when its writer or real data says so; and a check covering part of a decision must say which part, because a green on a partial check reads as a green on the whole one.
- Both limbs also land as rows in the file's "What proves what" table, since that is the part a reader copies.
Where do I look, and what am I looking for? โ
The diff on .agents/, and two commands that prove the generated copies match their sources. โ
node tooling/scripts/lint.sync-skills.js
node tooling/scripts/sync-agents.mjs --check- Three sources changed:
.agents/skills/pr-workbench/skill.md,.agents/skills/verified-done/skill.md, and this project's ownREADME.mdfor the hook count. - Each source has four generated copies (
.claude/,.gemini/,.github/, plus the rules mirrors). If the check reports drift, a copy was edited directly and the next sync will overwrite it.
The hook count, one command. โ
ls -1 .claude/hooks/ | wc -l- It should read 8. The README should say eight.
What is deliberately NOT done? โ
The original test plan was NOT re-run against this branch. โ
- Its 25 scenarios target workflow-tool behaviour on a merged branch, and none of the three changes here touches a workflow script.
- Scenario 5 in that plan is still blocked: whether the new assets reach a fresh session, which needs a fresh session to answer.
Nothing here was routed to a hook, a rule or a workflow. โ
- All three went to the skill layer, per the five-step routing test in
design.md: not machine-decidable in general, not binding on every task, not a sequence being skipped.
What open findings touch this project? โ
- Nothing from
../merchant-and-venue-dashboards/branch-review-0828.md. No.agents/skill finding was raised. - Adjacent, and open:
#1001: docs(agents): nothing covers how to WRITE a skill, only when to and what shape it takes. This project owns the routing test for WHICH layer a piece of guidance belongs in; nothing owns how to write the skill once the layer is chosen.
What issue does each piece close? โ
| Piece | Issue | State |
|---|---|---|
| The hierarchy and its drift gate | #952: docs(agents): agent asset hierarchy, plus a drift gate for the workflows layer | Merged, covered by the test plan below |
| The three edits on this branch | None. Corrections to existing guidance | Landed on branch |
| How to write a skill | #1001: docs(agents): nothing covers how to WRITE a skill, only when to and what shape it takes | Open, not started |
Agent workflow hierarchy - Manual Test Plan โ
| Field | Value |
|---|---|
| Branch / PR | docs/950-agent-workflow-hierarchy / #952 |
| Environment | A Claude Code session on this machine. Workflows run via the Workflow tool by scriptPath. No services need to be running |
| Build flags / config | none |
| Build (commit) | cab1dc12 for the recorded runs below. Scenarios 18 through 21 target 42361c97 or later |
| Tester | Claude Code session, driving on the operator's behalf |
| Date | 2026-08-22 (Pacific) |
Summary โ
| # | Scenario | Result | Notes |
|---|---|---|---|
| 1 | focus is required and refuses without it | [x] pass [ ] fail [ ] blocked | 0 agents spawned, threw in 25ms |
| 2 | args-as-string is named as the cause | [x] pass [ ] fail [ ] blocked | message points at the cause, not at repoRoot |
| 3 | Workflow Sync refuses a file that does not parse | [x] pass [ ] fail [ ] blocked | author-run; generated copy md5-identical after the broken sync |
| 4 | Skill Sync refuses a missing tier | [x] pass [ ] fail [ ] blocked | author-run; both arms, missing and misspelled |
| 5 | ACTIVATION: the new assets reach a fresh session | [ ] pass [ ] fail [x] blocked | deferred, and needs a fresh session |
| 6 | agenda-creation writes today's agenda | [x] pass [ ] fail [ ] blocked | PASS against a scratch dir. The real-board write and its push remain unexercised |
| 7 | The Focus line is her sentence, verbatim | [x] pass [ ] fail [ ] blocked | PASS. Written verbatim, no tidying |
| 8 | The written agenda matches the format skill | [x] pass [ ] fail [ ] blocked | PASS. All three explicit checks, plus the old-format tripwire clean |
| 9 | FALSIFYING PAIR A: a narrow focus cuts most of it | [x] pass [ ] fail [ ] blocked | its own prediction held, but see the pair verdict on scenario 10 |
| 10 | FALSIFYING PAIR B (control): a matching focus cuts little | [ ] pass [x] fail [ ] blocked | FAIL: ranked identical at zero in both arms, and the arms shared only 35 of 69 candidates |
| 11 | FALSIFYING: no phase after Backlog adds rows | [x] pass [ ] fail [ ] blocked | PASS after the fix. Zero ghosts in both arms; uncarried rows went to gaps |
| 12 | The agenda reader greps rather than reading 185KB | [x] pass [ ] fail [ ] blocked | grep plus sed, 22 percent and 13 percent of the two files, quantified unprompted |
| 13 | agenda-status returns an audit when there is no board | [ ] pass [ ] fail [x] blocked | deferred, out of this run's scope |
| 14 | agenda-status Reconcile runs against a real board | [ ] pass [ ] fail [x] blocked | deferred, out of this run's scope |
| 15 | agenda-status Record writes, and the publish gate opens | [ ] pass [ ] fail [x] blocked | deferred, out of this run's scope |
| 16 | A session's claim that disagrees with git is reported | [ ] pass [ ] fail [x] blocked | deferred, out of this run's scope |
| 17 | A reader that throws is ABSENT, not empty | [ ] pass [ ] fail [x] blocked | deferred, out of this run's scope |
| 18 | Every task field is ONE line | [x] pass [ ] fail [ ] blocked | PASS after the fix, and the PREDICTION WAS WRONG. Longest Context 141 chars against a bar of 200 |
| 19 | The handoff prompt is four labelled lines | [x] pass [ ] fail [ ] blocked | PASS on shape AND values. 5 of 5 paths and 4 of 4 issues resolved, not eyeballed |
| 20 | Realtime log entries carry a bold capitalized title | [x] pass [ ] fail [ ] blocked | PASS, clock genuinely read. But the format hardcodes PST and August is PDT. Status arm still blocked |
| 21 | Focus blocks are checkboxes, emojis are the fixed set | [x] pass [ ] fail [ ] blocked | PASS. 7 emoji, all in-table by codepoint, 0 missing and 0 wrong. Zero boxes ticked |
Setup / preconditions โ
- Repo at
/home/mechelle/repos/lantern_app, context home at/home/mechelle/.agents/context/lantern_app. - Both paths are machine-specific. On the dev VM they are
/home/cattreedev/...and every scenario needs them swapped. - Where a run writes:
docs/projects/agent-workflow-hierarchy/runs/<label>/, seeded by copyingfixtures/agenda/*.mdinto it. Recorded results below still name /tmp paths, correctly, because that is where those runs actually wrote. - Scenarios 6 through 16 WRITE.
agenda-creationcreates today's dated agenda file and runscontext:sync, which commits and pushes the whole context home. This is intended, and it is the one irreversible-ish thing here. The safe variant isagendaDirpointed at a scratch directory, which every write scenario names. - Scenarios 3 and 4 deliberately break a file and then restore it. Take the backup the steps name; do not skip it.
- Reach a clean state with
git status --porcelainempty in both the repo and the context home before starting. - Where to verify: the agenda markdown in
<contextHome>/agenda/<YYYY-MM-DD>.md, the workflow journal, and the tool's returned object. - The journal path, corrected. It is
~/.claude/projects/<sanitized>/<sessionUuid>/subagents/workflows/<runId>/journal.jsonl. The session uuid level was missing here, so the documented path resolves to nothing and an empty result reads as "no run launched". That misreading has now happened twice. - Validate any query before believing an empty answer from it. Two queries used to hunt for runs today were broken rather than empty: the path above, and
find -newermt "15 minutes ago", which returns nothing on this machine even for a file written one second earlier. Point the query at something known to exist first. Sorted-printf '%T@ %p'works where-newermtdoes not.
Re-run predictions, written 2026-08-22 before either arm launched โ
The first pair was void (74 candidates against 12) because a reader was silently killed. Three fixes landed afterwards. These predictions are recorded BEFORE the re-run, against 7ca235e4, so they cannot be fitted to the result.
THE GATE, checked before anything else is compared. Both arms must report the same candidateCount. If they differ, the pair is void again and the numbers below mean nothing. Expect roughly 74 in both, since all three readers should now run against an identical restored input.
| Prediction | Arm A (product surfaces) | Arm B (agent tooling) |
|---|---|---|
| candidateCount | same as arm B, roughly 74 | same as arm A, roughly 74 |
| ranked | SHORT, and possibly ZERO now that gaps cannot pad it | LONGER than arm A |
| cut | LONG, roughly 20 | SHORTER than arm A |
| gaps | at most 2 | at most 2 |
| ghosts in ranked | ZERO, or the structural fix failed where prose failed | ZERO |
Named in advance so it cannot be reinterpreted later:
- An EMPTY
rankedin arm A with a fullcutlist is a PASS, not a broken run. The narrow focus legitimately excludes today's agent-tooling backlog, and the gaps channel is no longer available to fill the vacuum. - A ghost in
rankedin either arm is a FAIL, and a worse one than last time, because it would mean a structural fix failed in the same place prose already failed. - The null-reader guard will most likely NOT fire, because the classifier refusal that produced the condition is exactly what the scratch-run suppression removes. If every reader returns, that arm of the guard stays unexercised and this run will say so plainly rather than claiming it works.
- If the two arms produce similar rankings ON EQUAL INPUT, that is the finding: the focus is decorative and the diagnosis was wrong. It will be recorded that way, not read as a pass.
Predictions for scenarios 18 through 21, written before the run โ
Recorded here so they cannot be fitted to the result afterwards.
| Scenario | Prediction | Why |
|---|---|---|
| 18, one-line fields | FAIL, or pass only on the short rows. This is the one I would bet against | Gather is deliberately told to write detail out IN FULL so nobody has to open yesterday's file. The writer is handed a paragraph and asked to shrink it, and shrinking is the step that gets skipped. The previous run produced a Context field of about 1,100 characters |
| 19, four-line prompt | PASS on shape, and the interesting failure is in the VALUES | Four labels in a fence is easy to satisfy. "Start here" pointing at a real path and "Done when" being a check the receiving agent can actually run are the parts that carry the value, and neither is enforced by the shape |
| 20, bold log title | PASS | One line, one shape, and the writer has an explicit example |
| 21, checkboxes and emojis | PASS on checkboxes, UNCERTAIN on emojis | A fixed table is easy to read off. The risk is improvisation: a heading that gets a sensible emoji from outside the table looks correct and defeats the point, which is that two sessions writing the same file agree |
What would tell us the format change was the wrong call. If 18 fails and the fix is to loosen the one-line rule rather than to create the missing project, then the rule was aimed at the wrong thing: the length was a symptom of a task carrying its whole history because it had nowhere else to put it. Record that reading if it is what the run shows, rather than filing it as a writer that misbehaved.
Scenarios โ
1. focus is required and refuses without it โ
- Goal: the run cannot proceed on an inferred focus.
- Preconditions: none.
- Steps:
- Invoke
agenda-creation.jswithrepoRootandcontextHomeonly, nofocus.
- Invoke
- Expected: throws immediately, zero agents spawned, message names
focusand gives an example. - Verify: the error text says there is no default and why. Nothing written to the agenda directory.
Result: [x] pass [ ] fail [ ] blocked
Actual: Threw immediately. `agent_count 0`, `agents_done 0`, duration 25ms, so nothing was
spawned and nothing billed. Run id `wf_d1104e11-522`. Threw at `required()`
(workflow.js:29), reached from the FOCUS constant at workflow.js:63, which is the
right place: after repoRoot and contextHome resolved, before the Rules phase.
Nothing written. `git status --porcelain` empty in both the repo and the context
home afterwards, and the agenda directory still held only 2026-08-19.md,
2026-08-20.md, 2026-08-21.md and agenda.html.
Evidence: (error text, verbatim)
`focus is required. Pass it through the Workflow tool's args, e.g. { focus: 'get the
admin portal signing in end to end' }. There is no default: this script cannot see
the filesystem, and guessing produces an empty report instead of an error.`
Names the argument, gives a runnable example, and states the no-default reason.
Follow-up: none.2. args-as-string is named as the cause โ
- Goal: a large payload that arrives as a string produces a message pointing AT the cause, not away from it.
- Preconditions: none.
- Steps:
- Invoke
agenda-status.jspassingargsas a JSON-encoded string rather than an object (a ~9KBsessionReportspayload is the natural trigger; a deliberately stringified small object also works).
- Invoke
- Expected: throws with "args arrived as a STRING, not an object", and says no argument is actually missing.
- Verify: the message does NOT tell you to pass
repoRoot, which you already passed. That misdirection cost a full run on 2026-08-22.
Result: [x] pass [ ] fail [ ] blocked
Actual: Threw in 15ms with 0 agents spawned. Run id `wf_429f12b6-431`. Threw at
workflow.js:16, which is the type guard at the top of the file, ahead of every
`required()` call. The message names args-as-a-string as the cause, says nothing
below it ran, and states outright that no argument is actually missing. It never
mentions `repoRoot`, which is the misdirection this scenario exists to catch.
Evidence: (error text, verbatim)
`args arrived as a STRING, not an object, so every argument reads as undefined.
This happens on large payloads: sessionReports carrying several verbose reports is
the usual trigger. Pass args as an actual JSON object, not a JSON-encoded string.
Nothing below this line ran, and no argument is actually missing.`
Follow-up: Reproducing the failure took a deliberately double-encoded payload. The Workflow
tool's own `args` parameter parses a JSON object literal into an object, so a
first attempt at this scenario passing the object shape ran the real workflow
instead of throwing (stopped before it wrote anything, context home verified
clean). Worth knowing that the string case is not something a caller falls into
through this tool: it comes from a caller that stringifies the payload itself.
`agenda-creation.js` carries the identical guard at workflow.js:31, unexercised
here but textually the same check.3. Workflow Sync refuses a file that does not parse โ
- Goal: a syntactically broken workflow cannot reach the generated copy.
- Preconditions: clean tree. Back up the file first.
- Steps:
cp .agents/workflows/test-rank.js /tmp/test-rank.bak- Append a deliberate syntax error:
printf '\nconst x =a+ ;\n' >> .agents/workflows/test-rank.js npm run sync:workflowscp /tmp/test-rank.bak .agents/workflows/test-rank.js && npm run sync:workflows
- Expected: step 3 reports
x test-rank.jswith "does not parse" andErrors: 1. Step 4 returns toErrors: 0. - Verify: after step 3,
.claude/workflows/test-rank.jsis UNCHANGED.git diff --stat .claude/workflows/test-rank.jsis empty. That is the whole point: before this check, the broken file synced clean and the drift gate called it up to date.
Result: [x] pass [ ] fail [ ] blocked
Actual: Run by the AUTHOR of the check, 2026-08-22 (weaker evidence than an independent
tester; worth re-running in a later pass). Appended a deliberate syntax error to
test-rank.js and synced. Output: "x test-rank.js / does not parse: Unexpected
token ';'" and Errors: 1. Restored the file, re-synced, Errors: 0.
Evidence: md5 of .claude/workflows/test-rank.js taken before and after the broken sync:
IDENTICAL. The broken file did not reach the generated copy, which is the whole
point. Before this check the broken file synced clean and the drift gate reported
it up to date, because it compares the copy to the source and both were equally
broken. git status clean afterwards in .agents/ and .claude/.
Follow-up: none.4. Skill Sync refuses a missing tier โ
- Goal: the frontmatter gate still bites after today's changes.
- Preconditions: clean tree. Back up the file first.
- Steps:
- Back up
.agents/skills/agenda-documentation/skill.md. - Delete its
tier:line. node tooling/scripts/lint.sync-skills.js- Restore, re-run.
- Back up
- Expected: step 3 fails naming the missing tier; step 4 reports "Skills are in sync."
- Verify: repeat with a misspelled tier (
tier: refrence). It must also fail, not pass through.
Result: [x] pass [ ] fail [ ] blocked
Actual: Run by the author, 2026-08-22, both arms. 4a, tier line deleted from
agenda-documentation: "missing `tier:`", 1 problem. 4b, tier misspelled as
"refrence": 'tier "refrence" is not one of orchestrator, procedure, discipline,
reference', 1 problem. Restored: "Skills are in sync."
Evidence: The misspelling arm is the one that matters. A gate checking only for presence
would pass "refrence" and the skill would carry a tier no consumer recognises.
It names the four valid values in the error, so the fix is obvious from the
message alone. git status clean in .agents/ and .claude/ afterwards.
Follow-up: none.5. ACTIVATION: the new assets reach a fresh session โ
- Goal: prove the shipped default is on, not that the files exist. Files existing in
.agents/proves nothing; the generated copies are what agents read. - Preconditions: a brand new session, no manual setup, nothing selected by hand.
- Steps:
- Open a fresh Claude Code session in the repo.
- Ask it, without hinting at paths: "what skills do you have for the agenda?"
- Ask it to list the available workflows.
- Expected: it names
agenda-creation,agenda-statusandagenda-documentationfrom its own loaded list, having read nothing. - Verify:
.claude/skills/agenda-creation/SKILL.md,.github/skills/agenda-creation/SKILL.mdand.gemini/skills/agenda-creation/skill.mdall exist and carry the sync banner. Confirm noday-plandirectory survives in any of the three.
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Also needs a brand new session, which this one is not.
Evidence:
Follow-up: Carry to the next pass.6. agenda-creation writes today's agenda โ
- Goal: the happy path, end to end.
- Preconditions: no agenda file for today, or use a scratch
agendaDir. - Steps:
- Invoke with
repoRoot,contextHome, andfocusset to a real one-sentence focus. - Watch
/workflowsfor the phase order.
- Invoke with
- Expected: phases run Gather, Rank, Write. A file appears at
<agendaDir>/<YYYY-MM-DD>.md. - Note on the recorded result below: it was taken against the five-phase version (Rules, Backlog, Check, Rank, Record) and is left verbatim. A run reporting those five phases NOW is a FAIL, not a match: it means the caller ran a stale copy of the script.
- Verify: exactly ONE file created, named for today's Pacific date. No
agenda.md. No other day's file touched.context:syncreports pushed.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and it is the FIRST TIME this workflow has ever written a file. Run wf_49d4fa52-069
against agendaDir /tmp/agenda-falsify, after the scratch-run fix.
Phase order ran Rules, Backlog, Check, Rank, Record, in that order, in both re-run arms.
Verify, clause by clause:
- Exactly ONE file created: yes. `/tmp/agenda-falsify/2026-08-22.md`, 80,276 bytes, created
fresh. The record agent confirmed no 2026-08-22.md existed, so a create and not an append.
- Named for today's Pacific date: yes. The clock was read with
`TZ=America/Los_Angeles date`, reported 12:35 PDT, not estimated.
- No agenda.md: confirmed, none created.
- No other day's file touched: confirmed, 2026-08-19/20/21 mtimes unchanged at 12:12:45.
- "context:sync reports pushed": NOT APPLICABLE and correctly so. This was a scratch run, so
the sync was deliberately suppressed. Recorded as inapplicable rather than as a pass.
SCOPE OF THIS PASS, stated so it is not over-read. The operator chose "leave your real board
alone", so the happy path against her real agenda directory was never run. What is proven is
that the workflow writes a correct, correctly-named, single file. What is NOT proven is the
real-board write, the context:sync push, and the "sync reports pushed" clause above.
Evidence: (file path, run id) /tmp/agenda-falsify/2026-08-22.md, run wf_49d4fa52-069. Preserved at
/tmp/armA-agenda.md and /tmp/armA-agenda.html, since the pristine restore for arm B removed
the originals. Real context home verified untouched after BOTH arms: clean `git status`,
still on bdedf95, still only three dated files.
Follow-up: The real-board arm is the remaining half and it is hers to authorize. Everything mechanical
about it is now proven; what is untested is only the sync and push at the end.Second result, re-run of 2026-08-22 evening against 0739b0a6, run wf_f80b57fb-b43. The earlier block above belongs to wf_49d4fa52-069 and is left verbatim.
Result: [ ] pass [x] fail [ ] blocked
Actual: FAIL, and it is a regression against the block above, not a flake. NO file was written.
Verify, clause by clause:
- Exactly ONE file created: NO. ZERO files created. /tmp/agenda-fmt still holds only the
three fixture days, byte-identical to the copies made before the run (md5 5c34a543...,
6dbb7c42..., f6839c7d..., unchanged before and after).
- Named for today's Pacific date: not applicable, nothing was named.
- No agenda.md: vacuously true, nothing was created at all.
- No other day's file touched: yes, and this is the one clause that passes. All three
fixture mtimes still 15:11.
- "context:sync reports pushed": not applicable, scratch run, correctly suppressed. The
run logged "SCRATCH RUN: agendaDir is /tmp/agenda-fmt; nothing will be synced or pushed".
PHASE ORDER IS CORRECT and is worth recording separately from the failure: Gather, Rank,
Write, in that order, three phases and not the deleted five. So the stale-script tripwire
in this scenario's own note is CLEAN. The caller ran the current script; the current
script is broken.
THE CAUSE, pinned to one character. The Write agent received the literal prompt `true`.
Its journal entry reads promptPreview "true", 0 tool calls, 8.0s, 43,509 tokens spent
returning "No task was provided". Line 330 of .agents/workflows/agenda-creation.js writes
the phrase "the detail after a `>`" using BACKTICKS inside the Write prompt's own
backtick template literal. The first of those backticks CLOSES the template early, and
what follows parses as a chain of `>` string comparisons, so the whole prompt argument
evaluates to the boolean `true` and `agent(true, ...)` is what actually runs.
Reproduced in isolation, not inferred: lines 306 to 343 extracted verbatim, evaluated with
stub bindings, and the prompt came back `typeof "boolean"`, value `true`. Backtick census
of the template, lines 308 to 343: line 309 has 1, line 330 has 2, line 335 has 1,
line 337 has 1, line 341 has 1. Seven in total where a well-formed template needs an even
count with none loose in the prose.
The irony is worth writing down: the exact line that breaks the Write phase is the line
that TEACHES the Realtime Log format, which is scenario 20. The format instruction killed
the writer that was supposed to follow it.
This bug is committed at HEAD `0739b0a6`, not a local edit. `git status --porcelain
.agents/workflows/` is empty and `git diff` on the file is empty.
Evidence: Run wf_f80b57fb-b43. Journal at .../subagents/workflows/wf_f80b57fb-b43/journal.jsonl,
4 result rows, the fourth being the write agent's refusal string. Full returned object at
tasks/wev0ut84c.output. Broken line: `.agents/workflows/agenda-creation.js:330`.
Follow-up: One-character class of fix: swap the backticks around `>` on line 330 for quotes, or
escape them. NOT applied in this run, because editing the script under test changes the
artifact being measured and it is her branch and her open PR. Scenarios 7, 8, 18, 19, 20
and 21 all depend on a written file and are all blocked behind this one line.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and it is the first end to end pass this scenario has ever had on the three-phase
script. The backtick fix landed as `fbfba81d` (author: a parallel session; verified in this
tree before spending a run, prompt now `typeof "string"`, and sync-workflows --check clean).
Phases ran Gather, Rank, Write. THREE phases, not the deleted five, so this scenario's own
stale-script tripwire is clean. 4 agents, 0 errors, 223,179 subagent tokens, 7m09s.
Verify, clause by clause:
- Exactly ONE file created: YES. `/tmp/agenda-fmt/2026-08-22.md`, 8,022 bytes, 94 lines.
The write agent checked for it first, so a create and not an append.
- Named for today's Pacific date: YES, and the clock was genuinely read. The journal shows
the tool call `TZ=America/Los_Angeles date '+%Y-%m-%d %H:%M'` actually EXECUTED, returning
16:08. Checked independently three minutes later: 16:11 Pacific. Not estimated.
- No agenda.md: confirmed, none created.
- No other day's file touched: confirmed by md5. All three fixture days are byte-identical
to the committed fixtures at docs/projects/agent-workflow-hierarchy/fixtures/agenda/.
- "context:sync reports pushed": correctly INAPPLICABLE. Scratch run, suppressed, logged.
SCOPE, unchanged from the first block: the real-board write and its sync and push remain
unexercised, deliberately.
The file is 8,022 bytes against the 80,276 the pre-restructure version produced for the
same job. That is a tenth the size, which is the one-line-fields rule doing what it was
written to do (see scenario 18).
Evidence: /tmp/agenda-fmt/2026-08-22.md, run wf_fe55300a-565.
Follow-up: None on the mechanics. The real-board arm is still hers to authorize.7. The Focus line is her sentence, verbatim โ
- Goal: the filter that governed the ranking is recorded unaltered.
- Preconditions: scenario 6 has run.
- Steps:
- Open the written agenda's header block.
- Compare the
Focus:line character by character against the sentence passed in.
- Expected: identical. Not tidied, not shortened, not made more specific, not turned into a heading.
- Verify: a paraphrase is a FAIL, not a nit. A paraphrase of the filter is a different filter, and the ranking was done against the original.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS. The written agenda's header line 5 reads, exactly:
- Focus: get the app, the admin portal and the merchant portal working end to end
Compared character by character against the string passed in as the focus arg. Identical.
Not tidied, not shortened, not made more specific, not turned into a heading. The return
object also echoes it back unchanged, so it survives all five phases intact.
CAVEAT, and it matters for how much this pass is worth. The focus exercised here is scenario
9's fixed test sentence, not the operator's own. Her sentence, "Today's focus is to smooth
out the agent workflow so we can comfortably utilize it to get normal work back on track.",
was never written to a file, because the run that would have used it was the real-board run
she chose not to make. Her sentence is the harder case: it begins "Today's focus is to", so
a correct header reads "Focus: Today's focus is to smooth out...", which reads redundantly
and is CORRECT. A header trimmed to "smooth out the agent workflow..." is a FAIL, because a
tidied filter is a different filter from the one the ranking ran against. The verbatim
mechanism is proven; the temptation-to-tidy case specifically is not.
Evidence: /tmp/armA-agenda.md line 5. Run wf_49d4fa52-069.
Follow-up: Re-check this clause on the first run that uses her actual sentence.Second result, re-run wf_f80b57fb-b43. The earlier block above is left verbatim.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED. There is no written agenda to open, so the header block this scenario compares
cannot be inspected. See scenario 6 for the single cause.
What CAN be said, and it is deliberately not scored as a pass, because this scenario is
about the FILE and not about the plumbing: the focus string survived every phase intact.
It was echoed into the run log as "focus: get the app, the admin portal and the merchant
portal working end to end", reached the Rank agent's prompt verbatim under "HER FOCUS FOR
TODAY, in her own words", and came back on the returned object's `focus` key character for
character identical to the string passed in. Zero drift across Gather, Rank and Write.
The clause this scenario actually owns, whether the WRITER tidies her sentence on its way
into the header line, is untested for a third run running. The writer never executed.
The caveat recorded in the block above still stands unchanged: this focus is the fixed
test sentence, not her own "Today's focus is to" phrasing, which is the harder case.
Evidence: Run wf_f80b57fb-b43 returned object, `focus` key. No agenda file exists to cite.
Follow-up: Same as scenario 6. Unblocks the moment line 330 is fixed and a run writes a file.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS. Header line 5 of the written file reads, exactly:
- Focus: get the app, the admin portal and the merchant portal working end to end
Compared character by character against the string passed in as `focus`. Identical. Not
tidied, not shortened, not made more specific, not turned into a heading, and not wrapped
in quotes. The returned object echoes it back unchanged as well, so it is intact at both
ends of all three phases.
The focus also demonstrably drove the output rather than just riding along: all three
ranked rows are local-environment blockers on the admin and merchant portals, and the cut
reasons argue explicitly against the focus ("if the app, admin portal and merchant portal
were all working end to end tomorrow, this would still be dropping the second delivery").
THE CAVEAT FROM THE FIRST BLOCK STILL STANDS, and should not be read as closed by this
pass. The sentence exercised here is the fixed test focus. Her own phrasing, which starts
"Today's focus is to", is the harder case, because a correct header reads redundantly and
a tidied one is a different filter. Still untested on a third run.
Evidence: /tmp/agenda-fmt/2026-08-22.md line 5. Run wf_fe55300a-565.
Follow-up: Unchanged: re-check on the first run that uses her actual sentence.8. The written agenda matches the format skill โ
- Goal: the writer writes the current format, not a remembered one.
- Preconditions: scenario 6 has run.
- Steps:
- Open
.agents/skills/agenda-documentation/skill.md, "Agenda Creation" state. - Compare section by section against the written file.
- Open
- Expected: every section present, none invented, none from the old numbered format.
- Verify, all three explicitly: there is no top-level "Need from you" table; every session block carries a
Needs from youline withnonewritten where there is nothing; Parked rows each say what would unpark them. Section 0 through 5 headings appearing anywhere is a FAIL: that is a deleted format, and its reappearance means the writer worked from memory. Scenarios 18 through 21 cover the parts of the format added since this scenario was written; this one still owns the asks, the focus and Parked.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS on all three explicit checks and on the tripwire.
- NO top-level "Need from you" table: confirmed, zero such headings in the file.
- Every session block carries a "Needs from you" line: confirmed, 10 occurrences across 10
session blocks, which is one per inProgress row. The record agent reported that 4 blocks
carry real asks and "the other 6 blocks say `none`", which is the rule about writing
`none` rather than leaving it blank, obeyed.
- Parked rows say what would unpark them: confirmed. Parked is a table with an explicit
"What unparks it" column, populated on every row, and several say plainly "Her word only"
with the reason (a rules change on a privacy surface, a build-config change a PM handing
out is already on record as an error).
TRIPWIRE CLEAN: zero section 0 through 5 headings anywhere in the file. The headings written
are Key Focus Blocks, Realtime Log and Activity Blockers, Focus Tasks and Session Prompts,
ten Session blocks, Gaps, Collisions and Sequencing, Parked, Cut today, and What the survey
did NOT see. That is the current format, not the one deleted on 2026-08-22, so the writer
worked from the skill rather than from memory. Worth noting because the risk was real: the
yesterday-board reader's own coverage note describes the 2026-08-21 file in old numbered-
format vocabulary, so old-format wording WAS present in the ranker's inputs and the writer
did not reproduce it.
Also correct, and not required by this scenario: the Gaps channel got its own section, kept
visually separate from the task list with a lead line saying nobody is carrying those rows.
Evidence: /tmp/armA-agenda.md, compared against .agents/skills/agenda-documentation/skill.md, the
"Agenda Creation" state. Run wf_49d4fa52-069.
Follow-up: None.Second result, re-run wf_f80b57fb-b43. The earlier block above is left verbatim.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED. No file was written, so there is nothing to compare against the format skill.
See scenario 6 for the cause.
None of the three explicit checks could be run: no top-level "Need from you" table,
a "Needs from you" line on every session block, and Parked rows naming what unparks them.
The section 0 through 5 tripwire is also unexercised, which matters, because a tripwire
that never fires is not the same as a tripwire that passed.
One structural observation from the RANK output, which does not score this scenario but
does bear on it: the ask channel returned 2 rows and each names the task it sits on via an
`onTask` field ("Decide the retention window (parked)" and "Fix the webhook retry loop
(cut from today)"). So the data handed to the writer is shaped for asks-on-their-block and
not for a top-level table. Whether the writer would have honoured that shape is exactly
what went unmeasured.
Evidence: Run wf_f80b57fb-b43. No agenda file at /tmp/agenda-fmt/2026-08-22.md.
Follow-up: Same as scenario 6.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS on all three explicit checks and on the tripwire, with one format tension worth
recording that this scenario does not currently score.
- NO top-level "Need from you" table: CONFIRMED. Zero such headings.
- Every session block carries a "Needs from you" line: CONFIRMED. Three blocks, three
lines, all reading exactly `- Needs from you: none`. The word is written rather than the
line left blank, which is the rule.
- Parked rows say what would unpark them: CONFIRMED. Four rows, all three columns
populated, and the geocoder row is flagged in its own cell as `FLAG: CONDITION ALREADY
FIRED` with the note that it is not independently verified beyond the agenda's wording.
TRIPWIRE CLEAN: zero section 0 through 5 headings. Sections written are the day header,
Key Focus Blocks, Realtime Log and Activity Blockers, Focus Tasks and Session Prompts,
three Session blocks, User Question Log, Parked, Cut today, Gaps. That is the current
format.
WORTH RECORDING SEPARATELY, because it is the strongest single piece of evidence in this
run that the writer read the skill rather than its neighbours: yesterday's fixture
(2026-08-21.md) is in the OLD format, with `Required Skills`, `Context / Content`, `Link to
project`, a five-line prompt and no emojis, and the writer NAMED that in its report and
followed the skill anyway. The old format was sitting in its input and did not leak.
THE TENSION, and it is a format question rather than a writer defect. All three asks went
to the User Question Log and every session block says `none`. The skill says an ask lives
on its task block and "that line is the only place an ask lives in this document"; it also
says every question asked of her is logged, without exception. All three of today's asks
attach to tasks that are PARKED or CUT, so no session block exists to carry them. The
writer routed them to the log and said why. That looks correct, but the two rules do not
currently say what happens to an ask on a task that is not on today's list.
Evidence: /tmp/agenda-fmt/2026-08-22.md, lines 22, 36, 50 (Needs from you), 60-67 (question log),
68-77 (Parked). Compared against .agents/skills/agenda-documentation/skill.md.
Follow-up: Decide where an ask on a parked or cut task lives, and write it into the format skill. The
current wording makes the log and the block rule collide whenever the ask outlives the
task's place on the day.9. FALSIFYING PAIR A: a narrow focus cuts most of it โ
This scenario and scenario 10 are the pair that can tell us the whole restructure is decorative. State both predictions out loud before running either.
Goal: show the focus actually filters, by choosing one that should exclude nearly all current work.
Preconditions: a scratch agenda directory that is a COPY of the real one, not an empty one:
cp -r ~/.agents/context/lantern_app/agenda /tmp/agenda-falsifyBoth runs then read identical carried work and write somewhere disposable. An EMPTY scratch dir would give the Gather readers nothing to carry, both runs would rank nothing, and the pair would agree by being vacuous. That is the defect this pair exists to avoid, so it must not be the way the pair is set up.
Prediction: the ranked list is SHORT and the
cutchannel is LONG, because today's carried work is agent tooling and the focus is product surfaces.Steps:
- Invoke with
focus= "get the app, the admin portal and the merchant portal working end to end". - Record the count of
rankedand the count ofcutfrom the returned object. - Read three cut reasons and judge whether each is fair against that focus.
- Invoke with
Expected: most agent-tooling candidates land in
cut, each with a reason naming the focus.Verify: a cut item is REPORTED, not missing. Anything cut appears in the agenda under Parked with its reason.
Result: [x] pass [ ] fail [ ] blocked
Actual: ranked count = 0 cut count = 15 candidates = 69
RE-RUN 2026-08-22 against `7ca235e4`, run wf_49d4fa52-069, after the three fixes. All 8
agents returned, 0 errors. gaps 2, inProgress 10, operatorAsks 16, parked 28.
Its own prediction was "ranked SHORT and cut LONG", with an empty ranked list named in
advance as a PASS rather than a broken run. That is exactly what happened, and the record
agent did not paper over it: the written agenda says the ranked list is empty and that the
day's blocks are therefore cut from in-progress work rather than from a ranking.
Three cut reasons were read and judged fair against this focus: idle worktrees on merged
branches, squash-residue branches that cannot carry new work, and the tree being level with
origin/dev. All three are repo hygiene, none moves the three surfaces, and each cut reason
names the focus. Cut rows are reported, not missing: all 15 appear in the written agenda
under Parked in a "Cut today" list.
THE GATE PASSED AND THE GATE IS NOT ENOUGH. Both arms reported candidateCount 69, which
is the condition the re-run was gated on. Comparing the candidate TITLES rather than the
counts, the two arms shared only 35 of 69. Jaccard 0.34. Half the candidate list differs
between the arms, on byte-identical input files, because a fresh set of reader agents runs
each time and they do not return the same rows. Some of the difference is paraphrase of the
same fact ("Worktree pm-session has been idle 12 days on a merged branch" against
"pm-session worktree is squash residue and cannot carry new work"). Some is genuinely
different content: arm B carried "Restructure planning into per-project folders" and "The
Claude effort setting resets to high in the app", which arm A never saw at all.
So candidateCount is a COUNT gate on a CONTENT problem, and it passed by coincidence. Any
channel-membership comparison across the two arms is invalid, including the zero-overlap in
`cut` (15 rows against 10, no shared titles) which looks like dramatic evidence of the
focus filtering and is mostly the readers rewording the same repo observations.
THE RANKED LISTS ARE IDENTICAL. Both arms returned ZERO ranked rows. Recording that plainly
as the plan instructs, rather than finding a reading that makes it a pass: the pair does NOT
demonstrate that the focus drives the ranking, because there was no ranking either time.
Arm B's prediction was "ranked LONGER than arm A". It was not longer. It was equal, at zero.
WHY RANKED IS EMPTY, and this is the useful part. Both rank agents said so deliberately and
in almost the same words. Arm A: "THE RANKED LIST IS EMPTY, DELIBERATELY. Nothing on the
carried list is startable work that serves the focus. Every carried row is one of: an
observation about the tree (cut)..." Every one of the 69 candidates routes to a channel
that is not `ranked`: owned work goes to inProgress, questions go to operatorAsks, deferred
work goes to parked, tree observations go to cut. The rank prompt's own rule 3 forbids
ranking any row whose owner is recorded, and on this board almost everything has an owner.
`ranked` is therefore close to structurally empty regardless of what the focus says, which
is a property of the routing rules, not of the focus.
WHAT THE FOCUS DEMONSTRABLY DOES REACH: the cut reasons argue against the focus given (arm
A's cite the three surfaces, arm B's cite agent tooling), and the gaps channel is
focus-shaped, 2 rows in arm A both about the product surfaces, 0 in arm B. That is real and
it is not nothing. It is also not what this pair was built to measure.
VERDICT ON THE PAIR: it still cannot answer its question, now for a second and different
reason. First time the arms had unequal inputs because a reader was silently killed. This
time the arms had unequal inputs because the readers are non-deterministic. The fix is not
another gate on the count: the candidate list has to be captured ONCE and replayed to both
arms, so the only variable between them is the focus string. Until that happens this pair
cannot falsify anything.
Evidence: Run wf_49d4fa52-069. Counts from the returned object. Written agenda preserved at
/tmp/armA-agenda.md (80,276 bytes) since the pristine restore for arm B deleted the original.
Follow-up: Marked pass because every expectation this scenario states was met. The pair-level verdict
is on scenario 10 and it is not a pass. Do not read this row as the pair working.10. FALSIFYING PAIR B (control): a matching focus cuts little โ
- Goal: the control. Our account of the cause says the focus is what drives the filtering, so a focus that MATCHES the work should behave oppositely.
- Preconditions: the SAME
/tmp/agenda-falsifycopy, same day, same carried work. Do not re-copy between the two runs: if the inputs differ, the comparison means nothing. - Prediction: the ranked list is LONGER and the
cutchannel SHORTER than in scenario 9, on the same input. - Steps:
- Invoke with
focus= "the agent workflow hierarchy: skills, workflows, and the agenda". - Record
rankedandcutcounts. - Compare against scenario 9.
- Invoke with
- Expected: the two runs disagree substantially about what belongs.
- What a surprising result means, and this is the point of the pair: if 9 and 10 produce similar rankings, the focus input is decorative and the diagnosis is wrong. "It ranked the wrong things because it had no focus input" would then be an incomplete explanation, the cause lies elsewhere (most likely in the candidate sourcing), and this reopens. Do not record that as a pass on a technicality.
Result: [ ] pass [x] fail [ ] blocked
Actual: ranked count = 0 cut count = 10 candidates = 69 vs scenario 9: RANKED IDENTICAL
RE-RUN 2026-08-22 against `7ca235e4`, run wf_474a8577-dec, on a pristine-restored input.
All 8 agents returned, 0 errors. gaps 0, inProgress 15, operatorAsks 14, parked 30.
The prediction was "the ranked list is LONGER and the cut channel SHORTER than in scenario
9, on the same input". Cut was shorter (10 against 15). Ranked was NOT longer: it was equal,
at zero. The plan's own words for this outcome are "if 9 and 10 produce similar rankings,
the focus input is decorative and the diagnosis is wrong... Do not record that as a pass on
a technicality." Identical is stronger than similar, so this is recorded as a fail.
The honest qualification, which cuts BOTH ways and is why the diagnosis is not simply
"decorative": the focus visibly reaches the cut reasons and the gaps channel, so it is not
inert. But the ranked channel, which is the thing the pair measures and the thing her day
is built from, was unaffected by a complete reversal of the focus.
THE GATE PASSED AND THE GATE IS NOT ENOUGH. Both arms reported candidateCount 69, which
is the condition the re-run was gated on. Comparing the candidate TITLES rather than the
counts, the two arms shared only 35 of 69. Jaccard 0.34. Half the candidate list differs
between the arms, on byte-identical input files, because a fresh set of reader agents runs
each time and they do not return the same rows. Some of the difference is paraphrase of the
same fact ("Worktree pm-session has been idle 12 days on a merged branch" against
"pm-session worktree is squash residue and cannot carry new work"). Some is genuinely
different content: arm B carried "Restructure planning into per-project folders" and "The
Claude effort setting resets to high in the app", which arm A never saw at all.
So candidateCount is a COUNT gate on a CONTENT problem, and it passed by coincidence. Any
channel-membership comparison across the two arms is invalid, including the zero-overlap in
`cut` (15 rows against 10, no shared titles) which looks like dramatic evidence of the
focus filtering and is mostly the readers rewording the same repo observations.
THE RANKED LISTS ARE IDENTICAL. Both arms returned ZERO ranked rows. Recording that plainly
as the plan instructs, rather than finding a reading that makes it a pass: the pair does NOT
demonstrate that the focus drives the ranking, because there was no ranking either time.
Arm B's prediction was "ranked LONGER than arm A". It was not longer. It was equal, at zero.
WHY RANKED IS EMPTY, and this is the useful part. Both rank agents said so deliberately and
in almost the same words. Arm A: "THE RANKED LIST IS EMPTY, DELIBERATELY. Nothing on the
carried list is startable work that serves the focus. Every carried row is one of: an
observation about the tree (cut)..." Every one of the 69 candidates routes to a channel
that is not `ranked`: owned work goes to inProgress, questions go to operatorAsks, deferred
work goes to parked, tree observations go to cut. The rank prompt's own rule 3 forbids
ranking any row whose owner is recorded, and on this board almost everything has an owner.
`ranked` is therefore close to structurally empty regardless of what the focus says, which
is a property of the routing rules, not of the focus.
WHAT THE FOCUS DEMONSTRABLY DOES REACH: the cut reasons argue against the focus given (arm
A's cite the three surfaces, arm B's cite agent tooling), and the gaps channel is
focus-shaped, 2 rows in arm A both about the product surfaces, 0 in arm B. That is real and
it is not nothing. It is also not what this pair was built to measure.
VERDICT ON THE PAIR: it still cannot answer its question, now for a second and different
reason. First time the arms had unequal inputs because a reader was silently killed. This
time the arms had unequal inputs because the readers are non-deterministic. The fix is not
another gate on the count: the candidate list has to be captured ONCE and replayed to both
arms, so the only variable between them is the focus string. Until that happens this pair
cannot falsify anything.
Evidence: Run wf_474a8577-dec. Candidate-title overlap computed from both runs' journals: 35 shared
of 69 each, 34 unique to each arm.
Follow-up: THIS REOPENS. Two things are needed before the pair can be run a third time, and the second
is the load-bearing one. (1) The candidate list must be captured once and replayed to both
arms, so the focus string is the only variable; re-running the readers per arm makes them
the dominant variable instead. (2) Someone has to decide whether an always-empty `ranked`
is correct behaviour. If almost every carried row has an owner and rule 3 forbids ranking
owned rows, then `ranked` is near-permanently empty and the pair is measuring a channel
that cannot move. That is a design question about the routing rules, not a bug, and it
needs answering before another run is paid for.11. FALSIFYING: no phase after Gather adds rows โ
Retitled 2026-08-22, and the retitle is itself a finding. This scenario was "the Check phase adds no rows" until the run below showed the Check phase is innocent: it returned exactly one annotation per candidate and invented nothing, on both arms. The rows were invented one phase later, by the RANK agent, on near-identical wording. Aimed at Check alone this scenario would PASS while the constraint it protects FAILS. It now covers every phase downstream of the candidate-gathering step. Do not re-aim it at the innocent phase.
The phase it names was renamed. Backlog and Check were folded into Gather, which runs two readers rather than three. The recorded result below says "three Backlog readers" and is left verbatim. The constraint is unchanged: nothing downstream of Gather may add a row.
- Goal: the filter constraint is obeyed, not merely stated. Prose telling an agent not to do something is exactly what failed before.
- Preconditions: any completed run.
- Prediction: every title in
ranked,asks,parkedandcuttraces back to a row a Gather reader returned. Onlygapsmay hold a row no reader carried, and it is capped at two. - Steps:
- From the run journal, list every title returned by the Gather readers.
- List every title in the ranked, asks, cut and parked channels.
- Diff them.
- Expected: the second set is a subset of the first. Zero rows appear that no reader carried.
- Verify: the launch plan has hundreds of rows and the tracker 177 open issues. A single row appearing from either, however sensible it looks, is a FAIL and means the constraint did not bind. Note it by name.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, in BOTH arms, and this is the one unambiguous win of the day. The structural fix bound
where the prose constraint did not.
Diffed every output channel against the titles the three Backlog readers actually carried:
arm A (69 carried) arm B (69 carried)
ranked 0 rows, 0 uncarried 0 rows, 0 uncarried
inProgress 10 rows, 0 uncarried 15 rows, 0 uncarried
parked 28 rows, 0 uncarried 30 rows, 0 uncarried
cut 15 rows, 0 uncarried 10 rows, 0 uncarried
gaps 2 rows, 2 uncarried 0 rows
Zero rows appeared from nowhere in any channel that is supposed to carry only carried work.
The two uncarried rows in arm A are in `gaps`, which is the channel built for exactly them,
within its cap of 2, and both are genuine focus-shaped observations rather than launch-plan
mining: "Exercise the app, the admin portal and the merchant portal together and write down
where each one breaks" and "Local admin and merchant portal login is impossible, so all
portal work is forced onto deployed dev (#819)". Arm B produced no gaps at all, which is
also allowed.
Compare with the pre-fix run, where 2 of 2 ranked rows were launch-plan mining and the rank
agent cited an exception that did not exist in its prompt. The difference is that the rows
now have somewhere legitimate to go, so the agent no longer has to choose between suppressing
a real observation and putting it somewhere wrong. That is worth recording as a general
lesson: the previous failure was a ROUTING failure wearing the costume of a discipline
failure, and giving the honest output a correct destination fixed it where forbidding it did
not.
Evidence: Runs wf_49d4fa52-069 and wf_474a8577-dec, diffed against their own journals' reader rows.
Follow-up: None on this scenario. Note that it now passes on a workflow that also returns an empty
ranked list in both arms, so a future reader should not take this pass as evidence the
ranking works. It only shows nothing is invented.Second result, re-run wf_f80b57fb-b43, and it CONFIRMS the pass above on a different focus and a different candidate set. The earlier block is left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS. This is the one scenario in this run that is fully measurable without a written
file, because it reads the returned channels rather than the agenda, and it passes.
Gather returned 15 candidates from two readers (carry 8, plan 7). Every title in every
channel was diffed against those 15:
rows uncarried
ranked 4 0
asks 2 0
parked 4 0
cut 6 0
gaps 1 1
Zero rows appeared from nowhere in any channel that may carry only gathered work. The
single uncarried row sits in `gaps`, which is the channel built for exactly that, and it
is 1 against a cap of 2: "An actual end to end pass across the app, the admin portal and
the merchant portal". That is focus-shaped rather than launch-plan mining, which is the
distinction that mattered in the original failure.
The two `asks` rows are not new work. Both point back at gathered rows through their
`onTask` field, one at the parked retention decision and one at the cut webhook row.
Accounting closes exactly: 4 ranked plus 4 parked plus 6 cut is 14, and the fifteenth
gathered row ("Decide whether a dropped webhook event is replayed from the log or accepted
as lost") was routed into `asks` rather than dropped. Nothing gathered went missing and
nothing ungathered appeared.
WORTH RECORDING AGAINST THE PREVIOUS RUN'S CAVEAT. The follow-up on the block above warns
that its pass came on a run whose `ranked` was EMPTY in both arms, so it only showed that
nothing was invented. This run ranked FOUR rows and still invented nothing, so the
constraint now has a real ranking under it. The rank agent also placed in-progress work in
`ranked` (rank 3, "Credential and identity colocation", status "In progress"), which is the
behaviour the owner rule used to suppress.
Evidence: Run wf_f80b57fb-b43, journal.jsonl diffed against its own two reader result rows.
Extracted candidate and channel sets preserved at /tmp/agenda-fmt-analysis.json.
Follow-up: None.Third result, run wf_fe55300a-565, against fbfba81d. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, a third consecutive time, and now on a run that also WROTE the file.
15 candidates gathered from two readers. Every title in every channel diffed against them:
rows uncarried
ranked 3 0
asks 3 0
parked 4 0
cut 7 0
gaps 1 1
17 rows across the four channels that may carry only gathered work, zero uncarried. The one
uncarried row is in `gaps`, within its cap of 2: "An actual end-to-end pass across the
three surfaces, recording what breaks". Focus-shaped, not launch-plan mining, and the same
observation the previous run's gap made, which is itself a small consistency signal.
Accounting closes: 3 ranked plus 4 parked plus 7 cut is 14, and the fifteenth gathered row
went to asks. Nothing gathered was dropped, nothing ungathered appeared.
NEW HERE, and it strengthens the pass: the written FILE was diffed too, not only the return
object. Every session block, every Parked row and every Cut row in the markdown traces to a
gathered candidate. The invention risk survives into the writer and it did not fire there
either.
Evidence: Run wf_fe55300a-565, journal.jsonl diffed against its own reader rows, and
/tmp/agenda-fmt/2026-08-22.md diffed against the same set.
Follow-up: None. The caveat on the second block is now retired: this pass came with a non-empty
`ranked` AND a written file, so it is no longer only evidence that nothing is invented.12. The agenda reader greps rather than reading 185KB โ
- Goal: the cost fix actually happened.
- Preconditions: a completed run.
- Steps:
- Read the
yesterday-boardreader's coverage note in the journal. - Read its tool calls.
- Read the
- Expected: it names which sections it extracted and roughly how much of each file it read.
- Verify: it used grep or sed against the sections, not a whole-file read. A coverage note that does not say is itself a finding: the instruction asks for it explicitly.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and the coverage note is better than the bar the scenario sets. The yesterday-board
reader in run wf_f1b91c9c-e29 named every section it extracted AND quantified how much of
each file it read, without being asked for a percentage.
2026-08-21.md (189.7KB, 1893 lines): two structural passes first, a heading grep and a
keyword grep for parked / close-of-day / EOD / Focus markers, then three targeted sed
extracts (lines 1 to 70, 1705 to 1801, 1857 to 1893). Total 41.4KB of 189.7KB, about 22
percent, 204 of 1893 lines. It then listed what it did NOT read, by section and line
range, and flagged the few facts it picked up from keyword grep line hits rather than
from reading the section.
2026-08-20.md (70KB, 380 lines): same two grep passes, then sed on lines 9 to 55 and 361
to 380. Total 8.9KB of 70KB, about 13 percent.
2026-08-19.md was not read, stated as out of scope rather than left silent.
No whole-file read of either file. The cost fix is real.
Evidence: Journal for wf_f1b91c9c-e29, the yesterday-board reader's coverage field. It also
correctly reported that no today file existed in the scratch directory, and said what it
did about it: it treated 2026-08-21.md as the previous day's file and additionally pulled
its unfinished task-list rows, so mid-flight work on four branches would not vanish.
Follow-up: One side effect worth naming, not a fault of this scenario. The reader runs
`npm run context:sync` against the REAL repo and context home even when agendaDir points
at a scratch directory, and it reported "pulled, nothing to commit, pushed, done". It was
a no-op push here, so nothing was harmed. It does mean agendaDir does not fully isolate a
run from the real context home, which is worth knowing before anyone treats a scratch
agendaDir as a dry run.13. agenda-status returns an audit when there is no board โ
- Goal: a boardless day stops cheaply and says what it is.
- Preconditions: a day with NO agenda file (use a scratch empty
agendaDir). - Steps:
- Invoke
agenda-status.jswithtrigger: pre-eod.
- Invoke
- Expected: returns
verdict: NO_BOARD_TO_RECONCILEafter the Claimed phase, before the observer fan-out. - Verify, all four:
readersOkis empty (the fan-out did not run);callerMustPublishisfalse;publishNoteis PRESENT and says DO NOT PUBLISH;callerMustDispatchandcallerMustNotifyare empty arrays, notundefined. On 2026-08-22 the early return omitted three of these and a caller read the absence as "no instruction".
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Evidence:
Follow-up: Carry to the next pass.14. agenda-status Reconcile runs against a real board โ
- Goal: the Reconcile phase executes. It has never run. Two attempts died before reaching it, for two different reasons.
- Preconditions: scenario 6 has produced a real agenda file.
- Steps:
- Invoke
agenda-status.jswithtrigger: operator-status-requestandsessionReportscollected by the calling session. - Inspect the returned object.
- Invoke
- Expected:
statusChanges,completions,logGapsandstillUnknownare all present, andverdictis NOTNO_BOARD_TO_RECONCILE. - Verify: at least one finding sits in a channel the Reconcile schema owns rather than flat in an observations array. On the previous run the reader prompts were doing the reconcile's job, which is why this scenario exists.
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Evidence:
Follow-up: Carry to the next pass.15. agenda-status Record writes, and the publish gate opens โ
- Goal: the Record phase executes for the first time, and
callerMustPublishreflects reality. - Preconditions: scenario 14 completed with a real board.
- Steps:
- From the same run, read the agenda's Realtime Log.
- Read
callerMustPublishandpublishNote.
- Expected: a new timestamped line appended, not a rewrite.
callerMustPublishistrueandpublishNotenames the page and the standing URL. - Verify: the timestamp is Pacific and matches wall clock; it must not be UTC stamped as Pacific. Then the inverse, which has also never been exercised: if Record ever fails,
callerMustPublishmust befalsewith the DO NOT PUBLISH note. Force it if you can, or record that the failure arm remains untested.
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Evidence:
Follow-up: Carry to the next pass.16. A session's claim that disagrees with git is reported โ
- Goal: the self-report is treated as a claim, not evidence.
- Preconditions: at least two live sessions, one of which will answer with a deliberate inaccuracy.
- Steps:
- The calling session polls the lanes and collects replies.
- Have one lane answer with something true about ITSELF but false about the BRANCH, for example "no commits today" while other lanes have committed to the same branch.
- Pass the reports in and run with
trigger: operator-status-request.
- Expected: the observer reports BOTH the claim and what git shows, and names which is which.
- Verify: the branch is NOT recorded as cold. This exact shape was planted on 2026-08-22 and caught; it must still be caught after the restructure.
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Evidence:
Follow-up: Carry to the next pass.17. A reader that throws is ABSENT, not empty โ
- Goal: the failure-partitioning branch executes. It never has: both runs had every reader return.
- Preconditions: none.
- Steps:
- Temporarily point one reader at a path that does not exist, or otherwise force it to throw. Back the file up first.
- Run.
- Restore.
- Expected:
readerFailuresnames that reader with its error, and the downstream prompt says its input is ABSENT rather than empty. - Verify: the run still completes rather than aborting, and nothing downstream treats the missing input as "nothing found". An absent reader silently reading as a clean result is the failure this branch exists to prevent.
Result: [ ] pass [ ] fail [x] blocked
Actual: Deferred to a later pass by operator instruction (2026-08-22): this run was scoped to
scenarios 1, 2 and 6 through 12 only. Not attempted, not a finding.
Evidence:
Follow-up: Carry to the next pass.18. Every task field is ONE line โ
- Goal: the writer COMPRESSES the gathered detail rather than pasting it through.
- Preconditions: a completed
agenda-creationrun that wrote a file. - Prediction: stated in the predictions table above. This is the scenario expected to fail.
- Steps:
- Extract every
- Context:line from the written agenda. - Measure each one's character count, and name the longest with its length.
- Extract the other field lines on each session block: Skills, Project, Needs from you, Status.
- Extract every
- Expected: every field is a single line. No field wraps into a second bullet, a sub-list or a paragraph.
- Verify: a Context line over roughly 200 characters is a FAIL even though it is technically one line. "One line" means what a person reads in one, not what fits before a newline.
- Verify the cause, not just the symptom: for any block whose Context ran long, check whether that row has a
Project:link. A task carrying its whole history in the agenda is usually a project that was never created, and the fix is the project, not a shorter sentence.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED, and the prediction is UNMEASURED. Recorded that way deliberately: scenario 18 was
predicted to FAIL, and it would be easy and wrong to read a run that produced no file as
confirmation. No Context line exists, so no Context line was measured. The prediction
stands untested for the next run.
No file was written. See scenario 6 for the cause, one stray pair of backticks on line 330
of the workflow script.
THE SECOND VERIFY CLAUSE CAN BE ANSWERED, and it is the more interesting half. The clause
says: for any block whose Context ran long, check whether that row has a `Project:` link,
because a task carrying its whole history is usually a project that was never created.
That can be checked on the pipeline rather than on the page, and the answer is that NO row
can have one.
The `RANKED` schema at .agents/workflows/agenda-creation.js:206 declares
`additionalProperties: false` on every channel item, and no channel item declares a
project field. A ranked row may carry rank, title, detail, why, status and issues, and
nothing else. All four ranked rows came back with exactly those six keys. So the rank
agent CANNOT emit a project link even if it wanted to, and the writer has nothing to
populate the format's `Project:` line from. The format skill's own rule, "when a task needs
more than one line, it has a project, the Project field links it", has no wire to travel
on.
That is the stated cause, confirmed structurally and in advance of the symptom.
SIZE OF THE COMPRESSION THE WRITER WOULD HAVE FACED, measured on the input it was handed,
since the output does not exist. The four ranked rows carry `detail` fields of 277, 281,
372 and 282 characters, and the schema marks that field "Passed through from the gathered
item UNCHANGED". The 15 gathered details run 277 to 915 characters, mean 490. The Verify
clause sets the Context bar at roughly 200. So the writer would have had to cut the
SHORTEST of its four inputs by about a quarter, and the longest by nearly half, with no
Project field to move the remainder into. That is the shape of the predicted failure, and
it is the reason the prediction was written the way it was.
Evidence: No agenda file. Run wf_f80b57fb-b43. Schema at
`.agents/workflows/agenda-creation.js:206-232`, ranked rows in
/tmp/agenda-fmt-analysis.json.
Follow-up: Two, and they are separable. (1) Unblock by fixing line 330, then re-run and MEASURE the
Context lines, because the prediction deserves a real number. (2) The missing project
field is a finding on its own and does not wait on the re-run: whatever the Context lines
turn out to be, the `Project:` line cannot be filled today. Per this scenario's own
framing, that points the fix at creating the project rather than at shortening a sentence.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and THE PREDICTION WAS WRONG. Recorded that way deliberately, because the prediction
table calls this "the one I would bet against".
EVERY field on every session block is a single line. No field wraps into a second bullet,
a sub-list or a paragraph. Three blocks, five fields each, measured by character count:
field s1 s2 s3
Skills 31 40 38
Context 141 114 122
Project 43 43 43
Needs from you 4 4 4
Status 13 13 13
THE LONGEST CONTEXT IS 141 CHARACTERS, at line 20:
"Application Default Credentials cannot mint custom tokens, so there is no way to sign
into the admin or merchant portal outside deployed dev."
Against a Verify bar of roughly 200, and against the ~1,100-character Context the
prediction cites from the previous run. The compression is real: the writer was handed
`detail` fields of 341, 219 and 268 characters and returned 141, 114 and 122. It cut every
one, roughly in half, rather than pasting through. The whole file is 8,022 bytes against
80,276 for the same job before the format change.
THE CAUSE CLAUSE, answered properly rather than skipped because the symptom did not appear.
The clause says: for any block whose Context ran long, check whether that row has a
`Project:` link. No Context ran long, so the clause has no trigger. But the underlying
finding recorded in the block above stands and was CONFIRMED in the output: the `RANKED`
schema still cannot carry a project link, and all three Project lines read, identically:
- Project: none yet ( run project-creation on pickup )
The writer did the honest thing. It did not invent a path (verified: none of
docs/projects/local-admin-merchant-login, local-service-stacks or doctor-learns-more exists
on disk), and it did not leave the field blank. It said there is no project and named who
creates one.
SO THE TWO CAUSES ARE CLEANLY SEPARATED, which is the thing this scenario was written to
do. With no project field reachable anywhere in the pipeline, a long Context CANNOT be the
missing link surfacing as prose. Anything long would have to be the writer failing to
compress. Nothing was long. Therefore: the writer compresses correctly, AND the missing
project link is a real, separate gap that this run happens not to be blocked by. Filed as
#953 (agenda-creation cannot fill the agenda's Project field: no channel in the RANKED
schema can carry a project link).
WHAT THE PREDICTION GOT RIGHT ANYWAY. Its reasoning was that the writer is handed a
paragraph and asked to shrink it, and shrinking is the step that gets skipped. That is
still the correct description of the risk; the format change simply made the instruction
specific enough to survive. The prediction's proposed failure reading ("if the fix is to
loosen the one-line rule rather than create the missing project") does not apply, because
the rule was not loosened and was not needed.
Evidence: /tmp/agenda-fmt/2026-08-22.md lines 19-23, 33-37, 47-51. Longest Context at line 20.
Run wf_fe55300a-565. Project-path non-existence checked against the working tree.
Follow-up: #953 stays open on its own merits. It is not blocking this scenario, and this run shows
why: the one-line rule holds without the project link, so the link is about depth having
somewhere to go, not about keeping the agenda short.19. The handoff prompt is four labelled lines โ
- Goal: an agent with none of this context can act on the prompt cold.
- Preconditions: a completed run that wrote a file.
- Steps:
- Extract every fenced prompt block from Focus Tasks and Session Prompts.
- Check each for the four labels, in order:
Goal,Start here,Done when,Watch out. - Read the VALUES, not only the labels.
- Expected: all four present, in order, in every block.
Watch outsaysnothingrather than sitting empty. - Verify, and this is what separates useful from merely well-formed:
Start hereis a real path, doc or issue link. A description of where to look is a FAIL, and a path that does not exist on disk is a worse one.Done whenis a check the receiving agent can run or observe. "It works", "the task is complete", or anything only the operator could judge is a FAIL.Goalis an outcome. "Look at X" or "investigate Y" is an activity, and a FAIL.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED. No file was written, so there are no fenced prompt blocks to extract. See
scenario 6.
Zero prompt blocks found, therefore zero checked for the four labels and zero read for
their values. Both halves of this scenario are unexercised: the easy shape check and the
harder value check the scenario says is what separates useful from merely well-formed.
What is on the record about the INSTRUCTION, which is not the same as the output: the
Write prompt does carry the requirement, at agenda-creation.js:327, naming all four
labels in order and both value rules (Start here a real path or link, Done when a check
the receiving agent can run). So the instruction exists and was never delivered, because
the argument carrying it collapsed to a boolean three lines later.
One thing the prediction should be held to when this is re-run: the prediction says PASS
on shape and locates the risk in the VALUES. Note that `Start here` pointing at a real
path is checkable against disk, and this run gathered nothing that would let a writer cite
a project path, because no gathered or ranked row carries one (see scenario 18). So the
likely `Start here` values on a re-run are issue links and launch-plan paths, and those
should be resolved rather than eyeballed.
Evidence: No agenda file. Run wf_f80b57fb-b43.
Follow-up: Same as scenario 6. When re-run, resolve every `Start here` value against the filesystem
or GitHub rather than accepting that it looks like a path.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS on shape AND on values, including the three value clauses this scenario says are what
separate useful from merely well-formed. The prediction expected shape to pass and located
the risk in the values; the values held.
THREE fenced blocks, one per session. All three carry exactly four labels, in order:
Goal, Start here, Done when, Watch out. None empty, none reordered, none with a fifth.
`Start here` is a real path or link in all three, and every one was RESOLVED rather than
eyeballed:
s1 gh issue view 819, then services/api/auth/ 819 OPEN, path EXISTS
s2 gh issue view 855, then tooling/scripts/lantern.mjs
and tooling/vscode-extension/src/config.js (PORT_MAP) 855 OPEN, both paths EXIST
s3 gh issue view 857 (CLOSED, read what actually landed),
then gh issue view 858, then tooling/scripts/doctor-env.mjs
857 CLOSED, 858 OPEN, path EXISTS
Five distinct repo paths cited, five exist. Four issue numbers cited, four exist, and
#857's CLOSED state is stated correctly IN the prompt rather than being a stale claim.
`Done when` is a check the receiving agent can run or observe in all three. No "it works",
no "the task is complete", nothing only the operator could judge. s3's is a literal
command plus a test file: "./lantern doctor prints all three on this machine, and
tooling/scripts/__tests__/doctor-env.test.js passes" (that test file exists). s1 and s2
are observable end states with the surface named.
`Goal` is an outcome in all three, not an activity. "A local auth-api mints a custom token
and both the admin and merchant portals accept a login against a local stack." No "look
at" or "investigate".
`Watch out` is populated in all three and carries a real trap rather than filler. s1 names
the exact missing IAM permission and says it is a credential-path change that a retry will
not fix. s2 names the 8080 collision between the Firestore emulator and venues-api and
says to read PORT_MAP rather than invent a port. s3 warns the plan row is stale against a
closed issue, so establish what shipped before rebuilding it. None needed the literal word
"nothing", so that fallback is UNEXERCISED and stays unproven.
Evidence: /tmp/agenda-fmt/2026-08-22.md lines 25-30, 39-44, 53-58. Paths checked against the working
tree, issues checked with `gh issue view`. Run wf_fe55300a-565.
Follow-up: The `Watch out: nothing` fallback has never been exercised. It fires only on a task with
no trap, so it needs a thinner candidate set to test.20. Realtime log entries carry a bold capitalized title โ
- Goal: the log is skimmable, which is the whole reason the title exists.
- Preconditions: a completed run, from either workflow.
- Steps:
- Extract every line under Realtime Log and Activity Blockers.
- Check the shape of each.
- Expected: each reads
- [ YYYY-MM-DD HH:MM <ZONE> ]: **TITLE** > detail, the title capitalized and short, the detail one line.<ZONE>is whatever%Zreturns, PDT for most of the year. This line said PST until the run that found the same bug in the skill, so a file saying PST in August is a FAIL and not a match. - Verify the clock, because this has been wrong before:
TZ=America/Los_Angeles dateshould appear in the writer's tool calls in the run journal. This machine runs UTC and a previous run stamped UTC times as Pacific, so a plausible-looking Pacific time in the file is not evidence that the clock was read. - Verify on a status run:
agenda-statusAPPENDS. An entry that rewrites or reorders an existing line is a FAIL even if its shape is perfect.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED on both arms, the creation arm and the status arm.
CREATION ARM: no file was written, so there are no Realtime Log lines to shape-check. See
scenario 6. Zero lines extracted.
THE CLOCK CHECK IS ALSO BLOCKED, and this one is worth naming rather than skipping,
because the scenario asks for it explicitly and for a reason. The verify says
`TZ=America/Los_Angeles date` should appear in the writer's tool calls. The write agent
made ZERO tool calls, so the clock was neither read nor faked. That is not a pass. A
previous run stamped UTC as Pacific, and this run produced no evidence either way.
STATUS ARM: unexercised. The append-versus-rewrite clause needs an `agenda-status` run
against an existing agenda, and no agenda exists to append to, so it could not be reached
even if this run's scope had included it.
The cause is the sharpest detail in this whole pass and belongs on THIS scenario's record
specifically: the workflow line that breaks the writer is the line that specifies the
Realtime Log format. agenda-creation.js:330 writes the phrase "the detail after a `>`"
with backticks around the `>`, inside the backtick template that IS the Write prompt. The
first backtick ends the template early. So the sentence teaching the log format is the
sentence that prevents the log from ever being written.
Evidence: No agenda file. Run wf_f80b57fb-b43, write agent 0 tool calls, 8.0s, prompt `true`.
`.agents/workflows/agenda-creation.js:330`.
Follow-up: Same as scenario 6. On re-run, check the clock in the journal's tool calls BEFORE
accepting a plausible Pacific timestamp in the file, exactly as this scenario warns.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS on the creation arm, including the clock check. The status arm stays BLOCKED and is
reported separately below rather than folded into the verdict.
ONE log entry, at line 14, and its shape is exactly the specified one:
- [ 2026-08-22 16:08 PST ]: **AGENDA BUILT** > 3 sessions ranked from the Prototype
Phase 1 rows and the Aug 21 carry, 4 parked, 7 cut, 1 gap; no git, branch, worktree or
PR state was inspected, and there was no 2026-08-22 carry file, so every carried item
comes from August 21
Timestamp in brackets, bold title, title capitalized and short, detail after the `>`, all
on one physical line. The title says what HAPPENED. The detail carries the coverage caps,
which is what the Write prompt asks that single line to do.
THE CLOCK CHECK PASSES, and it was checked the way this scenario demands rather than by
looking at the timestamp. The write agent's journal shows `TZ=America/Los_Angeles date
'+%Y-%m-%d %H:%M'` as an EXECUTED tool call, not merely as text in its prompt, returning
2026-08-22 16:08. Verified independently three minutes later at 16:11 Pacific. So the
previous run's UTC-stamped-as-Pacific failure did not recur, and this is evidence rather
than a plausible-looking string.
ONE REAL DEFECT, and it belongs to the FORMAT rather than to the writer. The stamp reads
`PST`. On 2026-08-22 the correct Pacific abbreviation is `PDT`: `TZ=America/Los_Angeles
date '+%Z'` returns PDT, and the file says PST. The writer is not at fault, because the
format skill hardcodes "PST" in its own template, in the day title, the log lines and the
question log. The agenda will be wrong for roughly eight months of every year. Not scored
against this scenario, whose Expected line also says PST, but it is a defect in both.
STATUS ARM: BLOCKED, unexercised. The append-versus-rewrite clause needs an
`agenda-status` run against this file, which is out of this pass's scope. The creation arm
wrote a single log line, so there is now something for that arm to append to.
Evidence: /tmp/agenda-fmt/2026-08-22.md line 14. Clock tool call in
.../workflows/wf_fe55300a-565/agent-*.jsonl. Run wf_fe55300a-565.
Follow-up: Fix PST to a real timezone abbreviation in .agents/skills/agenda-documentation/skill.md,
and in this scenario's own Expected line. Either read %Z from the same date call that is
already being made, or say "Pacific". Then run the status arm.21. Focus blocks are checkboxes, and the emojis are the fixed set โ
- Goal: the structural markers are the ones the format names, not ones the writer picked.
- Preconditions: a completed run that wrote a file.
- Steps:
- Extract Key Focus Blocks.
- Extract every
##heading. - Extract every
Status:line, on the day header and on each session block.
- Expected: every focus block starts
- [ ]or- [x]. Each heading carries exactly one emoji from the table in theagenda-documentationskill. Each Status carries one of the five status emojis. - Verify: an emoji from outside the table is a FAIL even when it is a good choice. The point is not that the emoji is apt, it is that two sessions writing the same file agree.
- Report the two failures separately: a heading with NO emoji means the table was skipped; a heading with a WRONG emoji means it was improvised. Different causes, different fixes, so do not merge them into one count.
- Falsifying clause: on a CREATION run every focus block should be unticked. A ticked box on a fresh agenda means the writer inferred completion, which is precisely what the checkbox exists to make visible. A run that comes back with ticks is a fail of the thing, not a formatting nit.
Result: [ ] pass [ ] fail [x] blocked
Actual: BLOCKED. No file was written, so there are no focus blocks, no headings and no Status
lines to inspect. See scenario 6.
Every clause is unexercised, and the scenario's instruction to report the two emoji
failures separately has nothing to count: zero headings with no emoji and zero headings
with a wrong emoji, out of zero headings.
THE FALSIFYING CLAUSE IS ALSO UNEXERCISED, and it is the one this scenario cares most
about: a ticked checkbox on a fresh agenda would mean the writer inferred completion. No
checkbox was written, ticked or otherwise, so that remains untested.
On the emoji question specifically, the ONE thing this run can say is about the
instruction rather than the output. The Write prompt does not enumerate the emoji table
inline. It points at the format file (agenda-creation.js:311, "READ ... FIRST and follow
its Agenda Creation state exactly", naming the emoji vocabulary as one of the four things
not to write from memory), and separately requires Status to use "the emoji vocabulary
from the format file, not free text" (line 328). So the design is a pointer, not a copy,
which is the right call for drift and which means the run turns entirely on whether the
writer actually opens the file. This run's writer opened nothing, so that question is
exactly as open as it was before.
The reference set this was to be measured against, from
.agents/skills/agenda-documentation/skill.md: headings take one each of the nine in its
table, and Status takes one of the five state emojis. Recorded here so a re-run measures
against the same list.
Evidence: No agenda file. Run wf_f80b57fb-b43. Format table at
`.agents/skills/agenda-documentation/skill.md:26-46`.
Follow-up: Same as scenario 6.Fourth result, run wf_af98c56f-14e, after the format fixes landed (253b66a1 plus the example-date correction). The three blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and the PST defect recorded in the block above is FIXED and verified in output.
The log line at line 14 keeps its shape, and the stamp now reads PDT:
- [ 2026-08-22 16:26 PDT ]: **AGENDA BUILT** > 3 ranked, 4 parked, 8 cut, 1 gap, ...
THIS IS A STRONGER RESULT THAN A BARE "IT SAYS PDT", and the difference is worth stating,
because it was named in advance rather than after. The writer's INPUT contained NINE
strings stamping an August date PST: three fixture day-titles and six fixture log lines
(verified: 2026-08-19, -20 and -21 all return PDT from `date '+%Z'`). The rule was written
down before the run: PDT means the skill beat contradicting input; PST would have been
ambiguous, because it could be a hardcode OR mirroring yesterday's file, and the fixtures
would have had to be fixed before this scenario meant anything.
It came back PDT, in all four places it appears: the day title and three log or question
entries. So the skill beat nine lines of input telling it otherwise. Same shape as the
old-field-names result in scenario 8.
The clock was read with the zone, not just the time: `TZ=America/Los_Angeles date
'+%Y-%m-%d %H:%M %Z'` appears as an EXECUTED tool call in the journal, twice. Cross-checked
independently one minute later at 16:27 PDT.
STATUS ARM: still BLOCKED, still unexercised. Unchanged, and it needs an `agenda-status`
run against an existing file.
Evidence: /tmp/agenda-fmt3/2026-08-22.md lines 1, 14, 64, 65. Clock tool call in
.../workflows/wf_af98c56f-14e/agent-*.jsonl. Run wf_af98c56f-14e.
Follow-up: The FIXTURES still carry the defect: nine PST-in-August strings across the three files.
They did not fool the writer this time, but they are wrong and they would make a future
PST result unreadable. Fixed separately; see the fix section at the end of this document.Third result, re-run of 2026-08-22 after the backtick fix, against fbfba81d, run wf_fe55300a-565. Both blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS on checkboxes, on the emoji set, and on the falsifying clause. The two failure counts
this scenario asks to be kept separate are both ZERO, and one edge is reported below that
is neither.
FOCUS BLOCKS: three, all checkboxes, all `- [ ]`.
- [ ] 1615-1730: local admin and merchant login, mint a custom token off a local stack (#819)
- [ ] 1730-1900: local service stacks, one command per preset with port-collision guards (#855)
- [ ] 1900-2000: doctor learns more, establish what #857 actually shipped, then finish the rest (#858)
FALSIFYING CLAUSE PASSES: zero ticked. On a creation run every box is unticked, and every
box is unticked. The writer did not infer completion, which is the thing the checkbox
exists to make visible.
EMOJI, compared by CODEPOINT against the table in the format skill, not by eye:
U+1F4C5 date day title in table
U+1F3AF direct hit Key Focus Blocks in table
U+1F550 clock 1:00 Realtime Log & Activity Blockers in table
U+1F9E9 jigsaw Focus Tasks & Session Prompts in table
U+2753 question User Question Log in table
U+1F17F parking Parked in table
U+2B1C white square Status, four occurrences in the status table
Seven distinct emoji, all seven in the skill's tables, at the exact codepoints. NOTHING
from outside the tables appears anywhere in the file. Every Status line, the day line and
all three session lines, carries U+2B1C "not started", which is right for a fresh agenda.
THE TWO FAILURE COUNTS, kept separate as the scenario requires:
headings with NO emoji, meaning the table was skipped: 0
headings with a WRONG emoji, meaning improvisation: 0
THE EDGE, which is neither of those and is a gap in the FORMAT rather than in the writer.
Five `###` headings carry no emoji: the three Session blocks, "Cut today" and "Gaps". The
three Session headings are correct, because the skill's own template writes them as
`### Session 1: [session title]` with no emoji. "Cut today" and "Gaps" are different: they
are sections the WORKFLOW prompt asks for (agenda-creation.js:332-333) that the format
skill does not define at all, so they appear in neither the section list nor the emoji
table. The writer had nothing to read off and correctly did not invent one. That is the
right behaviour under "nothing outside the table", and it means the workflow and the
format skill currently disagree about how many sections an agenda has.
Evidence: /tmp/agenda-fmt/2026-08-22.md lines 1, 8-11, 13, 16, 60, 68, 79, 89, and Status lines 6,
23, 37, 51. Codepoints extracted programmatically and compared against
.agents/skills/agenda-documentation/skill.md:26-46. Run wf_fe55300a-565.
Follow-up: Add "Cut today" and "Gaps" to the format skill's section list and emoji table, or drop
them from the workflow prompt. Right now the executable and the format disagree, and the
only reason it looks clean is that the writer declined to improvise.Fourth result, run wf_af98c56f-14e, after the format fixes landed. The three blocks above are left verbatim.
Result: [x] pass [ ] fail [ ] blocked
Actual: PASS, and the "Cut today / Gaps are undefined" edge recorded in the block above is FIXED
and verified in output.
Those two sections now come from the format rather than from improvisation:
## โ๏ธ Cut Today U+2702, matching the skill's new table row
## โ ๏ธ Gaps U+26A0, matching the skill's new table row
Previously they were `### Cut today` and `### Gaps`, h3 with no emoji, because the skill
defined neither. They are now h2 with in-table emoji, so the executable and the format
agree about how many sections an agenda has.
EMOJI CENSUS, by codepoint, whole file: ten distinct, ALL in the skill's tables. Nothing
off-table anywhere.
headings with NO emoji, meaning the table was skipped: 0
headings with a WRONG emoji, meaning improvisation: 0
The three `### Session N` headings still carry none, which remains correct: the skill's
own template writes them without one.
FALSIFYING CLAUSE PASSES AGAIN: three focus blocks, all `- [ ]`, zero ticked.
NEW AND WORTH RECORDING: the Status vocabulary was used with judgement rather than as a
default. Three lines carry U+2B1C not started and ONE carries U+23F8 blocked, on session 3,
the webhook fix, which genuinely is blocked on an unanswered question. The previous run had
four identical `not started` lines. This is the first run where a status emoji other than
the default appeared, and it appeared correctly.
Evidence: /tmp/agenda-fmt3/2026-08-22.md lines 1, 8-11, 13, 16, 60, 67, 78, 93, and Status lines 6,
23, 37, 51. Codepoints extracted programmatically. Run wf_af98c56f-14e.
Follow-up: None.Notes โ
Space for the tester's own observations, added scenarios, and anything discovered mid-run.
Run of 2026-08-22, scenarios 1, 2 and 6 through 12 โ
Scope: exercise 1, 2 and 6 through 12. Leave 3, 4, 5 and 13 through 17 for a later pass (3 and 4 were subsequently filled by the plan's author and are marked author-run). Do not edit the workflow, do not fix what it gets wrong, do not publish anything.
WHICH VERSION THESE RESULTS DESCRIBE. Every run below was made against
13a5f102. Three of the findings were fixed the same afternoon, ine3c29691and99a3cfc4: the null-reader partitioning (in BOTH workflows, the same copy-pasted line sat under agenda-status too), the unconditionalcontext:syncthat made a scratchagendaDirnot actually scratch, and the invented-rows failure, now given its own cappedgapschannel that is forbidden inranked. So scenarios 9, 10 and 11 describe behaviour that no longer exists in the tree. They are kept as recorded rather than rewritten, because the evidence is what justified the fixes, and a fixed finding quietly deleted reads to a later reader as a scenario that always passed. None of the three fixes has been verified by a run. That verification is the next pass.
FIRST PASS outcome (against 13a5f102): 3 pass, 1 fail, 3 void or unassessable, and the workflow had never written a file. The three findings below were fixed the same afternoon and the pair was re-run. See the re-run section at the end for the current state.
Outcome: 3 pass, 1 fail, 3 void or unassessable, and the workflow has still never written a file.
| Scenario | Verdict | The one line that matters |
|---|---|---|
| 1 focus required | pass | Refuses in 25ms, zero agents, names the arg and the reason |
| 2 args-as-string | pass | Names the cause, says no argument is actually missing |
| 12 reader greps | pass | 22 percent and 13 percent of the two big files, quantified unprompted |
| 11 no invented rows | FAIL | The entire ranked list, both arms, was mined from the launch plan |
| 9 and 10 the pair | VOID | Arms read 74 and 12 candidates, so nothing can be concluded |
| 6, 7, 8 the write path | blocked | No agenda file was produced by any run, anywhere |
The four findings, in the order they matter.
The ranked list is not drawn from the candidate list. In BOTH arms of the pair, independently, every ranked row was invented from ALPHA.md and no ranked row came from the 74 (or 12) carried candidates. The Check phase obeyed its identical constraint perfectly, returning exactly one annotation per candidate and inventing nothing, so this is a Rank-phase failure, not a Check-phase one, and scenario 11 as titled is aimed one phase too early. The rank agent announced what it was doing, labelling the rows "NEW GAP, named under Check A's one exception", and no such exception exists in its prompt. It constructed the permission and wrote it into its own output.
A killed reader is reported as a healthy one.
agent()returns null rather than throwing when an agent dies, and.then((v) => ({ reader, ok: true, ...v }))spreads null into nothing, so the object becomes{reader, ok: true}with no rows and never reaches the.catch. Run 10 returnedreadersOk: [repo-state, yesterday-board, in-flight]andreaderFailures: []whileyesterday-boardhad in fact been refused outright. The comment above that code states the exact principle it fails to implement. This is what made finding 3 invisible, and it partially answers deferred scenario 17 in the negative.A scratch
agendaDirdoes not isolate a run. Both theyesterday-boardreader and therecordagent runnpm run context:syncagainst the real repo and context home unconditionally, regardless of whereagendaDirpoints. The safety classifier blocked those steps once the operator set a leave-the-real-board-alone boundary, which is why the pair lost a reader and why no file was ever written. Until that call is conditional, this workflow cannot be exercised end to end without writing to her real board.The write path has never executed. Not once, across every attempt. Phase ORDER is confirmed (Rules, Backlog, Check, Rank, Record, in that order, both runs). Everything about the written FILE remains untested: the Focus line verbatim check, the format-skill comparison, the section 0 through 5 tripwire, the one-file-named-for-today check, and the context:sync push report.
What went right, and is worth keeping. The publish gate fixed earlier today behaved correctly on its failure arm in both runs: recorded: null produced callerMustPublish: false and a real DO NOT PUBLISH note. The cut channel fix landed and both arms returned their cut lists. The focus is echoed back intact through five phases. The focus text demonstrably reaches the cut reasons in both arms, so it is being applied somewhere, even though finding 1 means it cannot be reaching the ranked list by way of filtering candidates.
Run inventory. agenda-creation: wf_d1104e11-522 (scenario 1, refusal), wf_f1b91c9c-e29 (scenario 9), wf_ca348fa8-807 (scenario 10). agenda-status: wf_429f12b6-431 (scenario 2, refusal) plus two stopped misfires (wki6duaqb, wu3glddqo) that wrote nothing. The real-board run of scenario 6 was refused by the auto mode classifier and then withdrawn by the operator's choice.
Staging note, and a deliberate deviation from the plan. The plan says "do not re-copy between the two runs" and gives its reason: "if the inputs differ, the comparison means nothing." Read literally, run 9 writes today's file into the shared directory and run 10 then reads run 9's own output, which makes the inputs differ. Reason beating letter, a pristine master of the identical 11:02 PDT snapshot was staged at /tmp/agenda-falsify-pristine so the directory could be restored between arms. In the event no restore was needed, because run 9's Record was blocked and wrote nothing; /tmp/agenda-falsify was verified md5-identical to the master immediately before run 10 launched. The confound came from a lost reader, not from the staging.
Re-run of the pair, 2026-08-22 evening, against 7ca235e4 โ
Scope: scenarios 9, 10 and 11 only, on the operator's word relayed as a scope decision. Two arms, runs wf_49d4fa52-069 (product-surfaces focus) and wf_474a8577-dec (agent-tooling focus), each against a pristine-restored copy of the 11:02 PDT agenda snapshot. Scenarios 6, 7 and 8 came along for free, because arm A wrote a file and those three had never had one to inspect.
Final tally across both passes: 9 pass, 1 fail, 7 blocked.
The three fixes, and whether they fired.
| Fix | Verdict | Evidence |
|---|---|---|
| Scratch runs suppress sync and push | FIRES | Both arms reported no sync and no push. Real context home verified untouched after each: clean status, still bdedf95, still three dated files |
Uncarried rows go to gaps, never ranked | FIRES | Zero ghosts in ranked, inProgress, parked and cut across both arms. Arm A's 2 uncarried rows sat in gaps within its cap of 2 |
A killed reader reports ok: false | UNEXERCISED | All 8 agents returned in both arms. The guard never had a chance to fire, exactly as predicted, and it stays unproven |
What the re-run settled. The invented-rows failure is genuinely fixed, and the way it was fixed is the transferable lesson: it was a ROUTING failure wearing the costume of a discipline failure. The rank agent had a real observation and nowhere legitimate to put it, so forbidding the behaviour did nothing and giving the output a correct destination fixed it immediately. The write path also works, and its output matches the current format skill rather than the format deleted that morning.
What the re-run did NOT settle, and this is what reopens. Both arms returned an EMPTY ranked list. Recorded plainly rather than read as a pass: a complete reversal of the focus produced an identical ranking, so this pair still does not demonstrate that the focus drives ranking. Two independent reasons, and both need fixing before a third run is paid for:
- The arms still did not read the same input. Both reported candidateCount 69, which was the gate, and they shared only 35 of 69 candidate TITLES. Jaccard 0.34. A fresh set of reader agents runs per arm and they do not return the same rows from byte-identical files. The gate counted; it did not compare. The candidate list has to be captured ONCE and replayed to both arms so the focus string is the only variable.
rankedmay be structurally unable to move. The rank prompt forbids ranking any row whose owner is recorded, and on this board almost everything has an owner, so every candidate routes to inProgress, operatorAsks, parked or cut and none is left for ranked. Both rank agents said the empty list was deliberate and gave that reason. Whether an always-empty ranked channel is correct is a design question about the routing rules, and the pair is measuring a channel that may not be able to vary.
What the focus demonstrably DOES reach, and it is real but is not what the pair measures: the cut reasons argue against the focus given, and the gaps channel is focus-shaped (2 product-surface rows under the product focus, 0 under the tooling focus). So "the focus is decorative" is NOT the finding. The finding is narrower and more awkward: the focus reaches the reasoning and does not reach the output the day is built from.
Timing. Arm A ran 36m21s, arm B 38m42s, against 25m20s for the earlier void run. The deviation is explained: the void run lost its most expensive reader to a classifier refusal, so it was doing less work. Both re-run arms used roughly 725k and 736k subagent tokens.
Run of 2026-08-22 evening, format scenarios, against 0739b0a6 โ
Scope: scenarios 6, 7, 8, 11 and 18 through 21, against the committed fixture set rather than a copy of her real board. Run wf_f80b57fb-b43, agendaDir /tmp/agenda-fmt seeded from docs/projects/agent-workflow-hierarchy/fixtures/agenda/, focus "get the app, the admin portal and the merchant portal working end to end". 4 agents, 0 errors, 200,484 subagent tokens, 5m08s.
Tally for this run: 1 pass, 1 fail, 6 blocked, and all 6 blocked behind the same single character.
| Scenario | Verdict | The one line that matters |
|---|---|---|
| 11 no invented rows | PASS | 15 gathered, 16 rows across ranked, asks, parked and cut, 0 uncarried. The 1 uncarried row is in gaps, cap 2 |
| 6 writes today's agenda | FAIL | Zero files created. The Write agent's prompt was the boolean true |
| 7 focus verbatim | blocked | No header to read. The focus itself survived all three phases unchanged |
| 8 matches the format skill | blocked | No file to compare |
| 18 one-line fields | blocked | No Context line to measure. Prediction stays untested |
| 19 four-line prompt | blocked | No prompt blocks |
| 20 bold log title | blocked | No log lines, and the writer made zero tool calls, so the clock check is unexercised too |
| 21 checkboxes and emojis | blocked | No headings, no checkboxes |
The finding, and it is one character โ
.agents/workflows/agenda-creation.js:330 writes the phrase "the detail after a >" using BACKTICKS, inside the backtick template literal that IS the Write prompt. The first of those backticks closes the template early; what follows parses as a chain of > string comparisons; the argument to agent() evaluates to the boolean true. The Write agent is handed the literal text true, makes zero tool calls, spends 43,509 tokens saying "No task was provided", and returns.
Reproduced rather than inferred: lines 306 to 343 lifted verbatim, evaluated against stub bindings, prompt came back typeof "boolean". Committed at HEAD, not a local edit.
Two things this makes clear that a passing run would have hidden.
- The failure is silent by construction. The workflow returned a full, plausible, well-populated object: 4 ranked rows, 2 asks, 4 parked, 6 cut, 1 gap, all correct.
readerFailureswas empty andagents_errorwas 0. Only therecordedstring says anything is wrong, and a caller who trusted the return shape would have reported a successful agenda build. The publish gate did fire correctly (callerMustPublish: false), but on the scratch-run branch, not on the write-failed branch, so it was right by coincidence rather than by detection. - The line that breaks the writer is the line teaching the writer the log format. Scenario 20's own subject killed scenario 20. Worth keeping as the example of why prose that quotes syntax has to be escaped in a file that is itself executable.
What was measured anyway, without a file โ
- Scenario 11 passes on a real ranking, which the previous pass could not claim: that one passed with an EMPTY
rankedin both arms, so it only showed nothing was invented. Hererankedheld 4 rows, including one in-progress row carrying its status, and still nothing was invented. - Scenario 18's stated CAUSE is confirmed structurally. The
RANKEDschema setsadditionalProperties: falseand declares no project field on any channel, so no row can carry aProjectlink and the writer has nothing to fill that line from. The format skill's rule that a task needing more than one line has a project has no wire to travel on. That holds whatever the Context lines turn out to measure on a re-run. - The compression the writer would have faced: ranked
detailfields of 277, 281, 372 and 282 characters, against a Verify bar of roughly 200 for Context.
Not fixed in this run, and why โ
The fix is to escape or requote the backticks on line 330. It was NOT applied here: editing the script under test changes the artifact being measured mid-pass, and it is her branch with an open PR. Rule 18 says a broken tooling surface gets fixed in the session, so this is raised rather than filed, and it needs her word before the edit lands.
The real context home โ
Untouched, and watched rather than only checked at the end. A monitor polled /home/mechelle/.agents/context/lantern_app every 20 seconds for the duration and was armed to fire on any dirty status or any HEAD move. It never fired.
Re-run after the backtick fix, 2026-08-22, against fbfba81d โ
Scope: the eight scenarios above, re-run once the Write phase could execute. Run wf_fe55300a-565, identical args and identical focus to wf_f80b57fb-b43, against a freshly restored /tmp/agenda-fmt. 4 agents, 0 errors, 223,179 subagent tokens, 7m09s. The fix (fbfba81d) was authored by a parallel session and VERIFIED in this tree before the run: the Write prompt now evaluates to typeof "string", and sync-workflows.js --check reports 0 errors.
Tally for this run: 8 pass, 0 fail, 0 blocked. Combined with the failed run above, every scenario 6 through 12 and 18 through 21 now has a recorded result.
| Scenario | Verdict | The one line that matters |
|---|---|---|
| 6 writes today's agenda | pass | One file, 8,022 bytes, clock genuinely read, fixtures byte-identical after |
| 7 focus verbatim | pass | Character for character, and the cut reasons argue against the focus, so it drove the output |
| 8 matches the format skill | pass | All three explicit checks plus the tripwire. Yesterday's OLD-format fixture was in the input and did not leak |
| 11 no invented rows | pass | 17 rows across four channels, 0 uncarried. The 1 gap is within its cap |
| 18 one-line fields | pass, prediction wrong | Longest Context 141 chars. The writer compressed 341 to 141, not pasted through |
| 19 four-line prompt | pass | 5 of 5 cited paths exist, 4 of 4 issues exist, Done when runnable in all three |
| 20 bold log title | pass | Shape correct and the clock tool call actually executed |
| 21 checkboxes and emojis | pass | 7 emoji, all in-table by codepoint. 0 ticked boxes |
The prediction that was wrong, and it is the useful one โ
Scenario 18 was the bet-against, predicted to FAIL on the grounds that the writer is handed a paragraph and asked to shrink it, and shrinking is the step that gets skipped. It did not get skipped. The writer was handed detail fields of 341, 219 and 268 characters and returned Context lines of 141, 114 and 122. Every field on every block is one line. The whole file is 8,022 bytes against 80,276 for the same job before the format change.
The prediction's reasoning was sound and the outcome contradicts it, which is worth more than a confirmation would have been: the risk was real and the format change is what closed it.
The prediction also named a failure reading in advance, "if 18 fails and the fix is to loosen the one-line rule rather than to create the missing project, then the rule was aimed at the wrong thing". That reading does not apply. The rule held WITHOUT the project link existing, which separates the two concerns cleanly (see below).
Three format defects this run surfaced, none of them writer error โ
PSTis hardcoded and August isPDT. The skill's template writes PST into the day title, the log lines and the question log.TZ=America/Los_Angeles date '+%Z'returns PDT today. The agenda will carry a wrong timezone for roughly eight months a year. Scenario 20's own Expected line has the same bug.- "Cut today" and "Gaps" exist in the workflow and not in the format.
agenda-creation.js:332-333asks for both; the skill defines neither, in its section list or its emoji table. The writer correctly declined to invent emoji for them, so it looks clean, but the executable and the format disagree about how many sections an agenda has. - An ask on a parked or cut task has no home. The skill says an ask lives on its task block and that line is the only place one lives; it also says every question is logged without exception. All three of today's asks attach to parked or cut tasks, so no block could carry them, and the writer routed them to the User Question Log. Reasonable, and undefined.
Plus the separately filed #953: no channel in the RANKED schema can carry a project link, so all three Project: lines read "none yet ( run project-creation on pickup )". The writer handled it honestly, inventing no path (checked: none of the three plausible docs/projects/ slugs exists).
Why scenario 18's two causes were separable this run โ
The scenario asks whether a long Context is the writer failing to compress or the ranker having nowhere to put a project link. Because the project field is currently unreachable anywhere in the pipeline, a long Context provably could NOT have been the missing link surfacing as prose. So the run measured exactly one thing, and it passed. The missing link is a real gap that this run simply was not blocked by, which is why #953 stays open on its own merits rather than as a fix for scenario 18.
The real context home โ
Untouched across both runs. Watched every 20 seconds by a monitor armed to fire on any dirty status or HEAD move, which never fired. Verified after: git status --porcelain empty, HEAD still bdedf95ad3b510cffdf4ecc51f7dfd22ba765a24, agenda/ still holding its original three files plus agenda.html at their pre-session mtimes.
Format-defect fixes and their live verification, 2026-08-22 โ
The three format defects surfaced by run wf_fe55300a-565 were fixed and then VERIFIED IN OUTPUT rather than by inspection, because a format skill is a runtime surface and a static check cannot say whether the writer obeys it. Verification run: wf_af98c56f-14e, fresh /tmp/agenda-fmt3, identical args and focus. 4 agents, 0 errors, 232,212 subagent tokens, 6m58s.
| Defect | Fix | Verified in output |
|---|---|---|
PST hardcoded, wrong for eight months a year | 253b66a1 added a "How is the timezone written?" section requiring %Z be read live. Its examples still stamped January 1 as PDT, corrected separately | PDT in all four places, against NINE PST-in-August strings in the input |
Cut Today and Gaps existed only in the executable | 253b66a1 gave both a table row (โ๏ธ U+2702, โ ๏ธ U+26A0), a template heading and a definition; the workflow prompt now points at the skill | Both emitted as ## with the in-table emoji, where the previous run wrote bare ### |
| An ask on a parked or cut task had no home | 253b66a1 added the rule at skill line 112: it goes in the User Question Log, naming the row it belongs to | Both routings exercised in ONE run, see below |
The fix to the fix โ
253b66a1 did a blanket PST to PDT swap and did not touch the dates, so all 15 examples across the three template blocks read 2026-01-01 and January 1, 2026 stamped PDT. January 1 is PST. The section teaching "never hardcode the abbreviation" was demonstrating a wrong date-to-zone pairing in every one of its own examples.
The commit's own explanatory sentence reads "the examples below say PDT because they are August dates", which makes the intent unambiguous, so the dates were moved to match the sentence rather than the abbreviations reverted: 2026-01-01 becomes 2026-08-03, January 1, 2026 becomes August 3, 2026. 15 insertions, 15 deletions, nothing else touched. Synced to all three generated copies; lint.sync-skills reports in sync and lint:emdash exits 0.
This is the third instance today of one shape, and it is worth naming as a class rather than three bugs: a document that quotes the thing it teaches has two things that must agree, and nothing checks that they do. The backtick bug (prose quoting a > inside an executable template), the fixture folder eating its own run output, and now a timezone rule whose examples contradict it.
Why the PDT result is strong rather than merely green โ
Stated before the run so it could not be fitted afterwards: the fixtures carry nine strings stamping an August date PST, three day-titles and six log lines, and they are the writer's input. A PDT output means the skill beat contradicting input. A PST output would have been UNREADABLE, because hardcoding and mirroring yesterday's file produce the same string, and the fixtures would have needed fixing before this scenario meant anything.
It came back PDT.
Both ask routings exercised in one run, which was luck worth recording โ
The rank agent placed the webhook fix in ranked this time (it was cut in the previous run), while the retention decision stayed parked. So a single run exercised both halves of the new rule:
- The webhook ask sits on its own session block:
- Needs from you: When a webhook event is dropped, should it be replayed from the delivery log, or accepted as lost? ... - The retention ask, whose task is parked and has no block, sits in the User Question Log naming the parked row it belongs to.
- Sessions 1 and 2 read
- Needs from you: none.
That is exactly the rule, on both branches, without needing a second run to reach the second branch.
One observation, not scored โ
The Needs from you line carrying the webhook ask is 250 characters, the longest field on the page. Scenario 18's roughly-200 bar is written for Context specifically, so this is not a fail by the letter. It is also arguably correct: an ask has to be answerable cold. Noted in case the one-line rule is ever tightened to cover every field, because this is the field that would fail it first.
Context lines this run: 105, 117 and 136, all comfortably inside the bar, and the longest is shorter than the previous run's 141.