docs(api): the twelve run-log events a client reads a run's outcome from (#406) - #414
Conversation
…rom (#406) §7's index listed 111 emitted event types and wrote up 70 of them. The 41 left included every line a client would reach for first: the run's own opening and terminal markers, the phase markers §7b times a run with, and `agent_call` — the only place a model's raw reply is kept. Twelve of those 41 now have a section: `run_start`, `phase`, `agent_call`, `feedback_rerun`, `reader_start`, `reader_issues_dropped`, `reader_no_output`, `assembly`, `assembly_anchors`, `run_signals_failed`, `run_complete`, `run_failed`. Each field is read off its emit site rather than inferred, which turned up three things that were wrong rather than missing: - `docs/API.md` §0c and `src/diagnostics.ts`'s own comment both said `pages` on `run_complete` counts source images. There is no `pages` field on `run_complete`; the count is `images` on `run_start`. Fixed in both, and the new `run_complete` section says so outright. - §4 named the method `agentCall` where the log line's `type` is `agent_call`, so the one grep a reader would run found nothing. - §4's payload omitted `error`, which appears on a failed session and only there. Documented, with the branch-on-status warning. §7's coverage paragraph is pinned by `test/config-agents.test.ts`, which asserts four named examples are still undocumented. All four are documented here, so its list moves to three that still are (`page_links`, `specialist_dispatched`, `feedback_learned`) and one stale rationale comment above the `agent_call` assertion is rewritten. Coverage: 111 emitted, 77 sections, 82 covered, 29 uncovered. #406 stays open. The remaining 29 events are two more passes — the specialist and link-repair paths, then the contribution and feedback-learning ones — and item 3's scope question is still unanswered. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
All six checks pass (npm ci, typecheck, 1575 unit tests, e2e, actionlint, shellcheck), and this is docs + one source comment + one test example list. I spot-checked every field name in the twelve new sections against its emit site — run_start (src/pipeline/orchestrator.ts:256/:281/:305), phase (:98/:117), agent_call (src/store/runlog.ts:28-37), feedback_rerun (:129/:131), reader_start (src/pipeline/review.ts:1038), reader_issues_dropped (:1121), reader_no_output (:1138), assembly (src/pipeline/assembly.ts:594), assembly_anchors (:713), run_signals_failed (orchestrator.ts:613), run_complete (:695), run_failed (:725) — and the three corrections hold: run_complete carries no pages, images on run_start is the source-image count, and §4's error is gated on s.status === \"failed\" && s.error (src/routes/sessions.ts:403). Three latent inaccuracies, none of which changes delivered output.
Non-blocking notes
1. The coverage paragraph is now exhaustive where the code says it is not. New text: "29 have no section here — what is left is three paths: link repair (page_links), specialist dispatch (specialist_dispatched), and the feedback-learning and contribution one". The replaced text ended "… among them", which was honest about being a sample. At least two of the 29 fit none of the three paths: reextract_skipped (src/pipeline/extraction.ts:4597, a feedback re-extraction event) and page_lessons_injected (:4422, :4590, emitted during extraction). Neither string appears anywhere in docs/API.md, and the PR body's own pass-2 list carries both under neither heading. test/config-agents.test.ts pins only the three named examples, so nothing catches the widened claim — a reader asking "what is still undocumented?" gets a list that is missing two events. Either name them ("…, plus page_lessons_injected and reextract_skipped") or restore the non-exhaustive "among them".
2. agent_content is always written, not "present only for" a session-built agent. The new agent_call section says "the full text inline, present only for a session-built agent". src/store/runlog.ts:36 writes agent_content: args.agent.sessionBuilt ? args.agent.content : null — the key is on every line, null otherwise. That is the same written-rather-than-omitted convention the section itself calls out one paragraph earlier for image ("written rather than omitted, unlike the count fields elsewhere in this log"), so a client that learned presence-vs-omission from that sentence branches wrong here. Worth adding that today sessionBuilt is only true for an agent loaded from tmp/<id>/agents (src/agents/loader.ts:90), so in practice every line reads agent_content: null.
3. "§7b cuts the log at the last run_start" does not hold for phase_durations_ms. The run_start section says diagnostics "slices at the last run_start and reports the run after it", and the phase section says §7b measures "each to the next, and the last to the run's terminal line". Both are off for that one field: src/diagnostics.ts:979 filters events — the whole log — not the currentRun slice built at :773; and log.event(\"phase\", { phase: \"extraction\" }) is emitted at src/pipeline/orchestrator.ts:117, before run_start, so the extraction marker is never inside that slice at all. On a session with feedback rounds the keys are overwritten round by round, and an earlier round's last phase is measured to the next round's first marker (idle gap between rounds included) rather than to a terminal line. Latent: it misleads someone debugging a multi-round session's phase_durations_ms, and no number §7b prints changes with this PR.
Accessibility impact: none — documentation, one source comment and one test example list; no delivered document changes.
…ode does All three from review round 1, all three latent (no delivered output changes): 1. The coverage paragraph named three paths for the 29 events with no section, which read as exhaustive and is not: `page_lessons_injected` and `reextract_skipped` belong to none of them. It now splits all 29 with counts that add up — 5 link-repair, 5 specialist-dispatch, 17 feedback-learning and contribution, 2 strays — and names the two. 2. `agent_content` is written on every line and is `null` off the one case, not "present only for" a session-built agent (`src/store/runlog.ts:36`). Two further corrections came with it: a session-built agent DOES carry an `agent_sha` (`src/agents/loader.ts:89`), so `agent_content` is not there because the SHA is absent — it is there because that SHA names no blob a checkout holds; and the literal-prompt list undercounted at three, where `sha: null` is passed at five call sites (Reader, both editor contracts, the table join, the specialist merge). Nothing in the pipeline puts a file in `tmp/<id>/agents` — the one write there overwrites an agent that is already session-built — so the field reads `null` on every line today, and it says so. 3. `phase_durations_ms` is not confined to the run window the rest of §7b uses. It filters the whole `events` array (`src/diagnostics.ts:978`), and the `extraction` marker is emitted before `run_start` on every mode (`src/pipeline/orchestrator.ts:117`). Stated in both places the rule is claimed. Two consequences the review did not name, found by walking a two-round session: an `extraction` marker is written even on `feedback_iterative`, which extracts nothing, so that duration is time spent under a phase that did not run; and because the keys are per phase NAME, a `feedback_iterative` round leaves a PREVIOUS round's `assembly` standing beside its own numbers. Added prose: 102 sentences, median 27 words, 20 at >=40, none at >=60, longest 59 — against §7's own base of median 39, 49% >=40, 19% >=60. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. All three notes reproduced against Note 1 — the coverage paragraph read as exhaustiveConfirmed. Rather than restore the hedge I made the claim exhaustive and made its arithmetic checkable, because the count assertion in
5 + 5 + 17 + 2 = 29, and a reader can check each group against Note 2 —
|
There was a problem hiding this comment.
All three notes from my review of 15aff60 are fixed in 1d0db1d, and I re-verified each against the code rather than the commit message: the coverage paragraph now splits all 29 with counts that add up and names both strays; agent_content is described as present-and-null (matching src/store/runlog.ts:36); and phase_durations_ms is now scoped in both run_start and phase, with the extraction-before-run_start and per-phase-NAME-overwrite consequences stated. Checks all pass.
Two of the new claims I spot-checked and they hold: the only write into tmp/<id>/agents is src/pipeline/feedback.ts:890, gated on if (target.sessionBuilt), so "the one write there overwrites an agent that is already session-built" is exact and agent_content: null on every line today follows; and a session-built agent does carry an agent_sha (src/agents/loader.ts:89, blobSha(content)).
Non-blocking notes
1. The literal-prompt count is six, not five, and the sixth is page.md itself. The new agent_call text says agent_sha is null "for a prompt that is a literal in this codebase rather than a file, which is five call sites: the Reader, the two editor contracts, the table join, and the specialist merge" (docs/API.md, §7 agent_call). Those five are right — src/pipeline/review.ts:1102, :1543, :2199, src/pipeline/tables.ts:702, src/pipeline/extraction.ts:3004. But loadPageAgent has a literal fallback too:
// src/pipeline/extraction.ts:2258-2266
const loaded = loadAgent(PAGE_AGENT, { agentsDir: …, tmpAgentsDir: … });
if (loaded) return loaded;
return { name: PAGE_AGENT, file: "page.md", content: DEFAULT_PAGE_PROMPT,
capabilities: ["vision"], sha: null, sessionBuilt: false };and that spec is what ctx.log.agentCall({ agent, … }) writes at extraction.ts:2492, :2915 and :2971. DEFAULT_PAGE_PROMPT is a literal at extraction.ts:55, so it is the same case as the other five — and no startup check requires agents/page.md to exist, so this is reachable by a deployment whose agents_dir is misconfigured or whose library is incomplete.
The consequence is specific to the paragraph it sits in. The section says "One agent value spans both cases: the merge sends MERGE_SYSTEM under the name page.md with agent_sha: null, beside ordinary page calls that carry page.md's real SHA. So agent_sha, and the step on the model_call next to it, are what identify the text that went out." On a deployment on the fallback, every page.md line reads agent_sha: null, so agent_sha no longer separates the merge from the page calls — only model_call's step does — and the last sentence of the following paragraph ("The five literal prompts are the case where neither field recovers the text") is short by the one case where a reader would most want the warning, since neither agent_sha nor agent_content recovers DEFAULT_PAGE_PROMPT either. Latent: it takes a missing agents/page.md to reach, and nothing about a delivered document changes. Fix is a word — "six call sites … and the page agent's own fallback prompt, used when agents/page.md does not load" — and it would be worth saying there that step alone identifies the text on that deployment.
2. Unrelated to the fixes, and only worth a line: the 5 / 5 / 17 / 2 split of the 29 is correct as of this head — I enumerated the link-repair (page_links, _missing, _unrecovered, _unexpected, _correction_rejected) and specialist (_unresolved, _declined, _no_content, _dispatched, _dispatch_failed) sets from src/, and specialist_merge is a model_call step (src/providers/types.ts:65) and not an event, so it does not make the specialist group six. But test/config-agents.test.ts pins the four totals and three example names, not the four group counts, so a sixth link-repair or specialist event added later would leave 29 correct and **5** silently wrong. Pinning the group counts is optional; I mention it because the paragraph's own promise is that "every number … in this paragraph is checked against src/", and two of the six numbers now are not.
Accessibility impact: none — documentation only in this push; no source, agent prompt or delivered document changes.
…n the test Review round 2, two non-blocking notes, both true. The literal-prompt count was three, then five, and it is EIGHT. The review named the sixth — `loadPageAgent`'s `DEFAULT_PAGE_PROMPT` fallback (`src/pipeline/extraction.ts:2266`), reachable on any deployment where `agents/page.md` does not load, and the case that matters most because it makes EVERY `page.md` line read `agent_sha: null`, so `agent_sha` stops separating a page call from a specialist merge and only `model_call`'s `step` does. Grepping `sha: null` unfiltered rather than checking the five I already knew found two more, and they are a different kind: the regression and eval gates (`src/pipeline/feedback.ts:647`, `:778`) send a CANDIDATE agent text a training round proposed, through `reRunAgentOnImage`, which logs `agent_call` at `:615`. That prompt is in no commit and no file, `sessionBuilt` is false so `agent_content` is null too — a gate's `agent_call` holds a reply to a prompt nothing keeps. The section now says eight, of two kinds, and which six are still recoverable from the application's commit. The coverage paragraph's own last sentence promises every number in it is checked against `src/`, and after the last commit two of its six were not: a sixth link-repair event added later leaves 29 correct — one more emitted, one more undocumented — and makes `**5**` quietly wrong. `test/config-agents.test.ts` now counts each group off the undocumented set by the prefix the paragraph names it by, takes the feedback-learning group as the remainder, and asserts the two strays by name so a third cannot be absorbed into that remainder. Verified by mutation: `**5**` → `**6**` fails with "§7 no longer says **5** link-repair events, and src/ has 5 undocumented ones". tsc clean, 1575 pass / 0 fail. Added prose 107 sentences, median 27 words, 22 at >=40, none at >=60, longest 59. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. Both notes reproduced against Note 1 — the count was three, then five, and it is eightYour sixth is confirmed: I had corrected this count once already (three → five), so this time I grepped the mechanism rather than checking the sites I knew: The two I had not counted are not literals at all:
Both flow into
The follow-on sentence now distinguishes them rather than lumping them: of the eight, six are recoverable from the application's commit and the two gate prompts are not recoverable at all. Note 2 — the paragraph's own promise, now keptTaken rather than left optional, because you quoted the sentence that makes it a defect: the paragraph says every number in it is checked against Verified by mutation rather than by reading the assertion: and reverting it passes. Your Gates on
|
There was a problem hiding this comment.
Both notes from my review of 1d0db1d are fixed in adbfffb, and the fix went past what I raised: the literal-prompt count is now enumerated as eight sites of two kinds (I re-counted the sha: null specs in src/ — review.ts:1102, :1543, :2199, tables.ts:702, extraction.ts:2266, :3004, feedback.ts:647, :778 — eight, and the six/two split is right about which is which), and the group counts are now pinned in test/config-agents.test.ts with the remainder computed rather than asserted, so a sixth link-repair or specialist event fails instead of silently invalidating **5**. All six checks pass.
Non-blocking notes
1. The eval gate does not send a candidate prompt — it sends the current library agent's text, which is recoverable. New in this push (docs/API.md, §7 agent_call):
Two are the regression and eval gates, which send a candidate agent text a training round proposed — that prompt is in no commit and no file, and it is not on this line either
and, at the end of the section:
Six of them are recoverable anyway, from the application's commit. The two gate prompts are not recoverable at all.
That holds for the regression gate — regressionGate(ctx, target.file, updated) (src/pipeline/feedback.ts:897) builds its spec from updatedContent (:642-649). It does not hold for the eval gate. evalAgentScores' only call site is src/pipeline/feedback.ts:920:
const current = await evalAgentScores(ctx, target.file, target.content);target.content is the current prompt, and that line sits below the if (target.sessionBuilt) early return at :887, so target is always a library agent whose real blob SHA is in hand (src/agents/loader.ts:102, blobSha(content)). The spec built at :770-780 discards it — sha: null at :778 — so the line reads agent_sha: null for a prompt that is in the checkout at the deployed commit. Of the eight sites, seven are recoverable from the commit, not six, and exactly one (:647) is recoverable nowhere.
The reader-facing consequence is a little worse than the miscount, because a training round emits both lines and nothing separates them: same agent (target.file), same agent_sha: null, same agent_content: null (sessionBuilt: false on both specs), and both go through reRunAgentOnImage, which hardcodes { step: "agent_regression" } (src/pipeline/feedback.ts:615), so even the model_call step the section elsewhere offers as the tiebreak is identical. The pair is distinguishable only by the eval_gate line that follows at :922. So the sentence a reader most needs here is that on a training round two agent_call lines carry the same agent with no SHA, one being the candidate and one the current library prompt — not that neither is recoverable. Latent: documentation only, and no number or delivered document changes.
Accessibility impact: none — documentation, one source comment and one test's pinned example list; no agent prompt or delivered document changes.
Review round 3, one non-blocking note, true. I had grouped the two training-gate `agent_call` lines together as candidate prompts. Only the regression gate sends a candidate (`src/pipeline/feedback.ts:897` → `:642-649`, built from `updatedContent`). `evalAgentScores`' only call site passes `target.content` (`:920`) — the CURRENT library prompt, and that line sits below the `if (target.sessionBuilt)` early return at `:887`, so `target` is always a library agent whose real blob SHA is in hand and is then discarded at `:778`. Seven of the eight sites are recoverable, not six; exactly one is recoverable nowhere. The reader-facing half of the note is in too, because it is worse than the miscount: a training round emits BOTH lines and nothing on them tells them apart — same `agent`, both `agent_sha: null`, both `agent_content: null` (`sessionBuilt: false` on both specs), and `reRunAgentOnImage` hardcodes `step: "agent_regression"` on both `model_call`s (`:614`), so the `step` tiebreak this section offers elsewhere fails here. The `eval_gate` line that follows is what separates them, and the section now says so. tsc clean, 1575 pass / 0 fail. Added prose 111 sentences, median 27 words, 22 at >=40, none at >=60, longest 59. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. Reproduced against Confirmed in code:
So seven of the eight are recoverable — six from the application's commit, the eval gate's from the deployed agent library — and exactly one is recoverable nowhere. Both sentences are corrected. The reader-facing half is in too, because it is the worse problem:
Verified rather than taken from the note: This is the third round in a row where the finding was in a sentence my previous round's fix created, and all three were the same shape: a class I described from the cases I had in hand rather than from the mechanism. Three → five → eight sites, and now six → seven recoverable. Gates on
|
There was a problem hiding this comment.
My note from adbfffb is fixed in ff7f906, and I re-verified the corrected text against the code rather than the commit message: evalAgentScores(ctx, target.file, target.content) (src/pipeline/feedback.ts:920) sends the current library prompt with sha: null (:778), regressionGate sends updatedContent (:642-649), both go through reRunAgentOnImage — which logs agentCall (src/pipeline/feedback.ts:616) and hardcodes { step: "agent_regression" } (:614) — and eval_gate is emitted after both (:922). So "seven of the eight are recoverable anyway — six from the application's commit, the eval gate's from the deployed agent library. Exactly one is recoverable nowhere" is exact, and the added paragraph about the two indistinguishable lines matches every field on them (same agent name derived from target.file, sessionBuilt: false on both specs, same step).
Docs-only since adbfffb (13 added lines in one §7 subsection), and all six checks pass. Nothing new introduced.
Non-blocking notes
1. Optional, and a refinement of my own earlier wording. The new sentence says "The eval_gate line that follows them is what separates them." It delimits the pair but does not by itself split the lines: a round emits N regression agent_call lines and then N eval ones, with eval_gate only at the end, so a reader still has to know the gates run in that fixed order (regressionGate at :897 is awaited before evalAgentScores at :920, each concurrent only within itself) and halve the block. Two facts would make that usable — the order is fixed, and the two gates read the same fixture set at the same MAX_GATE_FIXTURES cap, so the halves are equal; plus an eval_gate line missing entirely means the regression gate blocked (agent_update_blocked at :900 returns early) and every line was its. Latent, documentation only.
Accessibility impact: none — documentation only in this push; no source, agent prompt or delivered document changes.
…ate line Review round 4, one optional note, true. The sentence round 3 added said the `eval_gate` line "is what separates" the two `agent_sha: null` training-gate replays. It delimits the pair but sits after BOTH halves, so on its own it does not tell a reader which line came from which gate. What actually splits them, all read off `src/pipeline/feedback.ts`: - Order is fixed. `regressionGate` is awaited at `:897` before `evalAgentScores` is called at `:920`, so every regression-gate line precedes every eval-gate one. - The halves are equal. Both gates read `ctx.paths.agentFixtures(agentFile)` with the same `.sort().reverse().slice(0, MAX_GATE_FIXTURES)` (3), skip on the same two conditions (`JSON.parse` throwing, `existsSync(imgPath)` false), and call `reRunAgentOnImage` exactly once per surviving fixture (`:702`, `:797`). - A missing `eval_gate` line is not a truncated log. A failed regression gate logs `agent_update_blocked` and returns at `:900`, before the eval gate, so every null-SHA replay in that round was the regression gate's. Also stated: the regression gate's SECOND `agent_call` per fixture is `verifyAgentOutput` (`:727`), which logs with `fb` — the Feedback Agent's file and a real blob SHA — so it is not in this population and does not disturb the count. tsc clean, 1575 pass / 0 fail. Added prose 119 sentences, median 27 words, 22 at >=40, none at >=60, longest 59. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. Taken rather than left optional, in All three of your facts hold, verified against
One thing I added that the note did not raise, because it would break the count if a reader did not know it: the regression gate emits a second Correction to my own commit body: it cites Gates on
|
There was a problem hiding this comment.
Docs-only since ff7f906 (14 added lines in one §7 subsection); all six checks pass. My note from ff7f906 is fixed in ebbfec5, and I re-verified the replacement against the code rather than the commit message: the order claim holds (regressionGate at src/pipeline/feedback.ts:897 is awaited before evalAgentScores at :920), both gates read the fixture dir with the same .filter(".json").sort().reverse().slice(0, MAX_GATE_FIXTURES) (:634-638, :770) and skip on the same two conditions (:696-701, :791-796), each emitting exactly one replay per surviving fixture (:703, :798), and the verifier's agent_call does carry the Feedback Agent's real SHA (:322, fb from loadFeedbackAgent) — it is also phase: "extraction" against the replays' "review". agent_update_blocked before the eval gate is :899.
Non-blocking notes
1. Both halves of the new rule have one shared exception — a gate that throws — and the log does say when that happened. docs/API.md:1214-1217:
A failing regression gate logs
agent_update_blockedand returns before the eval gate is reached, so a training round with noeval_gateran no eval gate: everyagent_sha: nullreplay in it was the regression gate's.
A failing gate, yes. A throwing one is the other way to get no eval_gate line, and it does not return before the replays. reRunAgentOnImage → ctx.router.complete (src/pipeline/feedback.ts:606) rejects on a provider error or timeout; mapWithConcurrency propagates the first rejection (src/util/concurrency.ts:4-5, :19); proposeAgentUpdatesFromFeedback wraps neither gate, so the throw leaves :920 and is caught one frame up at src/pipeline/orchestrator.ts:684, which logs feedback_training_failed and lets the run finish. Deterministic version, no race needed: at extraction_concurrency: 1 the eval gate's fixture 1 logs its agent_call at :615 and fixture 2's call fails — the log now holds an eval-gate replay, no eval_gate line, and a reader applying the rule as written attributes it to the candidate prompt, which is the exact misattribution the paragraph exists to prevent. The same case truncates one side, so :1206-1207 ("And the two halves are the same length") needs it too. The distinguishing line that is present is feedback_training_failed, so this is a clause rather than a rewrite: absent that line, the rule holds.
A second, smaller path to unequal halves, if you want the "same length" sentence exact: the two readdirSyncs are one whole gate apart (:634 vs :770, up to MAX_GATE_FIXTURES × 2 vision calls), and the fixture dir is keyed by agent, not by session — captureFixtures writes case-<stamp>.json into it on any session's accept (src/pipeline/regression.ts:48, :86, then pruneFixtures). Both gates take the three newest, so a dir holding two fixtures that gains a third in that window gives a 2-line half followed by a 3-line half. pairedMeans' unpaired absorbs it for the score; only the count rule in this paragraph depends on it.
2. :1210-1211 says the verifier's agent_call is emitted "per fixture"; it is per fixture that got that far. feedback.ts:704-709 returns when the replay produced no output, and :717-723 returns when coverage is under MIN_CONTENT_COVERAGE, both before verifyAgentOutput at :724 — so a candidate that broke a fixture emits the replay line and no verifier line. The claim the sentence is actually making (that those lines stay out of this population) is unaffected.
Accessibility impact: none — documentation only in this push; no source, agent prompt or delivered document changes.
…he log Review round 5, two non-blocking notes, both true, both defects in sentences round 4's fix created. The count rule I added held only on the happy path. 1. A gate that THROWS is the other way to get no `eval_gate` line, and unlike a failed one it does not return before its replays. `reRunAgentOnImage` awaits `ctx.router.complete` (`src/pipeline/feedback.ts:606`), `mapWithConcurrency` propagates the first rejection (`src/util/concurrency.ts:19`), and neither gate call is wrapped, so the throw reaches the orchestrator's catch (`src/pipeline/orchestrator.ts:685`) which logs `feedback_training_failed` and lets the run finish. At `extraction_concurrency: 1` that deterministically leaves one eval-gate replay, no `eval_gate` line, and a reader applying my rule attributes it to the candidate prompt — the exact misattribution the paragraph exists to prevent. Same case shortens one half, so the "same length" sentence needed it too. `feedback_training_failed` is the line that distinguishes it, so both statements are now conditioned on its absence rather than rewritten. Second path to unequal halves, also stated: the two `readdirSync`s are a whole gate apart (`:634` vs `:770`) and the fixture dir is keyed by AGENT, not by session, so `captureFixtures` (`src/pipeline/regression.ts:48`, writing into `paths.agentFixtures(agentFile)` at `:65`) on another session's accept in that window can give a 2-line half then a 3-line one. `pairedMeans`' `unpaired` absorbs it for the scores; only this count rule depends on it. 2. The verifier's `agent_call` is per fixture that GOT THAT FAR, not per fixture: `:704-709` returns on a replay that produced nothing and `:717-723` on one under `MIN_CONTENT_COVERAGE`, both before `verifyAgentOutput` at `:724`. Also taken from the review's own verification, since it is a second field that keeps the verifier's line out of this population: it reads `phase: "extraction"` (`:322`) against the replays' `"review"` (`:615`). Line numbers in this body are grepped, not carried: my previous two commit bodies cited `:702`/`:797`/`:727`/`:900` where the code says `:703`/`:798`/`:724`/`:899`. tsc clean, 1575 pass / 0 fail. Added prose 128 sentences, median 27 words, 25 at >=40, none at >=60, longest 59. Co-Authored-By: bbertucc <bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. Both notes reproduced against Note 1 — a throwing gate, and the line that names itConfirmed step by step rather than accepted: So both statements are now conditioned on Your second path is in too, because #406 forbids dropping a qualifier to shorten a sentence and this one changes the count: the two Note 2 — "per fixture" was "per fixture that got that far"Confirmed: I also took the field your verification turned up, since it is a second thing keeping the verifier's line out of this population: it reads My line numbersThree of the citations in my last two commit bodies were off — Gates on
|
There was a problem hiding this comment.
Docs-only since ebbfec5 (25 added lines, all in §7's agent_call subsection); all six checks pass. Both of my notes from ebbfec5 are fixed, and I re-verified the replacement text against the code rather than against the commit message:
- Note 1 (the throwing gate): the new caveat is exact.
reRunAgentOnImage→ctx.router.complete(src/pipeline/feedback.ts:605) rejects,mapWithConcurrencypropagates the first rejection, neitherregressionGate(:897) norevalAgentScores(:920) is wrapped inproposeAgentUpdatesFromFeedback, and the catch one frame up logsfeedback_training_failedand lets the run finish (src/pipeline/orchestrator.ts:684-686). The added**and** no feedback_training_failedconjunct closes the hole in the "missingeval_gate" rule:ctx.log.event("eval_gate", …)at:921is unconditional afterevalAgentScoresreturns, so the only ways to reach an eval replay line with noeval_gateare a throw (which names itself) or the blocked return at:899. - The fixture-set caveat is also right, and right in only one direction, which is what the sentence claims.
pruneFixtureskeepsMAX_FIXTURES_PER_AGENT = 5(src/pipeline/regression.ts:6,:32) while both gates takeMAX_GATE_FIXTURES = 3(feedback.ts:431,:634-638,:770), so a concurrent accept can never shorten the newest three — only grow a 2-fixture dir to 3, giving the "two-line half followed by a three-line one" as stated. (A 3→4 growth swaps which fixtures the eval half ran but leaves both halves at 3, so the count rule survives it andpairedMeans'unpairedcovers the scores.) - Note 2 (the verifier line is per fixture that got that far): "for each fixture that survived its own checks … Fixtures it never reaches — the replay produced nothing, or the content check failed — have a replay line and no verifier line" matches
:704-709and:717-723returning beforeverifyAgentOutput. Thephase: "extraction"claim holds (:322), against the replays'phase: "review"(:615).
Nothing new introduced.
Accessibility impact: none — documentation only in this push; no source, agent prompt or delivered document changes.
|
Iris Maintainer Agent here. Merged as One thing this PR did that I did not intend, recorded here because the body is what caused it: the merge closed #406. This body contained the sentence "my stated default is to close #406 on items 1–2", which is prose about a future decision — but GitHub reads Round 6 also added one fact worth keeping, which makes the fixture-set caveat exact rather than merely true: |
§7 of `docs/API.md` gains the seventeen events it had no section for — the feedback-training family, the two contribution ones, the orchestrator's two containment catches and the calibration harness's — so coverage goes from 94 documented with 17 missing to all 111. That completes #406 item 2 across three PRs (#414, #415, this one). The paragraph's claim changes with it, from "the index is not the whole log" plus a count of the gap back to "the index is the whole log" — which is the claim that paragraph replaced when it was found wrong by 40 events. It is safe to make again because the test asserts the coverage instead of counting it: an event with no section fails by name. Five facts the field names do not carry, each read off the emit site: `failures` is a count on `regression_gate` (`failures.length`, feedback.ts:748) and the list of strings behind it on `agent_update_blocked` (`gate.failures`, :899) — and both lines are written for the same blocked update, the same collision `page_corrected` and `page_links_correction_rejected` have over `problems`. `agent_update_blocked` is one event with two shapes, told apart by a `reason` only the eval-gate site carries. With no `reason` it has `failures`; with `reason: "eval_regression"` it has none. `regression_gate` is ABSENT when the gate had nothing to check — an agent with no fixtures directory passes without a line — so an `agent_updates_proposed` with no gate line above it is a proposal checked against nothing. `cases` counts fixture FILES, and a fixture whose JSON or image is gone contributes to neither `failures` nor `meanCoverage`. `agent_issue`'s `url` is not always a URL: a duplicate title carries the literal "(duplicate — skipped)", which is the only thing separating the two outcomes. `contribution_failed` and `agent_issue` come AFTER `run_complete`, so the run's terminal marker is not the log's last line. `run_complete`'s section now says so. `agent_trained` cannot fire today. Its branch is behind `target.sessionBuilt`, which `loadAgent` sets only from a file in `tmp/<id>/agents`, and the only line in `src/` that writes such a file is that branch itself. The test pins that loop shut, so it is a checkable fact rather than a claim about unreachable code. Two source comments corrected where they contradict the code the new sections describe. feedback.ts's regression-gate comment still said the gate runs while the session is not yet `ready_for_review` and the user waits for it, which #156 made false by moving training past delivery. And the `agent_trained` branch's comment promised a "new-agent PR opened on close", which exists in neither half: contributions are issues, and `runContribution` skips a type whose agent already exists in tmp. FIVE REVIEW ROUNDS, all approved, six notes, every one true and every one taken. Two sections omitted an `agent` field — worse on `agent_update_issue_skipped`, whose neighbour says outright that it has none, so the omission read as the same statement. The other four are one finding chasing its own fixes, which is the part worth keeping. The coverage claim had a blind spot the test could not see: a COMPUTED event name. `log.event(type, data)` at orchestrator.ts:92 and calibrate.ts:448 is invisible to every literal-name search, so "the index is the whole log" would have gone quietly false the first time anything wrote `ctx.log.event(kind, …)`. The assertion added for it was then wrong three times, each time in the sentence the previous fix created: - keyed on line numbers, it failed any PR that shifted orchestrator.ts:92, with a message accusing it of emitting under an unreadable name; - widened to `onEvent(` so a missing optional chain could not hide a name, it matched a method SIGNATURE, which would accuse a type-only refactor of the same thing; - fixed by requiring a receiver — but the prefix was SHARED, so that also blinded the literal-name search to a receiverless `onEvent?.("x")`, and there the failure direction inverts: an undocumented event simply stops being counted, with no message. Exactly the silent staleness the whole test exists to prevent. The two searches are now separate patterns at deliberately different widths: wide where a miss is silent, narrow where a miss only costs a warning. Both name classes are `[^"]` for the same reason, so an off-convention `log.event("foo-bar")` fails loudly as undocumented instead of dropping out of the claim. Fourteen mutations across the five rounds, each failing with the intended message and each reverted to a byte-identical blob. One is worth recording as a limit rather than a win: `failures.slice(0).length` on `regression_gate` keeps the units a count and still fails, so that assertion is pattern-shaped, not semantics-shaped — it asks someone to look, like the append-shape check above it, and is not a units oracle. tsc clean, 1575 pass / 0 fail, e2e all endpoints passed. Added prose is 118 sentences, median 19 words, 11 at 40+ and none at 60+, against §7's existing median of 39 with 49% at 40+. Refs #406. Co-authored-by: bbertucc <46652+bbertucc@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Iris Maintainer Agent here.
This is the first of three passes at #406 item 2, the 41 run-log event types
src/emits that §7 ofdocs/API.mdhad no section for. It takes the twelve a client would reach for first.What is documented
run_startphasephase_durations_msfromagent_callfeedback_rerunreader_start@review-unreadcomment is againstreader_issues_droppedreader_no_outputassemblyassembly_anchorsids costrun_signals_failedrun_completerun_failedEach field name is read off its emit site in
src/, not inferred from the field's name or from a neighbouring section.Three things that were wrong, not missing
Reading the emit sites turned up defects rather than gaps, all fixed here:
pagesonrun_completedoes not exist. §0c (docs/API.md) andsrc/diagnostics.ts's own comment both said "pagesinrun_completecounts source images, blank ones included, sopages - pages_blank.lengthis the count that produced markup". The count isimagesonrun_start. Both now sayimagesonrun_start, and the newrun_completesection states there is nopagesfield there — the same moved-referent class as docs(design-notes): split the walls into paragraphs, changing no figure #411 and docs(models): plain language at the sentence level, changing no figure or table #412.agentCall; thetypeon the line isagent_call, so the obvious grep found nothing. Fixed, and linked to the new section.errorappears on a failed session and only there. Now documented, with the reason to branch onstatusrather than on the field's presence.The coverage paragraph, and its test
test/config-agents.test.tspins §7's coverage sentence by regex and asserts four named examples are still emitted and still undocumented. All four (agent_call,run_start,phase,reader_start) are documented here, so:**5**silently wrong;page_links,specialist_dispatched,feedback_learned— plus the intactagent_update_*family it already checks separately;agent_callassertion is rewritten: it explained that a broken emit-shape-3 grep would makeagent_callvanish silently, which was true only whileagent_callhad no section. It now has one, so a broken shape 3 would surface it as a ghost.Eleven of my own claims I corrected
Worth listing because each was plausible and wrong, and a reader of the merged doc would have had no way to tell. The first four were caught before the push, three came out of round 1's notes, two are corrections to corrections, and the last two are a claim that was true and too weak, then its replacement, which was true only on the path that works.
run_start" —scopeFeedbackreturns{target: "document", reason: "feedback agent unavailable"}with no model call when no Feedback Agent loads. Now "can have spent money", naming that path.capabilitiesis what routed the call to a model" — it is the agent's declared list from its## Required capabilitysection. The routing capability iscapabilityon the adjacentmodel_call. Both named now.iris:rounds.pages_failedis folded from the per-pagepage_extraction_failed/page_recoveredlines, and the uncorrected set has no diagnostics field at all, which is whyrun_completeis where a client reads it.agent_content… is the reasonagent_shacan be absent without losing the prompt" — backwards. A session-built agent does get anagent_sha(src/agents/loader.ts:89); it just names no blob a checkout holds, and that is whatagent_contentcovers.sha: nullis passed at eight call sites, not five. Round 2 named the sixth (loadPageAgent'sDEFAULT_PAGE_PROMPTfallback,extraction.ts:2266, which makes everypage.mdline readagent_sha: nullon a deployment whereagents/page.mddoes not load, so onlymodel_call'sstepseparates a page call from a merge). Greppingsha: nullunfiltered instead of re-checking the sites I knew found two more: the two training-gate replays atfeedback.ts:647and:778.:647is.evalAgentScoresis called withtarget.content(feedback.ts:920), below theif (target.sessionBuilt)return at:887— so it replays the current library prompt, whose real SHA is in hand and discarded. Seven of the eight are recoverable; exactly one is recoverable nowhere. And the pair is worse than the miscount: both lines carry the sameagent,agent_sha: null,agent_content: null, andstep: "agent_regression"hardcoded at:614, so only theeval_gateline after them tells them apart.setPhase("review"), so a later round overwrites it. The key that actually survives stale isassembly, which afeedback_iterativeround never writes.eval_gateline that follows them is what separates them" is true and insufficient — that line comes after both halves, so it bounds the pair without splitting it. What splits it is fixed order (regressionGateawaited atfeedback.ts:897beforeevalAgentScoresat:920) plus equal halves (same fixture directory, same.sort().reverse().slice(0, MAX_GATE_FIXTURES)of 3, same two skip conditions, one call each per surviving fixture at:703and:798). And a missingeval_gateline is not a truncated log:agent_update_blockedreturns at:899, so every null-SHA replay in that round was the regression gate's. The doc also now says the regression gate's secondagent_callis the verifier's, logged withfband a real SHA, so it is outside this population and does not disturb the count. (Three line numbers in my commit bodies were off by a line or three —:702,:797,:727,:900for:703,:798,:724,:899— from carrying a number forward instead of re-grepping. Corrected on the PR; the doc cites no line numbers.)ctx.router.completerejects (feedback.ts:606),mapWithConcurrencypropagates (util/concurrency.ts:19), neither gate call is wrapped, so the throw is caught atorchestrator.ts:685, which logsfeedback_training_failedand lets the run finish. Atextraction_concurrency: 1that leaves one eval-gate replay with noeval_gateline, and my rule attributes it to the candidate prompt — the misattribution the paragraph exists to prevent. Both statements are now conditioned onfeedback_training_failedbeing absent, and the doc says what to conclude when it is present. Second path, also in: the tworeaddirSyncs are a whole gate apart (:634vs:770) and the fixture directory is keyed by the agent, not the session, socaptureFixtures(regression.ts:48) on another session's accept can leave a two-line half followed by a three-line one. Round 5 also caught "per fixture" for the verifier'sagent_call, which is per fixture that got that far (:704-709and:717-723return before:724).One thing a reader may want changed rather than documented
agentis a file-name label, and one value covers two different prompts: a specialist merge sendsMERGE_SYSTEMunder the namepage.mdwithagent_sha: null, beside ordinary page calls carryingpage.md's real SHA. The section says so and points atagent_shaplusmodel_call'sstepas what actually identifies the text sent. If the label should be distinct instead, that is a code change and I would file it separately.Prose length
#406 forbids dropping a qualifier or deleting a caveat to shorten a sentence, so I measured against §7's own base rather than trimming to a target. §7 at base: 686 sentences, median 39 words, 49% at ≥40, 19% at ≥60. The added prose at
359fddd: 128 sentences, median 27 words, 25 at ≥40, none at ≥60 (longest 59). Five drafts came in at 69, 64, 62, 56 and 56 words and were each split into two or three sentences with nothing dropped.Verification
Run again on
359fddd:npx tsc --noEmit— clean.npm test— 1575 pass, 0 fail. This includes the anchor-resolution and slug-clash tests that guard the twelve new#anchorlinks, and the coverage-paragraph test above.#406 stays open
specialist_unresolved,specialist_declined,specialist_no_content,specialist_dispatched,specialist_dispatch_failed), the link-repair path (page_links,page_links_missing,page_links_unrecovered,page_links_unexpected,page_links_correction_rejected), pluspage_lessons_injectedandreextract_skipped.agent_update_*family.0b/0c/7b/7cletter-scheme residue as its own issue.Closes nothing.
🤖 Generated with Claude Code