Skip to content

feat(routing): estimate-only, all-vs-all repeats and cost - #329

Merged
napetrov merged 21 commits into
intel:mainfrom
napetrov:fix/rescan-routing
Nov 4, 2026
Merged

napetrov merged 21 commits into
intel:mainfrom
napetrov:fix/rescan-routing

Conversation

@napetrov

@napetrov napetrov commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A rescan of public skill catalogs (10-30) found five gaps in novelty routing. This PR closes them:

routing --estimate-only              prices the run as JSON, makes no model call
all-vs-all --repeats N               noise block and cost block, like routing
disabled-skill query                 refused by name, hint: --include-disabled
--queries-from-headings              says its source (queries_source); no heading, no query
--seed help                          says it fixes card order only; answer noise is --repeats

Self-derived queries stay labelled as triage (triage: self-derived, queries_self_derived: true), and queries_source is headings or none. A skill with no usable heading still refuses with no_queries, as on main; its description is not used as a query (a description query scores near 1.0 against its own card, so it is not a routing rate). The warning no longer claims a derivation that did not happen.

Routing console lines and query loading move to commands/novelty_routing_report.py, so commands/novelty.py shrinks from 1171 to 1126 lines. The growth baseline is lowered, not raised.

Evidence

Red on intel/main: nine tests fail, six in tests/test_novelty_routing_cli_gaps.py, two in tests/test_novelty_routing_all_vs_all.py, and tests/test_novelty_routing.py::test_headings_inside_code_only_refuse_the_run_and_say_why. The two tests added after the first commit (no-derivation warning, attractor unit) were each shown red on the commit before their fix.

Local checks on this branch (aa9fcbeb):

uv run ruff check dokimi tests scripts           All checks passed!
uv run mypy dokimi                               Success: no issues found in 304 source files
uv run python scripts/check_growth.py            exit 0 (novelty.py 1126 <= baseline 1126)
uv run python -m dokimi.schema_check benchmarks/spec.v1.yaml   exit 0
uv run python -m dokimi.config_check             exit 0
pytest -n 8 -q (dokimi venv, head of this push)  12078 passed, 7 skipped

Self-review (pr-review local mode, 3 medium passes, diff and description only) ran on aa9fcbeb: pass 2 found one blocker (the attractor line counted calls under the word "queries"), fixed there, and pass 3 found none. The description fallback was removed after that pass, in review of this PR; that change got no fresh-context pass, only the suite, ruff and mypy.

Declined review findings

  • --estimate-only exits 0 with n_calls 0 where the real run exits 2 with no_queries (no usable query). The estimate prices calls and refuses nothing; a refusal would be a separate change.
  • The ASCII-only tokenizer ([a-z0-9]+) splits non-English words and gives CJK no tokens. It predates this PR, and headings_as_queries has the same limit: a CJK-only heading is refused as none.
  • The disabled-skill refusal loads the field a second time, which repeats some stderr notes and reports a second competitor count (the disabled skills are included). Cosmetic; the JSON is right.
  • --estimate-only is the spelling the rescan brief asked for. dry_run is the registry concept (dokimi/cli_flags.py); tests/data/cli_flags_allowlist.txt already admits this spelling for novelty analyze. Reviewers may prefer --dry-run; that is a rename, not a fix.

Not in this PR

Merge Danger

Door: two-way. The new flag and keys are additive (queries_source, cost, noise, estimate_only). Reverting restores the old refusals.

Blast Radius: low. Routing only; no catalog loading or field building is touched.

Ramifications:

  • --all-vs-all now accepts --repeats and makes N times the calls. Scripts that relied on the refusal will see model calls.
  • The CLI snapshot docs/reference/cli-interface.json is regenerated for the new option.

🤖 Generated with Claude Code

…n queries

- routing --estimate-only prints the calls, tokens and list-price USD that
  --model would make, as JSON, and makes no model call.
- all-vs-all accepts --repeats with the same noise block as routing, and
  prints a cost block; the JSON gets the cost too.
- all-vs-all refuses queries for a skill that only sets
  disable-model-invocation, names it, and suggests --include-disabled.
- --queries-from-headings falls back to the description's sentences when
  the headings give no query; queries_source records which one was used.
- --seed help says it fixes the card order only and points at --repeats
  for answer noise.
- Routing console lines and query loading move to
  commands/novelty_routing_report.py to keep novelty.py under its ratchet.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…fuses --estimate-only, repeats count calls

The model estimate skipped contentless queries that the run still sends,
--all-vs-all --estimate-only made paid calls, the stderr call count left
out the repeats, and the model arm's unscored and tied calls were printed
as queries. Fix each and cover it with a test.

Routing console output moves to commands/novelty_routing_report.py to keep
commands/novelty.py under its growth cap.

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

With neither a heading nor a description sentence, the run refuses with
no_queries, but the warning still said the queries were derived from the
artifact. Print it only when a query was derived.

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

With --repeats above 1 the model arm's sibling count is per call, so the
attractor line now names its unit the way the tie and unscored line does.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov
napetrov requested a review from a team as a code owner October 31, 2026 17:48
@github-actions github-actions Bot added the cli-interface-change Changes dokimi's user-facing CLI surface label Oct 31, 2026
@github-actions

Copy link
Copy Markdown

CLI interface change detected

This PR changes dokimi's user-facing CLI surface (commands and/or options):

~ `dokimi novelty routing` parameters changed:
    + added option `--estimate-only`

Updated on every push; the cli-interface-change label is on while the diff is non-empty.

@exai-bot

exai-bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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

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

Last review (1e2fa05): no findings

Open threads from the bot: 0

