Skip to content

review: real metering on salvage paths; gate lastText on a delivered result record - #377

Merged
jwbron merged 2 commits into
mainfrom
jwbron/runner-metering-and-riders
Aug 25, 2026
Merged

review: real metering on salvage paths; gate lastText on a delivered result record#377
jwbron merged 2 commits into
mainfrom
jwbron/runner-metering-and-riders

Conversation

@jwbron

@jwbron jwbron commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #362's final review fold (9 collapsed observations), plus riders from the other merged PRs' folds. The one that made this timely rather than just valid: the catch-path salvages returned usd: 0, turns: 0 for sessions the metered proxy really charged, and the runner's own comment says the Stop hook makes the error_max_turns ending common for free-text agents, so dispatch's perAgent entries and the totalUsd summed over them systematically undercounted. PRA-45's done-when is a week of live artifacts; an undercounted cost surface pollutes exactly that data.

  • Metering: a non-success result record still carries total_cost_usd/num_turns, so the runner captures them before throwing and every salvage path (captured, provisional, lastText) reports them. Zeros remain only when the stream dies with no result record at all, which is the genuinely unknowable case.
  • lastText gating: the salvage fired on any error, so a hard failure mid-stream returned mid-investigation narration as output, failed the contract parse, and burned the malformed-output re-dispatch (same shape as the timeout case that was already guarded). It now requires a delivered result record.
  • Stop hook: an agent whose submission the contract or prose gate bounced was told "you have not delivered your result yet, call the tool ONCE now", which is false and misleading mid-bounce. The reason now branches on whether a contract-valid submission exists. Also swaps the Promise.resolve idiom for the async form the sibling handler uses and reflows the one 99-column comment line.
  • Tests: last-of-several assistant texts, provisional-beats-lastText in the catch path, metering carried through both salvages, and the hard-failure rethrow.

Riders: the adjudicated cross-file hard-negative fixture now asserts its calibration band (>=6 shared bigrams, >=0.35 overlap, jaccard < 0.2) with the exported primitives instead of only documenting it, so a fixture drifting out of band fails instead of pinning nothing; the ack guard's never-firing isBotLogin clause is documented as belt-and-suspenders (staged threads carry GraphQL's bare logins; the early return and sameLogin are the real guards) and the test's impossible spelling is annotated; the consumer-bump skill stops describing the --repo footgun as silent now that #372 made the checker fail loudly; and the repo-root test uses the file's check helper.

1934 tests green locally.

KORE-2512

…result record

From the merged #362's review fold: the catch-path salvages returned usd 0,
turns 0 for sessions the proxy really metered, and the Stop hook makes the
error_max_turns ending common for free-text agents, so dispatch's perAgent
and totalUsd systematically undercounted, polluting exactly the live-week
artifacts PRA-45 watches. Non-success result records still carry
total_cost_usd/num_turns; the runner captures them before throwing. The
lastText salvage now requires a delivered result record (a hard failure's
narration burned the malformed-output re-dispatch), and the Stop hook's
reason distinguishes never-called from bounced-and-must-correct.

Riders from other merged-PR folds: the cross-file hard-negative fixture
asserts its calibration band, the ack guard's dead isBotLogin clause is
documented as belt-and-suspenders with the test spelling annotated, the
consumer-bump skill stops calling the now-guarded --repo footgun silent,
and the repo-root test uses the file's check helper.
@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9ca0d43

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
review Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Review live A/B

No reviewable delta: review.md is byte-identical in both arms (baseline origin/main, sha 1a6020971f11), so the extracted prompts and the orchestrator body match and no arms were run. Pass --force-arms for a deliberate wobble control.

@khan-actions-bot
khan-actions-bot requested review from a team, kevinb-khan and somewhatabstract and removed request for a team August 24, 2026 18:34

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested — see inline comments.

review details review-v1.18.0 | schema 2 | depth full | re-review scoped blocking-only | enable holistic,completeness,test-adequacy,first-principles,conventions,documentation

Comment thread workflows/review/lib/dispatch-runner.ts
Comment thread workflows/review/lib/dispatch-runner.ts Outdated
// mid-investigation narration, which would fail the contract
// parse and burn the malformed-output re-dispatch, exactly like
// the timeout case above.
if (ended && lastText !== undefined) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note (non-blocking): Gating lastText on ended also removes the second attempt, because a thrown run is shed rather than re-dispatched. I traced the null path: a throw makes dispatchAgent return null, and dispatch.ts's finder loop hits if (output === null) { shedDimension(); continue; } before parseWithRetry, so the malformed-output path this change stops burning was also the only retry a hard mid-stream failure got. Introduced by this change; the timeout branch above already accepts the same trade, so this may be the intended symmetry rather than a defect.

review details found by correctness-reviewer | also flagged by holistic: Gating lastText on `ended` removes the only retry for a transient mid-stream death.

Comment thread workflows/review/lib/dedup-adjudicated.test.ts Outdated
Comment thread workflows/review/lib/dispatch-runner.ts Outdated
Comment thread workflows/review/lib/dispatch-runner.ts
…through openThreadScore

The blocking catch: the mid-bounce reason branch had no test, and while
adding one the first-principles sibling proved the branch itself wrong:
provisional only covers prose-gate bounces (a contract bounce returns
before provisional is set), so a contract-bounced agent still heard 'you
have not delivered'. The reason now branches on a submitAttempts counter
incremented before the contract check, covering both bounce kinds, with
tests for each plus the never-called case. The band assertion scores
through a newly exported openThreadScore instead of re-deriving its
formula and threadProse's strip by hand, and the changeset stops claiming
zeros remain only without a result record (the rethrow path is a second,
pre-existing case).
@khan-actions-bot
khan-actions-bot requested a review from a team August 24, 2026 19:06
@github-actions

Copy link
Copy Markdown
Contributor

Guidance for reviewers

Triage notes for reviewers: risky files by owning team, repeated changes, and files excluded from review.

github-actions (4 files)
File Reason
dispatch-runner.ts Decides what a sub-agent delivers and what its run is recorded as costing, so the salvage gating and metering changes here alter what every consumer PR posts and reports.
dedup-threads.ts Scoring in the shared enforcement layer decides what gets suppressed on every consumer PR; this hunk only widens openThreadScore's visibility and leaves the scoring body untouched.
rereview.ts The acknowledgment guard decides whether a reply reads as the author conceding; this hunk is a comment plus a reflow, with the predicate's four conjuncts unchanged.
SKILL.md Prose that steers a future consumer-bump run, so the corrected --repo description has to match what the checker actually does.

Common patterns

2 files: Salvage-path metering: hardcoded usd: 0, turns: 0 replaced with real values captured from the non-success result record (endedUsd/endedTurns in impl; updated assertions in tests).

-                    usd: 0,
-                    turns: 0,
+                    usd: endedUsd,
+                    turns: endedTurns,

2 files: isBotLogin annotated as a belt-and-suspenders clause (staged threads carry GraphQL bare logins caught by isReviewBotAuthor; isBotLogin only fires if staging ever switches to the REST spelling).

-                    !isBotLogin(comment.author) &&
+                    // Belt-and-suspenders only: staged threads carry
+                    // GraphQL's bare logins, which never end in `[bot]`, so
+                    // this clause fires only if staging ever switches to the
+                    // REST spelling.
+                    !isBotLogin(comment.author) &&
review details review-v1.18.0 | schema 2 | depth full | re-review scoped blocking-only | enable holistic,completeness,test-adequacy,first-principles,conventions,documentation

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 of 6 prior review threads resolved; 1 still unaddressed as of 9ca0d43:

1 non-blocking thread still open
Note: divergence tripwire re-armed a full review (unreviewed share 0.44).
review details review-v1.18.0 | schema 2 | depth full | re-review scoped blocking-only | enable holistic,completeness,test-adequacy,first-principles,conventions,documentation

});
reason:
submitAttempts === 0
? "You have not delivered your result yet. Call the submit_result tool ONCE now, passing the ENTIRE JSON object your output contract specifies as its `result` argument; do not paste the JSON as a message."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): The never-delivered Stop-hook reason is the one branch no test pins. grep "have not delivered" workflows/review/lib/*.test.ts returns only the two negative assertions in the new bounce tests (dispatch-runner.test.ts:491 and :511); the only test of the zero-attempt branch (dispatch-runner.test.ts:464) asserts toContain("submit_result"), a substring both reason strings share. So collapsing the new submitAttempts === 0 ternary to always emit the "was rejected" arm keeps the whole suite green while telling an agent that never called the tool to correct a submission it never made — the mirror of the falsehood this PR removes. (Inverting the ternary would be caught by the two new tests; the collapse is the mutation that escapes.) One positive assertion in the existing test closes the loop.

A sketch, not a committable replacement:

        const first = await hook!();
        expect(first).toMatchObject({decision: "block"});
        expect(String(first["reason"])).toContain("submit_result");
        // The agent never called the tool: this branch, not the
        // mid-bounce "your submission was rejected" one.
        expect(String(first["reason"])).toContain("have not delivered");
        expect(String(first["reason"])).not.toContain("was rejected");
review details found by test-adequacy

@jwbron
jwbron merged commit 524f661 into main Aug 25, 2026
10 checks passed
@jwbron
jwbron deleted the jwbron/runner-metering-and-riders branch August 25, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants