Repository navigation
fix(novelty): a capped run probes atoms spread over the whole input - #358
Conversation
Settles intel#293. A capped novelty run took atoms in document order and stopped extraction at the cap, so its rates described the opening of the input (at triage, about a quarter of the root AGENTS.md of the dpnp guidance) and moved when sections were reordered. Extraction now reads every span. Of the M atoms extracted, a cap of n keeps those at floor((i + 0.5) * M / n), in extraction order (document order; on a bundle run, the files in bundle order), via the new atoms.spread_sample. There is no seed: the same extracted list gives the same sample. With M <= n every atom is kept, as before. novelty analyze and novelty atoms both go through extract_atoms, so probe and shorten read the same sample from atoms.json. - sampling is cap-spread:<n> for new runs; extraction.sampled records {"atoms", "of", "rule"} and extraction.reached is null. novelty.v1 is widened in place: older cap-per-artifact artifacts with a reach still validate and render as before. - The cap warning, the novelty.md header and the coverage: line say how many of the input's atoms were probed and at what stride. - The shortening forfeit says candidates are scored against a spread sample of the cap's atoms, so cutting a span with no sampled atom looks free. - --estimate-only counts every extraction batch over the expected uncapped atoms; probing stays at the cap. The held-out calibration checks still project the runs as they were measured. - Decision D017 records the rule and leaves re-measuring the capped tier profiles to follow-up work. Docs and a changelog fragment (Changed) follow the code. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 20 selected item(s). Review details
|
There was a problem hiding this comment.
🟡 Changes recommended
The schema accepts zero-atom samples that crash rendering, and capped sampling uses extractor reply order instead of document order.
2 open issues
What changed in this PR
2 new must-fix findings.
This PR addresses #293 by changing capped novelty runs from measuring the input’s opening to selecting a deterministic spread of extracted atoms across the input.
Changes:
- Extract every batch before applying the atom cap, including across bundled resources.
- Record and report spread sampling while preserving support for older artifacts.
- Update cost estimates, regression tests, documentation, and decision D017.
| File | Description |
|---|---|
dokimi/tests/test_novelty_tiers.py |
Tests revised forfeits and full-extraction estimates. |
dokimi/tests/test_novelty_settings_parity.py |
Accounts for intentional sampling and estimate changes. |
dokimi/tests/test_novelty_report.py |
Tests spread reporting and legacy rendering. |
dokimi/tests/test_novelty_cli.py |
Tests the sampled-coverage headline. |
dokimi/tests/test_novelty_atoms.py |
Tests deterministic selection and extraction of every batch. |
dokimi/tests/test_novelty_analyze.py |
Tests sampling metadata, bundles, and shortening inputs. |
dokimi/scripts/growth_baseline.json |
Lowers the extraction complexity baseline. |
dokimi/research/records/decision/D017.md |
Records the sampling decision and calibration follow-up. |
dokimi/dokimi/schemas/novelty.v1.json |
Adds optional sample metadata and updates descriptions. |
dokimi/dokimi/novelty/tiers.py |
Updates shortening forfeits and extraction estimates. |
dokimi/dokimi/novelty/report.py |
Renders sample details while retaining legacy reach text. |
dokimi/dokimi/novelty/atoms.py |
Replaces early stopping with post-extraction spread selection. |
dokimi/dokimi/novelty/analyze.py |
Records the new sampling rule and warning. |
dokimi/dokimi/commands/novelty.py |
Updates CLI warnings and --all-atoms help. |
dokimi/docs/skill-novelty.md |
Documents sampling, metadata, costs, and compatibility. |
dokimi/docs/skill-novelty-walkthrough.md |
Explains capped sampling across bundled files. |
dokimi/docs/reviewing.md |
Updates resource sampling and cost guidance. |
dokimi/changelog.d/20261101_120000_antonwolfy_novelty-cap-spread.md |
Announces the changed capped-run behavior. |
Review effort: Balanced
Settles the review of the cap-spread change (intel#293): - When the cap binds, the atoms are sorted (stably) by the input position of the earliest span each cites before the stride is taken, so the sample is in document order whatever order the extractor's reply lists a batch's atoms in. A cap that does not bind keeps the extraction order, so atoms.json is unchanged for those runs. - extract_atoms refuses a cap below 1 with ValueError before any call, and extraction.sampled.atoms has a minimum of 1, so no record asks the renderers to describe a stride over zero atoms. - When spans went unread, the cap warning, the novelty.md header and the coverage line say the sample is spread over the atoms extracted from the spans that were read and how many spans were not, instead of claiming the whole input was read. cap_warning takes the extraction record. - The docs and D017 say the stride runs over the concatenated atom list of the selected files, so a file with few atoms may get no probe; that the same extracted atom list, not the same text, gives the same sample, and that reusing atoms.json probes identical atoms; and that extraction cost changes for every run whose cap was reached before its last batch, an exact-cap artifact included. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #358 +/- ##
==========================================
+ Coverage 94.29% 94.32% +0.02%
==========================================
Files 308 309 +1
Lines 39322 39460 +138
Branches 6651 6676 +25
==========================================
+ Hits 37079 37220 +141
Misses 1430 1430
+ Partials 813 810 -3
🚀 New features to boost your workflow:
|
Settles the two review comments on e8fdf95 in intel#358 (issue intel#293). Extraction now reads every span, so a run whose atoms exactly meet the cap keeps every atom and records `extraction.sampled` as null. Two places still read `n_atoms >= cap` as capped: - `tiers.forfeits` took the post-cap atom count, so such a run carried the `sampled` forfeits and the spread-sample shortening forfeit in its warnings and `run_config.tier`. It now takes `sampled`, whether the cap left atoms out; `analyze` passes `extraction.sampled is not None` to `settings.warnings` and `tier_record`. Before extraction (estimates, the CLI's up-front tier listing) `sampled` is None and the cap is still assumed to bind. - `spans.span_summary` set `capped_atom_set` from the same count. It now takes `capped` from the same state, in `analyze` and in `novelty atoms`. Artifacts already written keep what they stored. The `capped_atom_set` schema description and docs/skill-novelty.md say when the flag and the forfeits apply. The changelog fragment says `extraction.sampled` is the {"atoms", "of", "rule"} object only when the cap leaves atoms out, and null when it keeps every atom. Tests: a study run whose atoms meet the cap has no sampled or shortening forfeit and `capped_atom_set` false, one atom over the cap has all three; `novelty atoms` flags the set the same way; the shortening-forfeit test no longer expects the warning at equality. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Settles the review point on intel#358 that a spread sample's estimate of the whole input's per-atom rate went unqualified. A fixed stride can fall in step with a repeated structure: with M = 100 atoms and a cap of 25 it always takes atoms 2, 6, ..., 98, so an input of same-shaped four-atom sections, each with its new fact in the same place, can give a sampled rate far from the whole input's, and re-extracting the same atoms gives the same sample. The cap passage of docs/skill-novelty.md, the changelog fragment and decision D017's rationale now say so, and recommend --all-atoms for an artifact of repeated, same-shaped sections or a rate that decides something. The stride stays seedless, as D017 decided; no code changes. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Bring in main at ad734f5 (intel#339, a SCIP code index reader; intel#340, eval coverage against a frozen need graph). No conflicts. CI's schema_compat compares the branch with main, which gained eval_coverage.v1 and need_graph.v1 after this branch's base. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Review rounds at
|
napetrov
left a comment
There was a problem hiding this comment.
Verdict: no blocking finding; one nit. Posted as a comment, not an approval. Head 9e72b02, merge base ad734f5, lane L2.
| # | Finding | Label | Provenance | Probe |
|---|---|---|---|---|
| 1 | an uncapped run (M <= N) reads cap-spread:N in atoms.sampling, the label the Risk section names as the capped-run signal (C7) |
nit | pre-existing (base labels it too) | 10 spans, cap 25, head vs base |
Earlier bot threads (exai-bot T1 to T11, Copilot T7 and T8) are fixed on head, except the docs wording which I read but did not re-probe line by line. The zero-atom division and the reply-order sort were probed.
Not probed here: the extraction-batch projection in the estimate (round(extracted / 12)) against the real 25-span batches; the PR says no measured run has checked it. The sampled forfeit text in tiers still says the rate is "not the whole input", which the new docs contradict (wording only). The litellm-dependent tests did not run on this host; CI on head is green.
Coverage: passes at level high (plain, edge, dedupe, VERIFY on 4 candidates); probes on head and base for the label, the unread-span path, and the sample table; schema minimum; prior threads. Not looked at: the CLI end to end; every docs line.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Review of intel#358 (comment 4438896876): `atoms.sampling` came from `max_atoms` alone, so a run whose atoms met or fell under the cap (`extraction.sampled` null, every atom probed) still read `cap-spread:<n>`. A run with 10 atoms under the triage cap of 25 was labelled `cap-spread:25`. `analyze` now derives the label from the same state as the forfeits and `spans.capped_atom_set`: `cap-spread:<n>` when `extraction.sampled` is set, `full` otherwise. Readers of the label only print or copy it, so such a run renders as a full one. The schema description, the cap passage of docs/skill-novelty.md and the changelog fragment say when each label is written. The review also named the `sampled` forfeit, which called `added` a rate over "the atoms the cap kept, not the whole input", although a spread run reads every span. It now says the rate is over the sample of extracted atoms the cap kept, not over every atom; the docs quote and the settings-parity test follow. Tests: the cap-met run is labelled `full`; a 10-span input under cap 25 is labelled `full` with `sampled` null and renders "10 atoms (full)"; the cap-met/cap-binds pair pins both labels. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Review round at
CI is green on |
|
Review at 82d576a. The sampling change is right: the stride runs in document order, a cap that keeps every atom counts as a full run everywhere (label, forfeits, What I ran, at 82d576a:
Checked, no issues
Comments on lines outside the diff:
|
extract_atoms checked the cap after the loop that resets every span's unread mark, so a cap below 1 raised before any model call but after it had changed the caller's spans. The check now runs first, and the cap-0 test asserts the spans keep the unread marks they came with. Settles the review comment on intel#358 that _check_cap runs after the side effect it is meant to precede. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
--estimate-only counted extraction as one call per 12 atoms expected before the cap, and the token rule expects one atom per 23 tokens. For triage on 300 spans and 19,000 tokens that gave 69 extraction calls, while the run asks one call per 25-span batch: 12. estimate_calls now takes n_spans in place of n_extracted and counts ceil(n_spans / BATCH_SPANS) extraction calls, with BATCH_SPANS the default batch size of extract_atoms, now a shared constant. RunSettings.estimate passes the span count. Without n_spans the old per-12-atoms rule stays, which is how the held-out calibration runs, whose extraction stopped at the cap, are projected. The per-call token profiles are unchanged. Settles the review comment on intel#358 that the estimate counts extraction calls from the pre-cap atom guess. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
…ample A capped run extracts every span but indexes spans, writes atoms.json and shortens from the sampled atoms only. D017 now names that gap and links the issue that tracks it. Settles the review comment on intel#358 that asked for the issue to be linked from D017. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Round for @vlad-perevezentsev's review, at 9f8bdf8 (CI green):
|
Review of intel#380 (comment 4458106517). The document-order cap before intel#358 wrote `extraction.reached` only when the cap bound, measured to the last kept atom's span. With cap 1 and a reply listing s0002's atom before s0001's, `reached.spans == reached.of` while s0001's only atom was dropped, and holds_only_a_capped_sample read that as nothing lost, so `shorten` could treat s0001 as empty with no warning. - holds_only_a_capped_sample (commands/novelty_atoms_file.py) is now true for a file with no sampled_atom_ids key, `cap` set and any `extraction.reached` object, whatever its counts. - warn_capped_sample stays the one warning: when `reached.spans` equals `reached.of` it says the file *may* hold only the atoms its run probed, since the cap was met at the last span and may have cut that reply. - The reach-end test now expects the qualified warning; a unit case covers a `reached` at the last span. skill-novelty.md and the changelog fragment say when the warning fires. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
) * fix(novelty): a capped run indexes spans from every extracted atom Closes #371, raised in the review of #358: a capped run extracted every span but kept only the cap's spread sample of atoms, so spans, attribution, atoms.json and shortening saw the sample only. At --tier standard, 200 one-atom spans left 140 spans with no atom_ids and frac_carrying_nothing 0.70, and a shortening could cut them as free. - extract_atoms returns every extracted atom; sample_atoms picks the cap's spread sample (same rule, same order). analyze and `novelty atoms` keep both: faithfulness, the need-graph match, probes, measurement, the conflict check, carrier confirmation and the shortening oracle run on the sample, so no stage makes more model calls. - Spans (index_atoms / span_summary), span labels, dead weight, sole carriers and shortening's structure use every atom. build_candidates takes scored_atoms for the oracle's knowledge and value score; an atom outside it adds span_value's no-outcome default to its span's density. - atoms.json holds every atom and names the sample in sampled_atom_ids (null when the cap kept every atom). A file without the record reads as every atom sampled; one that names an atom it does not hold is refused. `novelty probe` and `novelty shorten` measure the recorded sample by default and say so; --all-atoms measures every atom in the file. - spans.capped_atom_set is false on new runs, so the dead_tokens check judges a capped run and still skips older capped artifacts. The harmful_atoms check reads extraction.sampled, so a 0 from a sampled conflict check is still not cleared. - The cap no longer forfeits frac_carrying_nothing; the shortening forfeit says knowledge and value are retention of the sample. - Docs (skill-novelty.md, D017, a new implref R041), novelty.v1 field descriptions, the settings-parity intended changes, and a Fixed changelog fragment. The atoms.json readers move to commands/novelty_atoms_file.py to keep commands/novelty.py under its growth baseline. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn when an older capped atoms.json holds only its sample Follow-up to the review of the #371 fix. An atoms.json written by a capped `novelty atoms` run before sampled_atom_ids existed holds only the atoms its run probed. recorded_sample reads it as every atom sampled, and probe, shorten and view index spans from it with no word, while the forfeit that said a cut of an unsampled span looks free is gone. So `shorten --out` on such a file could still cut spans as free, silently. - holds_only_a_capped_sample (commands/novelty_atoms_file.py) is true when the file has no sampled_atom_ids key and its record shows a cap that left atoms out: extraction.sampled set (the spread cap, #358 on), or a cap with extraction.reached short of the input's last span (the document-order cap before it). A file this version writes always has the key, so one whose cap kept every atom (null) is not flagged. - `novelty probe` and `novelty shorten` (through measured_atoms) and `novelty view` (through load_view_inputs) print a WARNING on stderr: the file holds only the atoms its run probed, spans whose atoms fell outside them read as carrying nothing and a shortening can cut them; re-run `novelty atoms` to index every atom. - load_atoms_record returns the parsed record with the atoms and spans, so view reads the file once; load_atoms and load_atoms_or_exit use it. - skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten refuses a sample the entry point leaves empty Review of #380 (comment 4458106353). On a bundle, `novelty shorten` keeps only the entry point's atoms before it reads the recorded sample. When every sampled atom is a resource atom, measured_atoms returned an empty list, and shortening scored nothing as knowledge 1.0 and value 1.0, so `--out` could write a cut no atom measured. measured_atoms (commands/novelty_atoms_file.py) now exits 2, the code the command already uses for a refused input, when the file's sample is non-empty, none of it survives the caller's filter, atoms remain and `--all-atoms` is not set. The message names the sample size, the atoms kept and the file's total, and says to pass `--all-atoms`. skill-novelty.md says so; a test covers the two entry-point / five resource atoms case, with and without `--all-atoms`. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): a cut that drops an unsampled atom is not safe Review of #380 (comment 4458106460). On a capped run the shortening oracle scores only the sample, and `greedy_value` and `random` do not pin sole carriers, while Candidate.safe checked only protection. Four unprotected one-atom spans of 20, 10, 20 and 10 tokens with the 2nd and 4th sampled: at budget 0.5 `greedy_value` kept those two, dropped both unsampled facts, and read safe with knowledge 1.0, so pick_best could write it with `shorten --out`. - build_candidates counts, per candidate, the atoms outside scored_atoms none of whose cited spans it keeps, and stores it as Candidate.dropped_unscored_atoms (also in as_dict). safe now needs it to be 0 as well as full protection, so pick_best with require_safe never picks such a cut; every candidate is still built, scored and reported, and scoring stays on the sample. With no cap the count is 0. - novelty.v1.json lists the new optional integer and says what `safe` means; it did not constrain candidate keys, so nothing else changes. - analyze's unsafe-candidate warning moves into a helper: the old protected-span line is unchanged, and a second line names candidates that drop an unsampled atom. The shorten command's no-candidate line names both reasons. analyze's complexity baseline drops by one. - skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn on every older atoms.json whose cap bound Review of #380 (comment 4458106517). The document-order cap before #358 wrote `extraction.reached` only when the cap bound, measured to the last kept atom's span. With cap 1 and a reply listing s0002's atom before s0001's, `reached.spans == reached.of` while s0001's only atom was dropped, and holds_only_a_capped_sample read that as nothing lost, so `shorten` could treat s0001 as empty with no warning. - holds_only_a_capped_sample (commands/novelty_atoms_file.py) is now true for a file with no sampled_atom_ids key, `cap` set and any `extraction.reached` object, whatever its counts. - warn_capped_sample stays the one warning: when `reached.spans` equals `reached.of` it says the file *may* hold only the atoms its run probed, since the cap was met at the last span and may have cut that reply. - The reach-end test now expects the qualified warning; a unit case covers a `reached` at the last span. skill-novelty.md and the changelog fragment say when the warning fires. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): AnalyzeResult keeps every extracted atom Review of #380 (comment 4458106571). After the #371 fix, a capped analyze run indexes result.spans and result.attributions from every extracted atom, but AnalyzeResult.atoms held only the sample, and the full extraction was dropped on return. In the 200-atom / 60-sample case a caller could not resolve 140 of the ids those views cite. AnalyzeResult gains extracted_atoms, every extracted atom, filled in analyze and documented as the set spans and attributions index; atoms stays the probed sample. No reader in the package resolves span or attribution ids through result.atoms (`novelty analyze` renders the attributions, which carry their spans, and does not write atoms.json), so none needed switching. The 200-span test now checks 200 extracted and 60 sampled atoms, and that every cited id is an extracted atom. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): check sample_atoms' cap and refuse repeated sample ids Review of #380 (comments 4458106644 and 4458106699). - sample_atoms (novelty/atoms.py) did not enforce the positive cap that extract_atoms does: a cap of 0 or -1 returned an empty sample that a caller could save and later score as if it stood for the extraction. It now calls _check_cap first and raises ValueError for a cap below 1; None and a positive cap behave as before. - _check_sample accepted a sampled_atom_ids that repeats an id, such as ["a1", "a1"] over three atoms: shortening then divided one retained atom by a sample of two and reported knowledge 0.5. It now raises ValueError naming the repeated ids, so load_atoms refuses the file and `novelty probe` / `shorten` / `view` exit 2 with the loader's message. - skill-novelty.md says a repeated id is refused; tests cover both. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): the safe strategy keeps a span of each unsampled atom Review of #380 (4459082413): after the previous commit made a cut that drops every cited span of an unsampled atom unsafe, greedy_value_safe could still fail it. It pinned only protected spans and sole carriers, so an unsampled atom cited by two spans could lose both; with no other safe candidate, pick_best returned None and `shorten --out` wrote nothing. _select now also pins, for greedy_value_safe, the cheapest cited span (fewest tokens, then span order) of each atom outside scored_atoms none of whose cited spans is pinned yet. With no cap every atom is scored and nothing new is pinned. docs/skill-novelty.md says what the safe strategy pins. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): bundle.by_source counts every extracted atom Review of #380 (4459082810): _outcomes_by_source got the cap's sample, so on a capped bundle run a reference file whose atoms all fell outside the sample read n_atoms 0, "a measured file that produced no atom", while its spans carried those atoms. n_atoms now counts every extracted atom of the file, the outcome counts stay over the sample, and the new n_sampled gives the file's sampled atoms as their denominator (equal to n_atoms without a cap; null with n_atoms on a file nobody read). by_source rows are open objects, so the schema only gains the description; the docstring and docs/skill-novelty.md say both counts. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten tables show the unsampled atoms a cut loses Review of #380 (4459082819): neither the `novelty shorten` table nor the shortening table in novelty.md showed dropped_unscored_atoms, so a row reading `prot 1.000 safe False` had no visible reason, and the sentence under the novelty.md table said only that a `safe: false` candidate dropped a protected span. Both tables gain a `lost` column, the candidate's dropped_unscored_atoms (novelty.md shows a dash for a candidate written before the count existed), and the sentence gives both reasons a candidate is not safe: a dropped protected span, or every span of an atom outside the cap's sample. The shorten header now fits on two lines, so commands/novelty.py does not grow. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn on a capped atoms.json from before extraction.reached Review of #380 (4459082823): a document-order cap from before extraction.reached existed (#299) stopped at exactly `cap` atoms and recorded no `reached`, so holds_only_a_capped_sample read such a file (`cap` 3, three atoms, `extraction: {"batches": 2}`) as complete, and shorten could cut spans whose atoms were never extracted, silently. A file with no sampled_atom_ids key, `cap` set, no `reached` key and at least `cap` atoms now counts too. Its cap may or may not have bound, so the warning says the file may hold only the atoms its run probed, as it records no extraction.reached. A file with fewer atoms than `cap`, or one that records `reached: null` (a cap that did not bind, #299 on), does not warn. One helper now classifies the file for both holds_only_a_capped_sample and the warning. docs/skill-novelty.md and the changelog fragment say when the warning fires. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): --all-atoms warns about atoms never checked for faithfulness Review of #380 (4459082836): `novelty atoms` verifies only the cap's sample, so on a capped atoms.json the other atoms read faithfulness "unchecked", and `probe --atoms atoms.json --all-atoms` probed them with nothing saying their answer key was never checked. When --all-atoms adds atoms outside the recorded sample and some of them are unchecked, `novelty probe` and `novelty shorten` print one WARNING on stderr with both counts, and say that `novelty atoms --all-atoms` (a new, uncapped extraction) verifies every atom it extracts. No line without --all-atoms or when none of the added atoms is unchecked. docs/skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): probes.json records the sample probe measured Review of #380 (4459082846, 4459082853). probe now measures the sample atoms.json records by default, e.g. 60 of its 200 atoms, but build_probe_record wrote the same shape as a full sweep, and only the stderr NOTE said otherwise, so the paired 2x2 could later be quoted as the file's rate. The record now carries `sampling: {"atoms": n, "of": M, "all_atoms": bool}`: the atoms probed, the atoms in the file, and whether --all-atoms was set. The only reader in the package (`novelty items`) reads `runs` alone, and no schema constrains the record. docs/skill-novelty.md says it where probes.json is described. The changelog fragment was under Fixed only, though on a capped run frac_carrying_nothing, dead weight, sole carriers, the dead_tokens gate and the candidate `shorten --out` writes now move for the same inputs. changelog.d/README.md puts that under Changed, so the fragment gains a Changed entry saying those figures are not comparable with older capped runs, and to re-run them. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * refactor(novelty): drop span_summary's dead capped parameter Review of #380 (4459082858): since this PR's first commit every run indexes spans from every extracted atom and no caller passed `capped`, but a new caller passing it would write `capped_atom_set: true` on a run that indexes every atom. span_summary loses the parameter and always writes `capped_atom_set: false`. The key stays, as the gate still reads `true` on artifacts written before #371; the docstring says so. The spans test that passed capped=True now pins the false flag and the indexed count. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): the bundle table names a capped file's sample beside its atoms Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten names the right reason it offers no candidate pick_best also returns None when every safe cut keeps the whole artifact, the usual case on a capped run once greedy_value_safe pins a span per unsampled atom; the line then claimed nothing was safe. Say so in that case, and name unsampled atoms only when some were unscored. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * test(novelty): pin the already-pinned unsampled atom and the warning split _unscored_pins skips an unsampled atom whose cited span is already pinned, and the protected-span warning counts only protection; both survived mutation with every test green. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * docs(changelog): an unsafe shortening is listed, only --out refuses it The fragment said the offered candidates never lose an unsampled atom; the list still shows such a candidate, marked not safe. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * fix(novelty): the shorten/probe NOTE counts the file's atoms measured_atoms printed the entry-point subset as the file's total; the refusal message beside it already used the file's count. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> --------- Signed-off-by: Anton Volkov <antonwolfy@gmail.com> Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> Co-authored-by: Alexander Rybkin <alexander.rybkin@intel.com>
…ntel#380) * fix(novelty): a capped run indexes spans from every extracted atom Closes intel#371, raised in the review of intel#358: a capped run extracted every span but kept only the cap's spread sample of atoms, so spans, attribution, atoms.json and shortening saw the sample only. At --tier standard, 200 one-atom spans left 140 spans with no atom_ids and frac_carrying_nothing 0.70, and a shortening could cut them as free. - extract_atoms returns every extracted atom; sample_atoms picks the cap's spread sample (same rule, same order). analyze and `novelty atoms` keep both: faithfulness, the need-graph match, probes, measurement, the conflict check, carrier confirmation and the shortening oracle run on the sample, so no stage makes more model calls. - Spans (index_atoms / span_summary), span labels, dead weight, sole carriers and shortening's structure use every atom. build_candidates takes scored_atoms for the oracle's knowledge and value score; an atom outside it adds span_value's no-outcome default to its span's density. - atoms.json holds every atom and names the sample in sampled_atom_ids (null when the cap kept every atom). A file without the record reads as every atom sampled; one that names an atom it does not hold is refused. `novelty probe` and `novelty shorten` measure the recorded sample by default and say so; --all-atoms measures every atom in the file. - spans.capped_atom_set is false on new runs, so the dead_tokens check judges a capped run and still skips older capped artifacts. The harmful_atoms check reads extraction.sampled, so a 0 from a sampled conflict check is still not cleared. - The cap no longer forfeits frac_carrying_nothing; the shortening forfeit says knowledge and value are retention of the sample. - Docs (skill-novelty.md, D017, a new implref R041), novelty.v1 field descriptions, the settings-parity intended changes, and a Fixed changelog fragment. The atoms.json readers move to commands/novelty_atoms_file.py to keep commands/novelty.py under its growth baseline. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn when an older capped atoms.json holds only its sample Follow-up to the review of the intel#371 fix. An atoms.json written by a capped `novelty atoms` run before sampled_atom_ids existed holds only the atoms its run probed. recorded_sample reads it as every atom sampled, and probe, shorten and view index spans from it with no word, while the forfeit that said a cut of an unsampled span looks free is gone. So `shorten --out` on such a file could still cut spans as free, silently. - holds_only_a_capped_sample (commands/novelty_atoms_file.py) is true when the file has no sampled_atom_ids key and its record shows a cap that left atoms out: extraction.sampled set (the spread cap, intel#358 on), or a cap with extraction.reached short of the input's last span (the document-order cap before it). A file this version writes always has the key, so one whose cap kept every atom (null) is not flagged. - `novelty probe` and `novelty shorten` (through measured_atoms) and `novelty view` (through load_view_inputs) print a WARNING on stderr: the file holds only the atoms its run probed, spans whose atoms fell outside them read as carrying nothing and a shortening can cut them; re-run `novelty atoms` to index every atom. - load_atoms_record returns the parsed record with the atoms and spans, so view reads the file once; load_atoms and load_atoms_or_exit use it. - skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten refuses a sample the entry point leaves empty Review of intel#380 (comment 4458106353). On a bundle, `novelty shorten` keeps only the entry point's atoms before it reads the recorded sample. When every sampled atom is a resource atom, measured_atoms returned an empty list, and shortening scored nothing as knowledge 1.0 and value 1.0, so `--out` could write a cut no atom measured. measured_atoms (commands/novelty_atoms_file.py) now exits 2, the code the command already uses for a refused input, when the file's sample is non-empty, none of it survives the caller's filter, atoms remain and `--all-atoms` is not set. The message names the sample size, the atoms kept and the file's total, and says to pass `--all-atoms`. skill-novelty.md says so; a test covers the two entry-point / five resource atoms case, with and without `--all-atoms`. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): a cut that drops an unsampled atom is not safe Review of intel#380 (comment 4458106460). On a capped run the shortening oracle scores only the sample, and `greedy_value` and `random` do not pin sole carriers, while Candidate.safe checked only protection. Four unprotected one-atom spans of 20, 10, 20 and 10 tokens with the 2nd and 4th sampled: at budget 0.5 `greedy_value` kept those two, dropped both unsampled facts, and read safe with knowledge 1.0, so pick_best could write it with `shorten --out`. - build_candidates counts, per candidate, the atoms outside scored_atoms none of whose cited spans it keeps, and stores it as Candidate.dropped_unscored_atoms (also in as_dict). safe now needs it to be 0 as well as full protection, so pick_best with require_safe never picks such a cut; every candidate is still built, scored and reported, and scoring stays on the sample. With no cap the count is 0. - novelty.v1.json lists the new optional integer and says what `safe` means; it did not constrain candidate keys, so nothing else changes. - analyze's unsafe-candidate warning moves into a helper: the old protected-span line is unchanged, and a second line names candidates that drop an unsampled atom. The shorten command's no-candidate line names both reasons. analyze's complexity baseline drops by one. - skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn on every older atoms.json whose cap bound Review of intel#380 (comment 4458106517). The document-order cap before intel#358 wrote `extraction.reached` only when the cap bound, measured to the last kept atom's span. With cap 1 and a reply listing s0002's atom before s0001's, `reached.spans == reached.of` while s0001's only atom was dropped, and holds_only_a_capped_sample read that as nothing lost, so `shorten` could treat s0001 as empty with no warning. - holds_only_a_capped_sample (commands/novelty_atoms_file.py) is now true for a file with no sampled_atom_ids key, `cap` set and any `extraction.reached` object, whatever its counts. - warn_capped_sample stays the one warning: when `reached.spans` equals `reached.of` it says the file *may* hold only the atoms its run probed, since the cap was met at the last span and may have cut that reply. - The reach-end test now expects the qualified warning; a unit case covers a `reached` at the last span. skill-novelty.md and the changelog fragment say when the warning fires. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): AnalyzeResult keeps every extracted atom Review of intel#380 (comment 4458106571). After the intel#371 fix, a capped analyze run indexes result.spans and result.attributions from every extracted atom, but AnalyzeResult.atoms held only the sample, and the full extraction was dropped on return. In the 200-atom / 60-sample case a caller could not resolve 140 of the ids those views cite. AnalyzeResult gains extracted_atoms, every extracted atom, filled in analyze and documented as the set spans and attributions index; atoms stays the probed sample. No reader in the package resolves span or attribution ids through result.atoms (`novelty analyze` renders the attributions, which carry their spans, and does not write atoms.json), so none needed switching. The 200-span test now checks 200 extracted and 60 sampled atoms, and that every cited id is an extracted atom. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): check sample_atoms' cap and refuse repeated sample ids Review of intel#380 (comments 4458106644 and 4458106699). - sample_atoms (novelty/atoms.py) did not enforce the positive cap that extract_atoms does: a cap of 0 or -1 returned an empty sample that a caller could save and later score as if it stood for the extraction. It now calls _check_cap first and raises ValueError for a cap below 1; None and a positive cap behave as before. - _check_sample accepted a sampled_atom_ids that repeats an id, such as ["a1", "a1"] over three atoms: shortening then divided one retained atom by a sample of two and reported knowledge 0.5. It now raises ValueError naming the repeated ids, so load_atoms refuses the file and `novelty probe` / `shorten` / `view` exit 2 with the loader's message. - skill-novelty.md says a repeated id is refused; tests cover both. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): the safe strategy keeps a span of each unsampled atom Review of intel#380 (4459082413): after the previous commit made a cut that drops every cited span of an unsampled atom unsafe, greedy_value_safe could still fail it. It pinned only protected spans and sole carriers, so an unsampled atom cited by two spans could lose both; with no other safe candidate, pick_best returned None and `shorten --out` wrote nothing. _select now also pins, for greedy_value_safe, the cheapest cited span (fewest tokens, then span order) of each atom outside scored_atoms none of whose cited spans is pinned yet. With no cap every atom is scored and nothing new is pinned. docs/skill-novelty.md says what the safe strategy pins. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): bundle.by_source counts every extracted atom Review of intel#380 (4459082810): _outcomes_by_source got the cap's sample, so on a capped bundle run a reference file whose atoms all fell outside the sample read n_atoms 0, "a measured file that produced no atom", while its spans carried those atoms. n_atoms now counts every extracted atom of the file, the outcome counts stay over the sample, and the new n_sampled gives the file's sampled atoms as their denominator (equal to n_atoms without a cap; null with n_atoms on a file nobody read). by_source rows are open objects, so the schema only gains the description; the docstring and docs/skill-novelty.md say both counts. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten tables show the unsampled atoms a cut loses Review of intel#380 (4459082819): neither the `novelty shorten` table nor the shortening table in novelty.md showed dropped_unscored_atoms, so a row reading `prot 1.000 safe False` had no visible reason, and the sentence under the novelty.md table said only that a `safe: false` candidate dropped a protected span. Both tables gain a `lost` column, the candidate's dropped_unscored_atoms (novelty.md shows a dash for a candidate written before the count existed), and the sentence gives both reasons a candidate is not safe: a dropped protected span, or every span of an atom outside the cap's sample. The shorten header now fits on two lines, so commands/novelty.py does not grow. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): warn on a capped atoms.json from before extraction.reached Review of intel#380 (4459082823): a document-order cap from before extraction.reached existed (intel#299) stopped at exactly `cap` atoms and recorded no `reached`, so holds_only_a_capped_sample read such a file (`cap` 3, three atoms, `extraction: {"batches": 2}`) as complete, and shorten could cut spans whose atoms were never extracted, silently. A file with no sampled_atom_ids key, `cap` set, no `reached` key and at least `cap` atoms now counts too. Its cap may or may not have bound, so the warning says the file may hold only the atoms its run probed, as it records no extraction.reached. A file with fewer atoms than `cap`, or one that records `reached: null` (a cap that did not bind, intel#299 on), does not warn. One helper now classifies the file for both holds_only_a_capped_sample and the warning. docs/skill-novelty.md and the changelog fragment say when the warning fires. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): --all-atoms warns about atoms never checked for faithfulness Review of intel#380 (4459082836): `novelty atoms` verifies only the cap's sample, so on a capped atoms.json the other atoms read faithfulness "unchecked", and `probe --atoms atoms.json --all-atoms` probed them with nothing saying their answer key was never checked. When --all-atoms adds atoms outside the recorded sample and some of them are unchecked, `novelty probe` and `novelty shorten` print one WARNING on stderr with both counts, and say that `novelty atoms --all-atoms` (a new, uncapped extraction) verifies every atom it extracts. No line without --all-atoms or when none of the added atoms is unchecked. docs/skill-novelty.md and the changelog fragment say so. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): probes.json records the sample probe measured Review of intel#380 (4459082846, 4459082853). probe now measures the sample atoms.json records by default, e.g. 60 of its 200 atoms, but build_probe_record wrote the same shape as a full sweep, and only the stderr NOTE said otherwise, so the paired 2x2 could later be quoted as the file's rate. The record now carries `sampling: {"atoms": n, "of": M, "all_atoms": bool}`: the atoms probed, the atoms in the file, and whether --all-atoms was set. The only reader in the package (`novelty items`) reads `runs` alone, and no schema constrains the record. docs/skill-novelty.md says it where probes.json is described. The changelog fragment was under Fixed only, though on a capped run frac_carrying_nothing, dead weight, sole carriers, the dead_tokens gate and the candidate `shorten --out` writes now move for the same inputs. changelog.d/README.md puts that under Changed, so the fragment gains a Changed entry saying those figures are not comparable with older capped runs, and to re-run them. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * refactor(novelty): drop span_summary's dead capped parameter Review of intel#380 (4459082858): since this PR's first commit every run indexes spans from every extracted atom and no caller passed `capped`, but a new caller passing it would write `capped_atom_set: true` on a run that indexes every atom. span_summary loses the parameter and always writes `capped_atom_set: false`. The key stays, as the gate still reads `true` on artifacts written before intel#371; the docstring says so. The spans test that passed capped=True now pins the false flag and the indexed count. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): the bundle table names a capped file's sample beside its atoms Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(novelty): shorten names the right reason it offers no candidate pick_best also returns None when every safe cut keeps the whole artifact, the usual case on a capped run once greedy_value_safe pins a span per unsampled atom; the line then claimed nothing was safe. Say so in that case, and name unsampled atoms only when some were unscored. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * test(novelty): pin the already-pinned unsampled atom and the warning split _unscored_pins skips an unsampled atom whose cited span is already pinned, and the protected-span warning counts only protection; both survived mutation with every test green. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * docs(changelog): an unsafe shortening is listed, only --out refuses it The fragment said the offered candidates never lose an unsampled atom; the list still shows such a candidate, marked not safe. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> * fix(novelty): the shorten/probe NOTE counts the file's atoms measured_atoms printed the entry-point subset as the file's total; the refusal message beside it already used the file's count. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> --------- Signed-off-by: Anton Volkov <antonwolfy@gmail.com> Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com> Co-authored-by: Alexander Rybkin <alexander.rybkin@intel.com>

Summary
Settles #293, where
novelty analyzeunder an atom cap measured only the opening of the artifact. The decisions are posted on the issue (comment): a per-atom stride with no seed; re-measure only where the cap binds; reword the shortening forfeit;samplingbecomescap-spread:<n>; on--include-resources, the same stride over the bundle's atoms.On
main,extract_atomsstops its batch loop once it holdscapatoms. A capped run probes the first n atoms in document order, and its rate describes that stretch of the input. With a fake extractor giving one atom per span, on a 61-span input at the triage cap of 25 (the shape of the dpnp rootAGENTS.mdin the issue):mainreached: s0025, 25 of 61 spans, 40% of tokenssampled:{"atoms": 25, "of": 61, "rule": "cap-spread"},reached:nullThe second commit answers the review of this PR: the stride now runs in document order whatever order the extractor's reply uses, a cap below 1 is refused, the cap texts no longer claim the whole input was read when spans went unread, and the docs no longer promise every file a probe or the same sample from the same text.
The third commit answers the second review. A run whose atoms exactly meet the cap keeps every atom, since extraction no longer stops there. At
e8fdf95such a run still carried the cap's forfeits andspans.capped_atom_set: true; now it carries neither. The changelog now says whenextraction.sampledis set.The fourth commit answers the third review, in the docs only: like any fixed stride, the spread sample can fall in step with a repeated structure. With M = 100 and n = 25 it always takes atoms 2, 6, ..., 98, so an input of same-shaped four-atom sections with the new fact in the same place can give a sampled rate far from the whole input's, and re-extracting the same atoms gives the same sample. The docs now name that limit and say when to pass
--all-atoms. The stride stays seedless, as D017 decided.The last three commits answer the fifth review.
--estimate-onlycounted extraction as one call per 12 atoms expected before the cap. For triage on 300 spans and 19,000 tokens that gave 69 extraction calls, but the run asks one call per 25-span batch, which is 12 calls. The estimate now counts the span batches. A cap below 1 is now refused beforeextract_atomsresets the caller'sspan.unreadmarks, not only before any call. The review also asked that spans,atoms.jsonand shortening be indexed from every extracted atom while only the sample is probed. That changes theatoms.jsoncontents, the cost of shortening's oracle and the novelty gate's reading ofspans.capped_atom_set, so it is tracked in #371, which D017 now links (9f8bdf8).Changes
novelty/atoms.py:extract_atomsno longer stops at the cap. It extracts every batch, then keepsspread_sample(atoms, cap, key=_earliest_span(spans)): the atoms sorted stably by the input position of the earliest span each cites (document order; on a bundle run the files in bundle order: entry point, then resources by role and path), then the ones atfloor((i + 0.5) * M / n)foriin0..n-1. The sort is needed because a batch's reply lists its atoms in the model's order, which the prompt does not fix: a reply citing s0001, s0003, s0002 at cap 1 probed s0003 before the sort and probes s0002 with it. With M ≤ n, or no cap, every atom is kept, unsorted, in extraction order, soatoms.jsonof an uncapped run is as before. The selection has no RNG and no seed.sample_recordfillsExtractionReport.sampledwhen the cap left atoms out._cap_reachand thesentset are gone, andreport.batches/report.callscount every batch. A cap below 1 raisesValueErrorbefore any model call, and before any change to the spans passed in (AnalyzeConfig(max_atoms=0)reached a zero-atom sample; the CLI's--max-atomsalready took positive ints only). The default batch of 25 spans per extraction call is now the constantBATCH_SPANS. The complexity ofextract_atomsdrops from 16 to 14 (baseline lowered).novelty analyzeandnovelty atomscallextract_atoms, soprobeandshortenread the same sample fromatoms.json. Shortening candidates and the faithfulness, carrier and conflict checks get only the sampled atoms, as before.novelty/analyze.py:samplingiscap-spread:<n>for new runs whose cap left atoms out (extraction.sampledset), andfullwhen the cap kept every atom. The cap warning is added whenextraction.sampledis set. Nothing in the package parses the string: it is copied into the scorecard and printed innovelty.md, so an oldcap-per-artifact:<n>artifact reads as before.novelty/report.py: a newdescribe_sample.cap_warning(extraction)takes the extraction record and readsatom extraction read the whole input, and the atom cap probes 25 of the input's 61 atoms, spread over all of it (a stride of 2.44 atoms in document order): rates are estimated from that sample; pass --all-atoms to probe every atom. Whenextraction.unread_span_idsis not empty (a failed call, an empty or unparseable reply), the warning, thenovelty.mdheader and thecoverage:line say instead that the sample is spread over the atoms extracted from the spans that were read, and how many spans were not:atom extraction could not read 25 of the input's spans, and the atom cap probes 2 of the 25 atoms extracted from the spans that were read, spread over those (a stride of 12.5 atoms in document order; 25 of the input's spans were not read, see extraction.unread_span_ids): ....coverage_reachand_capped_atusesampledwhen it is present. For an old artifact withreachedthey keep their present wording.novelty/tiers.py: under a cap that left atoms out, with shortening on, the forfeit is nowshortening candidates are scored against a spread sample of <n> of the extracted atoms: a cut of a span with no sampled atom looks free.forfeitsis not passed M.estimate_callstakesn_spansand countsceil(n_spans / BATCH_SPANS)extraction calls.RunSettings.estimatepasses the span count, so--estimate-onlycounts every extraction batch the run asks, while the probe terms stay at the cap. The triage case above (300 spans, 19,000 tokens) now projects 13 stage calls: 12 extraction batches and the need graph. Onmainit projected 3, and at82d576a70. Withoutn_spans,estimate_callskeeps the rule of the measured runs, whose extraction stopped at the cap: one call per 12 atoms.extraction.sampledis set), not when its atom count reaches the cap.tiers.forfeits(andRunSettings.forfeits,tier_record,warnings) takessampledin place ofn_atoms.analyzepassesextraction.sampled is not None, so thesampledforfeits and the spread-sample shortening forfeit leave the warnings andrun_config.tierof a run that kept every atom. Before extraction (--estimate-only, the CLI's tier listing)sampledisNoneand the cap is still assumed to bind.spans.span_summarytakescappedin place ofatom_cap, from the same state, inanalyzeand innovelty atoms, sospans.capped_atom_set(read by the novelty gate and the subject report) is false at M = n.schemas/novelty.v1.json, widened in place: an optionalextraction.sampled(atoms, at least 1,of,rule, all required inside it). Thesampling,batches,reachedandconflict_coveragedescriptions now say what new runs write, andsampled's names the sort and the read spans.capped_atom_set's says it is true only when the cap left atoms out, and that older runs set it when the atoms reached the cap. Old artifacts still validate (schema_compatpasses, and the committednovelty.v1fixture is unchanged).commands/novelty.py:novelty atomsprints the new cap warning. The--all-atomshelp says "Probe every extracted atom", since a capped run now extracts every atom too.docs/skill-novelty.md, the cap passage, theextractiontable (sampled,reached), the forfeit list and the batches note. The cap passage says the stride runs over the concatenated atom list of the selected files, so a file with few atoms can get no probe. It says the same extracted atom list, not the same text, gives the same sample, and that reusingatoms.jsonwithnovelty probe --atomsprobes identical atoms across runs. It quotes both forms of the cap warning. Its forfeit list says that a skill with no more atoms than the cap keeps every atom (extraction.samplednull,spans.capped_atom_setfalse) and drops the cap's forfeits. Its cost section now says the measured runs and the tier table predate the spread cap. Where the cap passage says the sample estimates the per-atom rate an uncapped run reports, it adds the fixed stride's periodic-order limit (the M = 100, n = 25 example) and says to pass--all-atomsfor an artifact of repeated, same-shaped sections (a template per item) or a rate that decides something.docs/reviewing.mdanddocs/skill-novelty-walkthrough.md: at triage,--include-resourcesstrides over the selected files' atoms, a file with few atoms may get no probe, a higher tier spreads more probes without promising each file one, and--all-atomsprobes every extracted atom. Neither of those two says the sample estimates the whole input's rate, so the periodic-order limit is not repeated there.docs/skill-novelty-evidence.mdhas no tier profiles and is unchanged.--all-atoms.Risk
triage, 60 atstandard, 120 at default/study. Their rates now estimate the whole input's per-atom rate, so they are not comparable with capped results written before this change. On an artifact of repeated, same-shaped sections the fixed stride can still miss that rate; the docs point to--all-atomsthere. Runs below the cap are unchanged.--estimate-onlycounts these extra calls, one per 25-span batch, but no measured run has checked that count yet. Each extra call is priced with the tier's per-call extractor profile, which was fitted on capped runs. The held-out calibration checks intests/test_novelty_tiers.pynow project those 12 runs the way they were measured (extraction stopping at the cap). The triage extractor token profile is unchanged.extraction.reachedisnullon new runs. A consumer that used it to tell a capped run should readextraction.sampled, oratoms.sampling(cap-spread:<n>). The in-repo readers (report, headline, settings parity test) do. A cappedatoms.jsonlists its sample in document order, which can differ from the extractor's reply order.AnalyzeConfig(max_atoms=0)from the Python API now raisesValueErrorbefore any model call; it was accepted before.spans.capped_atom_setis false and the cap's forfeits are gone from its warnings andrun_config.tier. The novelty gate therefore checks its dead-token share and can clearharmful_atomsat 0 conflicts, as for any run under the cap. Artifacts already written keep what they stored.atom_ids,frac_carrying_nothing),atoms.jsonand shortening still see only the sampled atoms. The spans with no sampled atom are now spread over the whole input instead of forming the unread tail. Thefrac_carrying_nothingand spread-sample shortening forfeits say so. Indexing them from every extracted atom is novelty: a capped run indexes spans and writes atoms.json from the sample only #371.Testing
On a Linux review host, at
3606282(onmain41b3d8d):make ci-local: 30 passed, 0 failed, 7 skipped. Skipped:external-catalog-smoke(needs docker and network),test-latest,docs.yml:deployandpip-audit.yml:report(job conditions false on a PR),cli-surface / Package the result(an expression ci-local cannot evaluate),conda-forge-deps(no micromamba), and semgrep secrets (not installed). 12566 tests passed, 5 skipped and 6 xfailed; changed-line coverage againstmainwas 100% (61 lines measured). The branch mergesmaintwice (9e72b02at ad734f5,ca507e9at 41b3d8d), with no conflicts.[1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 28, 30, 32, 35, 37, 40, 42, 45, 47, 50, 52, 54, 57, 59]), n=1 ([30]), M=n, M<n and no cap;mainmade 6 / 2);samplingiscap-spread:<n>when the cap binds, andfullwhen the atoms meet or fall under it (10 spans under cap 25);cap-per-artifactartifact withreachedstill validates and renders its old header and coverage line;SKILL.mdandreferences/tuning.mdin file order;novelty atoms --max-atoms 2saves the same sample and asks both batches;ValueErrorand the mocked client is never called;extraction.sampled.atoms: 0;studyrun (shortening on) whose two atoms meet a cap of 2 has neithersampledforfeit and no spread-sample shortening forfeit, in its warnings or inrun_config.tier, andspans.capped_atom_setfalse; at a cap of 1 it has all three;novelty atomsover three one-atom spans printscapped_atom_setfalse at--max-atoms 3and true at 2;forfeitswithsampled=Falsehas no sampled or shortening forfeit, withsampled=Trueor before extraction it has both (the shortening-forfeit test no longer expects the warning at M = n).unreadmarks as they were.82d576athe 300-span and 26-span cases fail (70 and 2 stage calls where 13 and 3 are expected), and so does the cap-0 test (the unread mark reset to false). The 25-span case passes on both, as it should: there the old rule happens to give the same count. The estimate test ofstudyfails there on then_spanskeyword.tests/test_docs_code_refs.pyand the comment-block check pass on it.main's code, the first commit's new tests fail. Extraction makes 1 call where 6 are expected,(6, 2, 2)where(24, 8, 8)is expected,novelty atomsasks 1 batch, there is nosampledrecord, and the texts, forfeit and estimate are the old ones. The test of an old artifact validating passes on both, as it should. Against the first commit, the review's new tests fail: the three out-of-order cases, the cap of 0, the zero-atom schema case and the partly-read case. The at-cap and no-cap cases pass on both, as they should: they pin the order a cap that does not bind keeps. Against the second commit, the at-cap cases of the second review's tests fail (the forfeits andcapped_atom_set: trueat M = n), and the cap-binds cases pass, as they should. Theforfeitstest fails there on the keyword it now passes.cap-per-artifactrestored;analyze, and innovelty atoms;sampled;sampled.of;sampled.atomsremoved;cap_warning,coverage_reachand_capped_ateach ignoring unread spans; a fully read input described as partly read;analyze's warnings, itsrun_config.tierand itsspan_summary, and innovelty atoms'span_summary;forfeitsignoringsampled;span_summaryignoringcapped;BATCH_SPANSat 20 instead of 25;span.unread.tests/test_novelty_settings_parity.pycompares againstmain's capture. It now records the intended changes the way it already does: the forfeit and cap-warning texts,stage_callsin the estimate, and for the two--max-atoms 1runs only the settings, since they now probe a different atom.82d576awith the branches for review_replay: optional judged recall over the credited hits #291 (judged recall,8e05118), repo compare: flag replay tasks a guidance revision may have seen #290's recording half (f6a9cb9) ontomain41b3d8d: the full suite (12641 passed, 5 skipped, 6 xfailed),mypy(318 files) andruff checkpass.3606282.Revert
git revertthe eight non-merge commits. New artifacts written withcap-spread:<n>andextraction.sampledstay validnovelty.v1once reverted (extractiontakes extra keys), but render without the sample sentence. D017 would need asupersededrecord rather than deletion.Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)