Comment on lines +64 to +65
priced = list_cost_usd(args.model, args.provider, estimate["input_tokens"],
estimate["output_tokens"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: these are run-wide token totals, but list_cost_usd prices them as one completion. For models with context-length pricing tiers (for example Gemini 2.5 Pro), many short calls can therefore cross the long-context threshold only in the estimate and be charged the higher rate, although every real request stays below it. A short query with a sufficiently large --repeats reproduces this; changing repeats should not change the per-call tariff. Price each query's prompt and one pick separately, then sum and multiply by repeats. Add a tiered-pricing test; the current linear pricing mock cannot detect this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 1c5348b. The estimate now prices each query's prompt and one pick on their own, sums them, and multiplies by the repeats, so a long-context tier applies to one call's prompt as it does in a real run. Pinned by test_estimate_prices_each_call_so_a_long_context_tier_is_not_reached_by_the_run_total, which uses a tiered stub; it failed on the previous head (0.568 against the 0.284 expected).

f"{len(queries_by_skill)} skills with queries")
_print_attractors(result)
print_attractors(result)
print_all_vs_all_model(model_arm, payload.get("cost"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: this reporting call is unreachable whenever any model call fails, because the earlier if failed raises SystemExit. For example, the existing partial-failure test (one successful call followed by two failures), extended with --repeats 3, saves cost/noise in the file but prints neither on stdout. This hides usage from successful calls and breaks the new console-reporting promise. Move this call before the failure exit, while keeping the success-only summary after it, and extend the partial-failure test to assert cost/noise output and a non-zero exit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2ccd64b. The cost and noise lines now print before the failure exit, so a partial run shows the usage of the calls that ran. Pinned by test_a_partial_failure_still_prints_the_cost_of_the_calls_that_ran, which found no cost line before the fix. On a successful run those two lines now come before the OK Saved line instead of after the attractors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my earlier reply on this thread. I wrote that the success path was unchanged. It was not: a6fc22d dropped the noise and cost lines from a clean all-vs-all run.

Fixed in 8611ba8. A clean run now prints the summary, the attractors, then the noise and cost lines, as on main. A partial run prints the noise and cost lines before the failure exit, so the usage of the calls that ran is still shown.

Pinned by test_a_partial_failure_still_prints_the_cost_of_the_calls_that_ran and test_a_clean_all_vs_all_run_still_prints_its_cost_after_the_summary.

@codecov-commenter

codecov-commenter commented Oct 31, 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 94.41%. Comparing base (1cc9663) to head (9a7277d).
⚠️ Report is 5 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #329      +/-   ##
==========================================
+ Coverage   94.37%   94.41%   +0.03%     
==========================================
  Files         312      314       +2     
  Lines       40068    40347     +279     
  Branches     6801     6847      +46     
==========================================
+ Hits        37816    38093     +277     
- Misses       1436     1438       +2     
  Partials      816      816              
Files with missing lines Coverage Δ
dokimi/dokimi/commands/novelty.py 94.85% <100.00%> (-0.32%) ⬇️
dokimi/dokimi/commands/novelty_routing_report.py 100.00% <100.00%> (ø)
dokimi/dokimi/novelty/all_vs_all.py 99.22% <100.00%> (+0.05%) ⬆️
dokimi/dokimi/novelty/routing.py 96.29% <100.00%> (+0.25%) ⬆️

... and 14 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The estimate priced the run's summed prompt and picks as one completion. A
long-context tier applies to one call's prompt, so many short calls could be
charged the higher rate although no real request reaches it. Each query's
prompt and one pick are now priced on their own, summed and multiplied by the
repeats.

Thread: PRRT_kwDOVBg7g86yTJiq
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The failure exit came before the cost and noise lines, so a run with some
failed calls hid the usage of the calls that succeeded. Those lines now print
before the exit; the success summary stays after it.

Thread: PRRT_kwDOVBg7g86yTJix
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The per-call pricing added in 1c5348b returns None for an unpriced model; no
test reached that path, which left a patch-coverage gap.

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

CI on a6fc22d failed the growth ratchet: novelty.py was 1149 lines
against a baseline of 1147. The partial-failure exit moves into
exit_partial_all_vs_all in novelty_routing_report.py, so the command
body shrinks. The success path keeps its original order: the summary,
then the attractors, then the noise and cost lines. A test pins that
the cost line still follows the saved-file line on a clean run.
The baseline drops to 1146 as check_growth.py --update reports.

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

@vlad-perevezentsev vlad-perevezentsev 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 8611ba8. --estimate-only, --all-vs-all --repeats and the all-vs-all cost block work as described. The description fallback of --queries-from-headings needs another look: its queries are the gold card's own text, so a skill that used to give no_queries now gets a rate near 1.0.

What I ran, at 8611ba8:

  • make ci-local --offline: 11,990 passed, 0 failed, 58 skipped; all checks pass.
  • A repro of the description fallback: one-word headings, a three-sentence description, and three competitors that share its words. Lexical R@1 1.0, MRR 1.0, queries_source: description.
  • --estimate-only on an artifact the real run refuses: exit 0, n_calls: 0, expected_usd: 0.0.
  • An all-vs-all query file that names a disabled catalog skill: the refusal is right, but the field is built twice, so stderr shows two different --catalog counts.

Checked, no issues

  • Estimate pricing: each call is priced on its own prompt and then multiplied by the repeats, so a long-context tier is not reached by the run total. The tiered-stub test pins this.
  • Partial all-vs-all exit: the noise and cost lines print before the failure exit. A clean run prints them after the attractors, as on main.
  • Repeats in all-vs-all: model_routing returns each query's repeats next to each other, so the slices in _model_arm group them correctly for _noise. The call count on stderr is multiplied by the repeats.
  • CLI snapshot and docs: cli-interface.json, the --seed help and skill-novelty.md match the new flags.

Comment thread dokimi/dokimi/novelty/routing.py Outdated
Comment on lines +431 to +453
def description_as_queries(description: str, *, limit: int = 40) -> list[str]:
"""Sentences of a description, for triage when the artifact has no usable heading.

A skill whose body is a ``#`` title and no ``##`` heading of two or more words still says
what it is for in its description. Each sentence with two or more content words is
offered, as :func:`headings_as_queries` offers headings. The same self-derived stamp applies.
"""
sentences = _SENTENCE_END.split(" ".join(description.split()))
return [s for s in sentences if len(tokenize(s)) >= 2][:limit]


def self_derived_queries(body: str, description: str) -> tuple[list[str], str]:
"""The triage queries of an artifact and where they came from.

Headings are preferred; the description's sentences are the fallback. Returns
``(queries, source)`` with ``source`` ``"headings"``, ``"description"``, or ``"none"`` when
neither gives a query of two or more content words.
"""
headings = headings_as_queries(body)
if headings:
return headings, "headings"
sentences = description_as_queries(description)
return sentences, "description" if sentences else "none"

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.

Blocking: The description fallback routes the gold card's own text, so its rates are near 1.0 by construction.

The gold card both arms rank is name + description, and these queries are sentences of that same description, so every query appears word for word in the gold card. Headings at least come from the body, which is not on the card. A repro: gold grill with the description "Grill the user about a plan. Use when stress-testing a design. Ask hard questions about tradeoffs." and only a one-word ## Steps heading, against three competitors that share plan, design, user and questions: queries_source: description, lexical R@1 1.0, MRR 1.0. Before this PR the same skill gave status: no_queries. So the 24 of 144 skills that were "not measured" now get a near-perfect figure, with the same triage stamp as a heading run, and a corpus-wide figure like the 0.225 in the docs moves up because of them. The changelog does not say this.

Suggestion: keep the no_queries refusal for a skill with no usable heading. If the fallback stays, the rate lines and the docs should say that a description-derived rate is a self-match and not comparable with a heading-derived one, and a corpus summary should leave these skills out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Waiting on a decision before I change this: should the description fallback stay?

Context: your point holds. A description-derived query scores R@1 1.0 and MRR 1.0 against its own gold card (a 3-sentence description, three competitors: queries_source: description). Before this PR those skills gave no_queries.

Options:
(a) Keep the no_queries refusal for a skill with no usable heading (your first suggestion). The description queries are dropped, and the "description queries" part of the PR goes.
(b) Keep the fallback, stamped as a self-match: the rate lines and the docs say it is not comparable with a heading rate, and a corpus summary leaves those skills out.

My recommendation: (a). A rate near 1.0 by construction should not reach a corpus figure, and (b) needs changes in the summary code too. The thread stays open until the author picks.

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.

Blocking: Option (a), please: keep the no_queries refusal for a skill with no usable heading.

One more reason: main already pins that refusal. test_headings_as_queries_on_a_skill_with_no_headings_is_a_usage_refusal from #327 expects exit 2 and a no_queries record for the door-check shape, and 66341f4 flips it to expect a description run with status: ok. With (a) that test goes back to main's version. description_as_queries, self_derived_queries, _sentences and the description part of queries_source, the help text, the docs and the changelog bullet go too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maintainer decision: (a), keep the no_queries refusal; the description fallback is dropped from this PR. Done in 1e2fa05.
The PR title and body are updated to match. A test pins the refusal for a skill with a description and no usable heading.

Comment on lines +517 to +519
if args.estimate_only:
print_routing_estimate(args, candidates, queries)
return

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.

--estimate-only returns before the no-usable-queries refusal, so it prices a run that would be refused as a free, valid run.

Repro: an artifact with the description Stop. and only ## Steps, run as novelty routing --queries-from-headings --model claude-sonnet-4-5 --estimate-only, prints n_calls: 0, expected_usd: 0.0 and exits 0. The same command without the flag exits non-zero with "no usable queries" and writes a no_queries record. The same happens with a --queries file of only stop words. A batch script that budgets with the estimate sees a zero-cost run instead of a refusal.

Suggestion: check tokenize on the queries (as selection_prompt_tokens already does) before the estimate branch, and exit with the same reason the real run gives.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0fdc281. --estimate-only now refuses with the real run's reason when no query has a content word: exit 2 with no usable queries, nothing estimated, nothing written. Pinned by test_estimate_only_refuses_the_run_the_real_run_refuses (a --queries-from-headings case and a stop-words-only file), which failed before the change.

The PR body still says the estimate exits 0 for such a run (its "declined" bullet). That line is out of date now and needs the author's update.

Comment thread dokimi/dokimi/commands/novelty.py Outdated
Comment on lines +435 to +436
everyone = _routing_field(argparse.Namespace(**{**vars(args), "include_disabled": True}),
set())

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.

_refuse_queries_naming_disabled builds the field a second time, so every field warning prints twice, with a wrong competitor count.

Repro: a catalog with one disable-model-invocation skill and a queries file that names it. stderr shows --catalog ...: 5 competitors and then --catalog ...: 6 competitors before the refusal; the second count includes the disabled skill, which is not in the field. Skipped-competitor and no-description warnings repeat too, and a large catalog is read twice.

Suggestion: the first _routing_field call already appends <name>: disable-model-invocation to left_out (the catalog notes too); pass left_out in and take the disabled names from it instead of loading the field again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0fdc281. The field is read once. The loader reports the names it left out for disable-model-invocation (--competitors and --catalog), and the refusal takes them from that list, so each field warning prints once and the count is right. Pinned by test_a_disabled_skill_named_by_a_query_warns_once_and_counts_the_field_once, which failed before the change.

gold=index, seed=seed + index)
gold=index, seed=seed + index, repeats=repeats)
rows = routed["per_query"]
groups += [rows[start:start + repeats] for start in range(0, len(rows), repeats)]

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 all-vs-all noise.per_query rows pool every skill's queries but do not say which gold each row belongs to.

In a single-artifact run there is one gold, so query is enough. Here two skills can list the same request (profile my binary for perf and for profiler), and you get two rows with the same query and different n_correct, with nothing to tell them apart or to read agreement per skill.

Suggestion: add the gold skill name to each group's row (for example gold) when building groups in _model_arm.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0fdc281. Each row of an all-vs-all noise block now names its gold skill, so two rows with the same query can be told apart. Pinned by test_every_noise_row_names_the_gold_skill_it_belongs_to, and docs/skill-novelty.md describes the field.

Comment thread dokimi/dokimi/novelty/routing.py Outdated
return [h for h in headings if len(tokenize(h)) >= 2][:limit]


_SENTENCE_END = re.compile(r"(?<=[.!?])\s+")

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.

Nit: The sentence split breaks after any period and space, so e.g. / i.e. / vs. cut one sentence into two queries.

"Profile code, e.g. PyTorch training loops, and report hotspots." gives "Profile code, e.g." and "PyTorch training loops, and report hotspots."; both have two content words, so both are routed.

Suggestion: do not split after a short lowercase token ending in a period, or skip common abbreviations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0fdc281. The sentence split no longer breaks after e.g., i.e., vs. or cf., so "Profile code, e.g. PyTorch training loops, and report hotspots." stays one query. Pinned by test_a_sentence_with_e_g_or_i_e_is_one_description_query, which failed before the change.

Comment thread dokimi/dokimi/novelty/all_vs_all.py Outdated

from .client import LLMClient
from .routing import BM25, Candidate, RoutingInputError, model_routing, tokenize
from .routing import BM25, Candidate, RoutingInputError, _noise, model_routing, tokenize

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.

Nit: all_vs_all.py now imports the private _noise from routing.py.

Someone changing a _ helper in routing.py will assume it has no users outside the module.

Suggestion: rename it to a public name (for example repeat_noise) now that two modules use it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0fdc281. The helper is public as repeat_noise in novelty/routing.py, and both modules import that name.

- --estimate-only refuses a run the real run would refuse (no query with a
  content word), with the same reason, and writes nothing.
- The disabled-skill refusal reads the field once: the loader reports the
  names it left out, so field warnings no longer print twice.
- Each all-vs-all noise row names its gold skill.
- A sentence split does not break after e.g., i.e., vs. or cf.
- _noise is public as repeat_noise: two modules use it.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 01:06

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

A lexical-only estimate incorrectly reports a query as sent when no selector calls occur because zero repeats still populate the prompt list.

1 open issue
What changed in this PR

1 new finding.

This PR extends dokimi’s routing evaluation with cost estimates, repeated all-vs-all measurements, and description-derived triage queries.

Changes:

  • Adds single-artifact --estimate-only and all-vs-all repeat, noise, and cost reporting.
  • Adds description fallback queries, clearer disabled-skill refusals, and precise seed help.
  • Extracts routing reporting helpers, expands regression coverage, and updates documentation.
File Description
dokimi/​tests/​test_novelty_routing.py Updates noise helper and no-query assertions.
dokimi/​tests/​test_novelty_routing_field.py Checks disabled-skill warnings are not duplicated.
dokimi/​tests/​test_novelty_routing_cli_gaps.py Covers estimates, description queries, and help text.
dokimi/​tests/​test_novelty_routing_all_vs_all.py Covers repeats, costs, refusals, and noise attribution.
dokimi/​scripts/​growth_baseline.json Lowers command-module growth baselines.
dokimi/​dokimi/​novelty/​routing.py Adds query derivation and estimation helpers.
dokimi/​dokimi/​novelty/​all_vs_all.py Pools repeated model results and labels noise rows.
dokimi/​dokimi/​commands/​novelty.py Wires CLI options and delegates reporting.
dokimi/​dokimi/​commands/​novelty_routing_report.py Centralizes query loading, estimates, refusals, and output.
dokimi/​docs/​skill-novelty.md Documents the expanded routing behavior.
dokimi/​docs/​reference/​cli-interface.json Records the new estimate flag.
dokimi/​changelog.d/​20261031_120000_napetrov_routing-cli-gaps.md Summarizes routing additions and fixes.

Review effort: Balanced

Comment thread dokimi/dokimi/novelty/routing.py Outdated
Every repeat sends the same prompts (see :func:`selection_prompt_tokens`). The reply is one
JSON pick, :data:`PICK_REPLY_TOKENS`.
"""
prompts = selection_prompt_tokens(candidates, queries)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 12f8c62. selection_estimate takes no prompts at repeats=0, so n_queries_sent and n_calls are 0 and n_queries keeps the supplied count. Red test first: test_a_zero_repeat_estimate_sends_no_queries failed on 0fdc281 with n_queries_sent 2.

Comment on lines +251 to +252
f"({model_arm['first_error']}); the partial result, with those queries "
f"left out of every rate, is in {out}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · medium
Must-fix: With --all-vs-all --repeats 2, one query can have a successful first call and a failed second call. _model_arm retains the successful call in the confusion matrix and own_rate, but this exit message says the query is left out of every rate. Describe failed calls as excluded from model-arm scoring, and distinguish that from repeat-noise rates, which exclude the entire query if any repeat fails. Update the matching warning in _cmd_routing_all_vs_all too, and cover a mixed-success repeat in a CLI test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 12f8c62. The exit message and the WARNING now say failed calls are left out of model-arm scoring, and a query with a failed repeat is left out of the repeat-noise rates. A query's good repeat stays counted. Red test: test_a_repeat_that_fails_after_a_good_one_keeps_the_good_pick_in_model_scoring (new file, test_novelty_routing_repeat_failures.py) failed on the old wording and the old estimate.

…lls leave out

--estimate-only without --model passed repeats=0, which still counted the
prompts, so n_queries_sent was non-zero for a run that sends nothing.

The failed-call message said the queries were left out of every rate. A
call that failed is only left out of the model-arm counts; a query with a
failed repeat is left out of the repeat-noise rates. Say both.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.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.

Review at 12f8c62. Three things are worth fixing before merge, plus one nit inline, and the merge waits for a decision on the description fallback.

@vlad-perevezentsev's blocking point on the description fallback is still open at this head: the fallback queries are sentences of the gold card's own description, so the rate is near 1.0 by construction. The author has handed the choice back and it waits for a decision. New evidence: the docs and the changelog still do not say a description run is a self-match, and skill-novelty.md puts the 0.225 corpus figure for self-derived queries next to the fallback text.

What I ran, on a Linux review host at 12f8c62:

  • make ci-local: 30 passed, 0 failed, 7 skipped; 11,996 tests; the changed-line coverage step passes.
  • A mutation run over the new code: 114 mutants, 38 of which pass the five routing test files. Most only change a message. Two decide reported values and are in the inline comments.
  • Reproductions for the code findings, with the LLM transport mocked.

Findings that cannot be anchored inline:

  1. The "Cost of --model in all-vs-all" paragraph is stale after this PR. docs/skill-novelty.md:693-698 says the model arm makes one call per query, that the call count is the number of queries with a content word, and quotes the stderr line all-vs-all: --model makes N model calls, one per query, .... With --repeats 5 the run makes five times that, and the code (novelty.py:394-396) multiplies by args.repeats and prints "one per query and repeat". A reader who sizes a budget from this paragraph under-counts by the repeat factor. Suggestion: say "one per query and repeat" and update the quoted line.

Checked, no issues

  • Estimate vs. run: the estimate prices every query, contentless ones included, once per repeat, and refuses exactly when the real run refuses (no query has a content word). --all-vs-all --estimate-only is a usage error.
  • All-vs-all repeats: groups are query-major and repeat-minor, n_unscored is multiplied by the repeats, and the noise rows pair with their gold through zip(..., strict=True). The partial-failure exit prints noise and cost before it exits.
  • Disabled-skill refusal: it runs before any client is built, the field is loaded once, and --include-disabled bypasses it.
  • CLI snapshot: cli-interface.json matches a fresh dump, and --estimate-only is the only entry added. All commits are signed off.
Bot points I checked that no longer apply at this head (4)
  • Copilot's point on the lexical-only estimate reporting a query as sent does not apply: at this head selection_estimate takes no prompts at zero repeats, so n_queries_sent and n_calls are 0.
  • exai-bot's point on pricing run-wide token totals as one completion does not apply: the estimate now prices each call's prompt and one pick separately, then multiplies by the repeats.
  • exai-bot's point that the cost and noise lines are unreachable after a failed call does not apply: they print before the failure exit, and a clean run prints them after the attractors.
  • exai-bot's point on the partial-failure wording does not apply: the exit message and the warning now say failed calls are left out of model-arm scoring, and a good repeat stays counted.

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

_SENTENCE_END = re.compile(r"(?<=[.!?])\s+")
# A period that ends an abbreviation, not a sentence: "e.g.", "i.e.", "vs.", "cf.".
_ABBREVIATION_END = re.compile(r"(?:^|\s)(?:e\.g|i\.e|vs|cf)\.$", re.IGNORECASE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug · low
Nit: The abbreviation fix still splits after parenthesized abbreviations. description_as_queries("Profile code (e.g. PyTorch training loops) and report hotspots.") produces "Profile code (e.g." and "PyTorch training loops) and report hotspots.", rather than one query, because ( does not satisfy (?:^|\s). Recognize opening punctuation before these abbreviations and add this case to the regression test; otherwise common description wording becomes two incomplete routing requests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8174695. The sentence splitter's abbreviation check now accepts an opening bracket or quote before e.g., i.e., vs. and cf., so Profile code (e.g. PyTorch training loops) and report hotspots. is one query. The case is added to test_a_sentence_with_e_g_or_i_e_is_one_description_query, which failed before the fix.

@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 94f84fa, which fix the nit from my review:

  • 94f84fa: dokimi/commands/novelty.py:824, docs/skill-novelty.md:621 and the changelog entry now say the list-price expected_usd instead of usd.

make ci-local at 94f84fa: 30 passed, 0 failed, 7 skipped.

Not approving yet. Still open: the three risks from my review; @vlad-perevezentsev on description fallback routes the gold card's own text, so its rates are near 1.0 by construction; unfixed, docs and changelog silent; @vlad-perevezentsev on keep the description fallback (stamped as a self-match) or drop it: the author handed the choice back.

… the estimate and the noise rows

- "(e.g." and "(i.e." no longer end a sentence, so "Profile code (e.g.
  PyTorch training loops) and report hotspots." is one query.
- A test records the prompts the run sends and checks each one's length
  against the estimate, so the two cannot drift apart unseen.
- A test with one skill, two queries and two repeats checks that each
  noise row names its own query and its own gold skill.

Thread: PRRT_kwDOVBg7g86yazxp
Thread: PRRT_kwDOVBg7g86yauiU
Thread: PRRT_kwDOVBg7g86yauiV
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The round changed --queries-from-headings: a skill with no usable heading
now takes its description's sentences as triage queries (changelog
bullet), so the door-check shape runs instead of refusing. The shape test
still expected the refusal from intel/main. A skill with neither still
refuses; test_novelty_routing_cli_gaps covers that case.

Thread: PRRT_kwDOVBg7g86yauiV
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.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.

The merge still waits on two points. The all-vs-all cost paragraph in skill-novelty.md was not updated for --repeats, and the description fallback is still undecided. The code since my last review is sound: the estimate-versus-run test and the per-query gold test were added, and the (e.g. …) splitter fix is right.

I agree with @vlad-perevezentsev's blocking point on the description fallback. At this head the fallback queries are still sentences of the gold card's own description, and neither skill-novelty.md nor the changelog says a description run is a self-match. The author handed the choice back, so it waits for a decision.

What I ran, on a Linux review host at 66341f4:

  • make ci-local: 30 passed, 0 failed, 7 skipped; the changed-line coverage step passes.
  • A mutation run over the two new tests and the splitter regex: 28 mutants. The estimate test catches the divisor, card numbering and run-side separator mutants. The survivors are small (no exact pin on PICK_REPLY_TOKENS, no two-skill all-vs-all case pairing each noise query with its own gold), so I left them out.

Findings that cannot be anchored inline:

  1. The "Cost of --model in all-vs-all" paragraph is still stale. docs/skill-novelty.md:693-697 says one call per query and quotes all-vs-all: --model makes N model calls, one per query, .... With --repeats 5 the run makes five times that, and novelty.py:396 prints "one per query and repeat". A reader sizing a budget from this paragraph under-counts by the repeat factor. This is the point from my earlier review; the commits since did not touch it. Suggestion: say "one per query and repeat" and update the quoted line.

Checked, no issues

  • Estimate vs. run: the new test compares each prompt the run sends with selection_prompt_tokens. Mutating // 4 to // 3 or // 5, dropping the position prefix, or a \n\n card separator in the run all fail it.
  • Noise gold: the new test with one skill, two queries and --repeats 2 fails when the gold list is one label per skill.
  • Splitter: (e.g., (i.e. and vs. stay inside one query; dropping the bracket class fails the test.
  • Commits: all 13 non-merge commits are signed off.
Bot points I checked that no longer apply at this head (4)
  • Copilot's point on the lexical-only estimate reporting a query as sent does not apply: at this head selection_estimate takes no prompts at zero repeats, so n_queries_sent and n_calls are 0.
  • exai-bot's point on pricing run-wide token totals as one completion does not apply: the estimate now prices each call's prompt and one pick separately, then multiplies by the repeats.
  • exai-bot's point that the cost and noise lines are unreachable after a failed call does not apply: they print before the failure exit, and a clean run prints them after the attractors.
  • exai-bot's point on the partial-failure wording does not apply: the exit message and the warning now say failed calls are left out of model-arm scoring, and a good repeat stays counted.

@vlad-perevezentsev vlad-perevezentsev 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 66341f4. The estimate refusal, the single field read, the gold on noise rows, the abbreviation split and the repeat_noise rename are all fixed and tested. The description fallback is still in, and I replied in its thread with my pick: drop it.

Agreed with @antonwolfy: the "Cost of --model in all-vs-all" paragraph in skill-novelty.md (lines 693-697) still says one call per query and quotes the old stderr line, while the run now makes one call per query and repeat.

What I ran, at 66341f4:

  • make ci-local --offline: 12,030 passed, 0 failed, 58 skipped, 6 xfailed. The one FAIL row is schema_compat, and it does not come from this PR. The PR touches no schema; the check reads the schemas main added after this branch's base.
  • My first-round repros again: --estimate-only on an artifact with no usable query now exits with "no usable queries". A queries file that names a disabled catalog skill prints one --catalog count. The description fallback still gives lexical R@1 1.0, MRR 1.0 with queries_source: description.
  • description_as_queries on e.g., (e.g. …), vs. and I.E.: each stays in one query.

Checked, no issues

  • Disabled names: _named_competitors and catalog_competitors collect the names they leave out on the first read, so the refusal does not load the field again.
  • Noise gold: one label per sent query, matched to the noise rows with zip(..., strict=True), so a length mismatch raises instead of mislabelling.
  • Zero-repeat estimate and failure wording: a lexical-only estimate sends nothing, and the partial-exit message says a good repeat stays counted.

…is refused

A skill with no usable heading is refused with no_queries again, as on main.
Its description's sentences are not triage queries: each scores near 1.0
against its own card. Removes description_as_queries and self_derived_queries,
the abbreviation split, the description help text, docs and changelog bullet.
Restores test_skill_repo_shapes from main and adds a refusal test.
Growth baseline lowered for novelty.py.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
The schema_compat check compares against the PR base; this branch lacked the
schema files main added since its base, so they showed as removed.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@napetrov napetrov changed the title feat(routing): estimate-only, all-vs-all repeats and cost, description queries feat(routing): estimate-only, all-vs-all repeats and cost Nov 2, 2026
The cost paragraph said one call per query and quoted the old stderr
line; with --repeats the run makes queries x repeats calls and prints
'one per query and repeat'.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Main already hinted at --include-disabled when a query named a disabled
skill; the change is that the message now names each such skill.

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

xaleryb commented Nov 3, 2026

Copy link
Copy Markdown
Contributor
  • Ran: the full suite, ruff, mypy, bandit and the benchmark gate on the head and on the head merged with main. I also re-ran each earlier reviewer's input at the head, and all of them pass.
  • skill-novelty.md "Cost of --model in all-vs-all": with --repeats 3 and 3 queries the run makes 9 calls and prints one per query and repeat. The paragraph still says one call per query and quotes the old line, which is the open point in the 12f8c62/66341f4 reviews. Expected: queries x repeats calls.
  • Changelog "Fixed" bullet on disable-model-invocation: main already refuses that query with an --include-disabled hint. The bullet and the refuse_queries_naming_disabled docstring say there was no hint. Expected: the change is that the message now names each disabled skill.
  • Pushed:
    • c921c24: the cost paragraph now counts query x repeat calls and quotes the current stderr line.
    • bb30d32: the changelog bullet and docstring now describe what main printed before.
  • Left for the author: the "Declined" bullets in the PR body about the estimate refusal and the double field read are out of date, since both were fixed in 0fdc281.

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

Approving bb30d32: CI green, and the automated review of this head raised nothing new. The findings from the comment above are fixed by the pushed commits, each with a test or a doc change.

@vlad-perevezentsev vlad-perevezentsev 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.

LGTM

I checked bb30d32. The description fallback is gone: a skill with no usable heading is refused with no_queries again, as on main. Everything raised in the earlier rounds is fixed, including the all-vs-all cost paragraph and the changelog wording.

Merge: after a rebase: it conflicts with current main (now with #328 in) in skill-novelty.md, commands/novelty.py, novelty/routing.py, growth_baseline.json and test_novelty_routing_field.py. #316 and #337 conflict with it too, in novelty.py and growth_baseline.json.

What I ran, at bb30d32:

  • make ci-local --offline: 12,514 passed, 0 failed, 58 skipped, 6 xfailed. The one FAIL row is schema_compat, which does not come from this PR: it touches no schema, and the check reads headroom.v1 and the repo_environment change that main added after this branch's base.
  • The first-round repro: a skill with a three-sentence description and only ## Steps, run with --queries-from-headings, exits 2 with "no usable queries" and writes status: no_queries.

Checked, no issues

  • Removal: description_as_queries, self_derived_queries and the sentence splitter are gone, and nothing still refers to them. queries_source is now headings or none, and the help, the docs and the changelog say the same.
  • Shape test: test_skill_repo_shapes.py is back to the merge-base version, so it expects the refusal for the no-heading shape.
  • Docs: the cost paragraph counts calls per query and repeat, and it quotes the stderr line the run prints.

Keeps intel#328's field rules (spec errors and name conflicts leave the field) and
intel#329's estimate-only, all-vs-all repeats and cost; the refusal of a query file
naming a left-out skill now names each skill with every reason that applies.

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

xaleryb commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Merged main (1cc9663) after #328. The field keeps #328's rules: a spec error or a name with conflicting copies leaves it. When a query file names a skill the field left out, the refusal now names each skill with every rule that applies (disable-model-invocation, spec error, name conflict) and the flag that keeps it. Estimate-only, all-vs-all repeats and cost are unchanged. Suite, ruff, mypy, bandit and the benchmark pass on Linux.

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

Approving 95631dc: CI green after main was merged in, and the automated review of this head raised nothing new. The refusal now names each left-out skill with every rule that applies, and the docs findings are fixed in c921c24 and bb30d32.

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

LGTM. The description fallback is gone, the cost paragraph counts calls per query and repeat, and the refusal for a query that names a left-out skill gives each skill's rules. One nit inline.

What I ran, on a Linux review host at 95631dc:

  • make ci-local: 30 passed, 0 failed, 7 skipped; the changed-line coverage step passes.
  • Reproductions with the LLM transport mocked: the estimate's n_calls against the calls a real run makes (single artifact and all-vs-all, repeats 1 and 3, contentless queries included) match; the all-vs-all stderr call count matches the calls made; a partial failure at --repeats 3 prints the cost of the calls that ran before the exit.
  • A mutation run over the new code, about 50 mutants: the survivors only change a label or a defensive guard (queries_source: headings is not asserted, PICK_REPLY_TOKENS is not pinned, a disabled-only --competitors skill is not asserted); none decides an exit code or a rate.

Checked, no issues

  • Description fallback: removed. A skill with a description and no usable heading exits 2 with no_queries and queries_source: none.
  • Refusal naming left-out skills: the field is read once; --include-disabled and --include-invalid drop only the rule they waive; a name that is also in the field is not refused.
  • Noise rows: each carries its gold, paired with zip(..., strict=True).
  • Commits: every non-merge commit is signed off.
Bot points I checked that no longer apply at this head (4)
  • Copilot's point on the lexical-only estimate reporting a query as sent does not apply: at this head selection_estimate takes no prompts at zero repeats, so n_queries_sent and n_calls are 0.
  • exai-bot's point on pricing run-wide token totals as one completion does not apply: the estimate prices each call's prompt and one pick separately, then multiplies by the repeats.
  • exai-bot's point that the cost and noise lines are unreachable after a failed call does not apply: they print before the failure exit, and a clean run prints them after the attractors.
  • exai-bot's point on the partial-failure wording does not apply: the exit message and the warning say failed calls are left out of model-arm scoring, and a good repeat stays counted.

Comment thread dokimi/docs/skill-novelty.md Resolved
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
@napetrov
napetrov merged commit c29e830 into intel:main Nov 4, 2026
16 checks passed
napetrov added a commit to napetrov/dokimi that referenced this pull request Nov 4, 2026
* feat(routing): estimate-only, all-vs-all repeats and cost, description queries

- routing --estimate-only prints the calls, tokens and list-price USD that
  --model would make, as JSON, and makes no model call.
- all-vs-all accepts --repeats with the same noise block as routing, and
  prints a cost block; the JSON gets the cost too.
- all-vs-all refuses queries for a skill that only sets
  disable-model-invocation, names it, and suggests --include-disabled.
- --queries-from-headings falls back to the description's sentences when
  the headings give no query; queries_source records which one was used.
- --seed help says it fixes the card order only and points at --repeats
  for answer noise.
- Routing console lines and query loading move to
  commands/novelty_routing_report.py to keep novelty.py under its ratchet.

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

* fix(novelty): routing estimate counts every sent query, all-vs-all refuses --estimate-only, repeats count calls

The model estimate skipped contentless queries that the run still sends,
--all-vs-all --estimate-only made paid calls, the stderr call count left
out the repeats, and the model arm's unscored and tied calls were printed
as queries. Fix each and cover it with a test.

Routing console output moves to commands/novelty_routing_report.py to keep
commands/novelty.py under its growth cap.

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

* fix(novelty): no derivation warning when --queries-from-headings derives nothing

With neither a heading nor a description sentence, the run refuses with
no_queries, but the warning still said the queries were derived from the
artifact. Print it only when a query was derived.

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

* fix(novelty): attractor line says calls, not queries, when the model arm repeats

With --repeats above 1 the model arm's sibling count is per call, so the
attractor line now names its unit the way the tie and unscored line does.

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

* fix(routing): price each estimated call, not the run's total

The estimate priced the run's summed prompt and picks as one completion. A
long-context tier applies to one call's prompt, so many short calls could be
charged the higher rate although no real request reaches it. Each query's
prompt and one pick are now priced on their own, summed and multiplied by the
repeats.

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

* fix(novelty): print the cost and noise of a partial all-vs-all run

The failure exit came before the cost and noise lines, so a run with some
failed calls hid the usage of the calls that succeeded. Those lines now print
before the exit; the success summary stays after it.

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

* test(routing): cover the estimate for a model with no list price

The per-call pricing added in 1c5348b returns None for an unpriced model; no
test reached that path, which left a patch-coverage gap.

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

* fix(novelty): keep the all-vs-all cost line on success; ratchet for the partial exit

CI on a6fc22d failed the growth ratchet: novelty.py was 1149 lines
against a baseline of 1147. The partial-failure exit moves into
exit_partial_all_vs_all in novelty_routing_report.py, so the command
body shrinks. The success path keeps its original order: the summary,
then the attractors, then the noise and cost lines. A test pins that
the cost line still follows the saved-file line on a clean run.
The baseline drops to 1146 as check_growth.py --update reports.

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

* fix(routing): answer review of estimate, gold and disabled-skill paths

- --estimate-only refuses a run the real run would refuse (no query with a
  content word), with the same reason, and writes nothing.
- The disabled-skill refusal reads the field once: the loader reports the
  names it left out, so field warnings no longer print twice.
- Each all-vs-all noise row names its gold skill.
- A sentence split does not break after e.g., i.e., vs. or cf.
- _noise is public as repeat_noise: two modules use it.

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

* fix(routing): zero-repeat estimate sends no query; say what failed calls leave out

--estimate-only without --model passed repeats=0, which still counted the
prompts, so n_queries_sent was non-zero for a run that sends nothing.

The failed-call message said the queries were left out of every rate. A
call that failed is only left out of the model-arm counts; a query with a
failed repeat is left out of the repeat-noise rates. Say both.

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

* Name the estimate's price key expected_usd in help, doc, changelog

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

* fix(routing): keep a parenthesised abbreviation inside its query; pin the estimate and the noise rows

- "(e.g." and "(i.e." no longer end a sentence, so "Profile code (e.g.
  PyTorch training loops) and report hotspots." is one query.
- A test records the prompts the run sends and checks each one's length
  against the estimate, so the two cannot drift apart unseen.
- A test with one skill, two queries and two repeats checks that each
  noise row names its own query and its own gold skill.

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

* test(routing): the no-heading shape falls back to its description

The round changed --queries-from-headings: a skill with no usable heading
now takes its description's sentences as triage queries (changelog
bullet), so the door-check shape runs instead of refusing. The shape test
still expected the refusal from intel/main. A skill with neither still
refuses; test_novelty_routing_cli_gaps covers that case.

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

* fix(routing): drop the description fallback; a skill with no heading is refused

A skill with no usable heading is refused with no_queries again, as on main.
Its description's sentences are not triage queries: each scores near 1.0
against its own card. Removes description_as_queries and self_derived_queries,
the abbreviation split, the description help text, docs and changelog bullet.
Restores test_skill_repo_shapes from main and adds a refusal test.
Growth baseline lowered for novelty.py.

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

* docs(novelty): count all-vs-all model calls per query and repeat

The cost paragraph said one call per query and quoted the old stderr
line; with --repeats the run makes queries x repeats calls and prints
'one per query and repeat'.

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

* docs(changelog): say what the disabled-skill refusal used to print

Main already hinted at --include-disabled when a query named a disabled
skill; the change is that the message now names each such skill.

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

* Document null queries_source for --queries runs

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

---------

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Co-authored-by: Anton Volkov <antonwolfy@gmail.com>
Co-authored-by: Alexander Rybkin <alexander.rybkin@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli-interface-change Changes dokimi's user-facing CLI surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants