Skip to content

fix(novelty): write the partial routing result when --max-usd stops the run - #406

Open
napetrov wants to merge 17 commits into
intel:mainfrom
napetrov:fix/cap-partial-save
Open

napetrov wants to merge 17 commits into
intel:mainfrom
napetrov:fix/cap-partial-save

Conversation

@napetrov

@napetrov napetrov commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Summary

When novelty routing --model --max-usd hit the cap, it exited with spend cap reached ...; nothing was written, and every paid call was lost. On real skill-repo runs this lost more than $9, one run at 444 of 456 calls.

Before After
Cap hit, single or --all-vs-all exit 1, no file exit 1; --out written with status: "partial" and partial: {reason: "spend_cap", calls_done, calls_planned}
Rates in a partial file n/a only finished calls are scored; a skill never reached has own_rate: null and its queries go to n_unscored
Console cap message only summary and cost lines; model line reads partial: N of M calls; warning when fewer than 5 calls answered
--out already holds a full result n/a partial goes to <stem>.partial.json next to it; the exit message names the path

A finished run is unchanged: JSON, stdout, exit code and files written are identical on base and head for 7 scenarios.

Evidence

  • New tests in tests/test_novelty_routing_spend_cap_partial.py fail on base and pass on head; make ci-local passes
  • Two fresh-context review passes: pass 1 found 2 blockers (a capped run wrote status: "ok"; the model line had no partial tag), both fixed and confirmed by probes in pass 2

Merge danger

  • Not covered: analyze, atoms, probe, shorten still write nothing at the cap (each writes once after several stages, so it is a larger change per command)
  • Known nits: two dated --out names share one .partial.json; when a call has no price, its paid reply is not kept; when the cap and failed calls both happen, the exit message names only the cap

🤖 Generated with Claude Code

A single-artifact or --all-vs-all routing run that hit the spend cap exited
with 'nothing was written', losing every call already paid for. It now
writes --out with the completed calls scored, a partial block
(reason spend_cap, calls_done, calls_planned), prints the summary and cost
lines, and still exits non-zero.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov requested a review from a team as a code owner November 5, 2026 01:29
Copilot AI balanced review requested due to automatic review settings November 5, 2026 01:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Capped runs can overwrite finished results in a fallback report file, while partial all-vs-all runs omit required summaries, partial-run labels, and low-sample warnings.

2 open issues
What changed in this PR

2 new findings.

This PR preserves partial novelty-routing measurements when --max-usd stops a run, rather than discarding completed work.

Changes:

  • Retains completed model calls, marks capped results as partial, and excludes unfinished work from scoring and repeat-noise calculations.
  • Adds partial-result saving and reporting while retaining a nonzero exit.
  • Adds regression tests and documents capped-run behavior.
File Description
dokimi/​tests/​test_novelty_routing_spend_cap_partial.py Tests partial results, scoring, output, and file preservation.
dokimi/​scripts/​growth_baseline.json Lowers the command module’s size baseline.
dokimi/​dokimi/​novelty/​routing.py Retains completed rows when the spend cap interrupts routing.
dokimi/​dokimi/​novelty/​client.py Removes the unconditional “nothing was written” claim.
dokimi/​dokimi/​novelty/​all_vs_all.py Aggregates partial results and marks unreached calls unscored.
dokimi/​dokimi/​commands/​novelty.py Saves partial results before exiting routing commands.
dokimi/​dokimi/​commands/​novelty_routing_report.py Adds partial-result persistence, reporting, and exit helpers.
dokimi/​docs/​skill-novelty.md Documents partial records, scoring, and reruns.
dokimi/​docs/​skill-novelty-walkthrough.md Explains routing’s exception to cap-triggered data loss.
dokimi/​changelog.d/​20261104_130000_napetrov_fix-cap-partial-save.md Announces partial-result preservation.

Review effort: Balanced

Comment thread dokimi/dokimi/commands/novelty.py Resolved
@exai-bot

exai-bot commented Nov 5, 2026 •

Copy link
Copy Markdown

OpenCodeReview: Commit 8560de6 was not reviewed. This PR has reached its review limit (6 reviews).

CI at `8560de6`: 7 still running, 7 passing, 2 skipped
Check Status
CLI interface diff still running
CodeQL (actions) still running
CodeQL (python) still running
Package install smoke test (py3.11) still running
Tests (py3.11) still running
Tests (py3.14) still running
Windows smoke test (py3.13) still running

To review this commit: re-request review from exai-bot in the Reviewers list.

<details><summary>Last review (d9a7538): no findings</summary>

Open threads from the bot: 0

</details>

Comment thread dokimi/dokimi/novelty/routing.py Resolved
@codecov-commenter

codecov-commenter commented Nov 5, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.00%. Comparing base (5deeb85) to head (39823f2).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #406      +/-   ##
==========================================
+ Coverage   94.99%   95.00%   +0.01%     
==========================================
  Files         363      364       +1     
  Lines       48348    48437      +89     
  Branches     8318     8332      +14     
==========================================
+ Hits        45928    46019      +91     
+ Misses       1531     1530       -1     
+ Partials      889      888       -1     
Files with missing lines Coverage Δ
dokimi/dokimi/commands/novelty.py 98.70% <100.00%> (+0.40%) ⬆️
dokimi/dokimi/commands/novelty_routing_report.py 98.51% <100.00%> (+0.32%) ⬆️
dokimi/dokimi/novelty/all_vs_all.py 99.31% <100.00%> (+0.07%) ⬆️
dokimi/dokimi/novelty/client.py 98.27% <100.00%> (+0.07%) ⬆️
dokimi/dokimi/novelty/noise.py 100.00% <100.00%> (ø)
dokimi/dokimi/novelty/routing.py 96.56% <100.00%> (+0.07%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

A capped run no longer overwrites a finished sidecar: the partial result
goes to the first of <stem>.partial.json, <stem>.partial2.json, ... that
is free or already partial.

A call the cap stops after it was paid for (price unknown) keeps its
reply: it is scored, then the run stops, and the query enters the noise
block when all its repeats are in.

A capped all-vs-all run prints the same summary as a finished one
(field line, attractors), with the model line `partial: N of M calls`
and the fewer-than-five warning.

partial_record and repeat_noise move to novelty/noise.py to keep
routing.py under its size cap.

Thread: PRRT_kwDOVBg7g86z4hq2
Thread: PRRT_kwDOVBg7g86z4hrt
Thread: PRRT_kwDOVBg7g86z4ihc
Thread: PRRT_kwDOVBg7g86z4ihf
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
No code change in this commit: the fixes are in 86903d8. The findings
nested in the Copilot review body have no thread, and the exai review
body has no finding of its own, so this commit names those reviews.

Thread: PRR_kwDOVBg7g88AAAABWG2GsA#1: already fixed in 86903d8
Thread: PRR_kwDOVBg7g88AAAABWG2GsA#2: already fixed in 86903d8
Thread: PRR_kwDOVBg7g88AAAABWG2GsA: already fixed in 86903d8
Thread: PRR_kwDOVBg7g88AAAABWG2g-w: already fixed in 86903d8

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The Codecov patch report found three uncovered lines in
novelty_routing_report.py: a sidecar this cannot read, a partial model
line with five answered calls, and the blind-skill warning without a
lexical arm. The code is right on head; these tests pin it.

Thread: IC_kwDOVBg7g88AAAABjDYjMg: accept, test-only

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov

napetrov commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Both findings are answered below (#1, #2), fixed in 64b71de.

"Avoid overwriting finished results at fallback output paths": fixed in 64b71de by Nikolay Petrov. A capped run writes to the first free <stem>.partial<N>.json. Pinned by test_a_partial_run_skips_a_sidecar_that_holds_a_finished_result.

"Show partial all-vs-all summaries when budget limits a run": fixed in 64b71de by Nikolay Petrov. A capped all-vs-all run prints the summary, partial: N of M calls and the fewer-than-five warning. Pinned by test_an_all_vs_all_run_stopped_by_the_cap_scores_the_queries_it_finished.

Its findings are the threads PRRT_kwDOVBg7g86z4ihc and PRRT_kwDOVBg7g86z4ihf, fixed in 64b71de.

Nothing to change: this summary reports 0 findings on 86903d8, so there is no claim to act on.

Covered in b5fdca5: tests for the unreadable sidecar and two more lines. Test-only; the code was right.

@antonwolfy antonwolfy 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.

The cap handling holds up: a capped routing run writes a partial result, keeps finished files safe, and still exits non-zero. One risk is left in the tests for the capped --all-vs-all --repeats N path, and one doc page is now stale.

What I ran, on a Linux review host at b5fdca5:

  • make ci-local: 29 passed, 1 failed, 7 skipped; 15,724 tests. The failure is test_unbalanced_quote_in_hook_completes_quickly (a 1 s timing bound, 2.09 s measured) plus two 60 s timeouts in test_audit_test_quality.py. Both files pass on a rerun (680 passed), and the PR does not touch them. The changed-line coverage step passes.
  • Reproductions for the code findings, with the LLM transport mocked.
  • A mutation run over the new code: 68 mutants, 19 of which pass the new tests plus tests/test_novelty*.py.

Other finding, outside the diff:

  1. Nit: docs/reviewing.md:79-80 still says --max-usd "writes no artifact when the cap trips" for novelty steps. That is no longer true for novelty routing --model, which now saves a partial file or a .partial.json sidecar. Suggestion: add "except novelty routing, which saves the calls it made as a partial result", as the walkthrough page now does.

Checked, no issues

  • Sidecar naming: a finished --out sends the partial result to <stem>.partial.json, then .partial2.json, and the exit message names the path.
  • Documented keys: partial, status, n_calls, n_unscored, own_rate: null and the noise exclusion match the output of a mocked capped run.
  • Cap on the first call: zero rows, calls_done 0, no crash, both modes.
  • Failed calls with the cap: counted in n_failed, and the refused calls land in n_unscored.
  • Repo rules: changelog fragment, growth baseline, docs nav and the offline tests pass; the new tests write only under tmp_path.
  • Commits: all four carry Signed-off-by.

Comment thread dokimi/dokimi/novelty/all_vs_all.py Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to 664b082, which fix the nit from my review:

  • 664b082: dokimi/docs/reviewing.md:79: the --max-usd sentence now says novelty routing --model is the exception and saves the calls it made as a partial result.

make ci-local at 664b082: 30 passed, 0 failed, 7 skipped.

Not approving yet. Still open: the risk from my review.

The capped --all-vs-all --repeats N path had no test with more than one
repeat. The new test stops a run mid-query over three skills and checks
calls_done and calls_planned, n_unscored for the stopped and the never
reached skill, and the noise block. Each of the four mutants in the
review fails it.

Thread: PRRT_kwDOVBg7g860Dr0t
Thread: PRR_kwDOVBg7g88AAAABWOVf0w

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov

napetrov commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 664b082 by Anton Volkov: reviewing.md:79 names the routing exception.

Open risk (the repeats test gap) is fixed in 2fe172c: see the thread.

Baseline: commands/novelty.py at 921, its merged length (main 928, branch 1014).
Name the capped steps by their intel#396 paths (skill novelty analyze/atoms/probe,
skill shorten) in skill-novelty.md and the changelog fragment.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

d9a7538: merged main after #396. The growth baseline records novelty.py at its 921 merged lines, and skill-novelty.md and the changelog fragment name the moved skill novelty analyze/atoms/probe and skill shorten commands.

@antonwolfy antonwolfy 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.

Review at d9a7538. Both points from my last review are fixed: the reviewing.md sentence names the routing exception, and the new repeat-run test pins calls_done, calls_planned, both n_unscored values and the noise block. The problems are at the edges of the unpriced-call path and in two measurements the tests do not pin. Three things are worth fixing before merge, plus one nit inline.

What I ran, on a Linux review host at d9a7538:

  • make ci-local: 30 passed, 0 failed, 7 skipped. Changed-line coverage passes.
  • Reproductions for the code findings, with the LLM transport mocked.
  • A mutation run over the new code: 67 mutants applied, 22 of which pass the new test file. Two of the survivors are inline below.

Checked, no issues

  • Uncapped output: a finished single-artifact run writes the same routing.json on the PR's base and on head, apart from the artifact path.
  • Schema: routing files are written with write_text, carry no schema_version and are not validated against novelty.v1.json, so status and partial need no schema change.
  • Sidecars: a finished --out sends the partial result to <stem>.partial.json, then .partial2.json; the exit message names the path.
  • Commits: all non-merge commits carry Signed-off-by.
  • Docs: the keys and console lines documented in skill-novelty.md match a mocked capped run, single and --all-vs-all --repeats 2.

Comment thread dokimi/dokimi/novelty/routing.py Resolved
Comment thread dokimi/dokimi/novelty/routing.py Resolved
Comment thread dokimi/dokimi/novelty/routing.py Resolved
Comment thread dokimi/docs/skill-novelty.md Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

@antonwolfy antonwolfy 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.

I pushed a commit per nit, up to 8560de6, which fix the nit from my review:

  • 8560de6: dokimi/docs/skill-novelty.md:762: the resume advice now says DOKIMI_RESPONSE_CACHE must also have been set on the capped run.

make ci-local at 8560de6: 29 passed, 1 failed (host timing flake, green on rerun), 7 skipped.

GitHub CI has not been waited for at this head: the Actions queue held the runs for over an hour, so the fix is not checked there yet. Locally the fix commit passes the CI steps; the only failures are load-sensitive timeouts in files the docs change does not touch.

Not approving yet. Still open: the bug and the two risks from my review.

A run whose last planned call comes back unpriced has nothing left to
run, so it is complete: no partial block, status ok, exit 0. The stop
is kept as cap_stop, not as partial, so all-vs-all still asks no later
skill unmetered after an unpriced reply.

Thread: PRRT_kwDOVBg7g860fvOE
Thread: PRRT_kwDOVBg7g860fvON
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860fvON
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Thread: PRRT_kwDOVBg7g860fvOQ
Review: PRR_kwDOVBg7g88AAAABWbHvBQ
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

# Conflicts:
#	dokimi/dokimi/commands/novelty.py
#	dokimi/dokimi/commands/novelty_routing_report.py
#	dokimi/dokimi/novelty/routing.py
Review nits on the merge resolution: drop the second Path import under
TYPE_CHECKING, and say the partial marker is set only when the cap stops
the arm before every planned call.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov

napetrov commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Not approving yet: open items are 51bcf96, daa7270, c807886.

Nit fixed in 8560de6 (Anton Volkov). Open items: see the threads above.

Nothing to change: novelty.py is 921 lines, as in growth_baseline.json, and the command names in skill-novelty.md match the code.

The growth ratchet caps a function at complexity 10; model_routing reached
11 with the cap's stop logic. The repeat loop of one query moves to
_ask_query, with no change in the calls, rows or order.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

This branch has not been deployed

No deployments
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.

6 participants