Repository navigation
Conversation
db984b0 to
f8d6f9a
Compare
|
Live-eval attempt on Reason: no provider key was available on the machine that ran this. Result: passed = false. The changed roles ( To produce the receipt (budget cap $1.5, one call per role), run once per role with the matching key set, e.g. |
f8d6f9a to
5a8ab49
Compare
Live provider smoke defaults move off retired/shut-down ids and the request shapes are fixed for the new targets (SPEC Part A §A.2 NodeAgent). - gpt-5.5 -> gpt-6.1-sol, claude-3-5-haiku-latest -> claude-haiku-5-5, gemini-2.0-flash -> gemini-3.8-flash - delete nex-agi/nex-n2-pro (absent from OpenRouter catalog 2026-10-10) from the priority list, .env.example and README - drop temperature (400 on Haiku 5.5, removed on GPT-6, deprecated on Gemini 3.6+); 16-token caps -> 256; OpenAI branch sends max_completion_tokens + reasoning_effort low; Anthropic text read by type - add request-shape test (fake fetch) for all four providers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…read - clear NODEAGENT_*_MODEL overrides in beforeEach so a host-exported override no longer fails the default-resolution assertions - give the non-text fixture block a text field and assert it is excluded, so reverting the type filter in the Anthropic parser now fails the test Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ression test Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ider smoke
The smoke report recorded only the requested model, so none of the four
keyed runs could produce a receipt review would accept. Add the rule 3
fields; no model site, precedence or request shape changes.
- builders keep the response `model` (Gemini `modelVersion`) and usage;
OpenRouter `usage.cost` is recorded as costUsd, null elsewhere
- rows[{requestedModel, resolvedModel, echoedModel, usage, costUsd,
fallbackUsed}], candidateIds, echoedModels, command, environment
- testedCommit (git rev-parse HEAD), gitDirty (ignores docs/eval),
constantSha256 (this file at HEAD), budget with maxUsd stop
- fake-fetch tests: echoed model differs from requested and is recorded;
fallbackUsed set when the OpenRouter chain moves past index 0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…efusal row metadata spentUsd is null when any dispatched row has no reported cost (direct OpenAI, Anthropic, Gemini); the maxUsd guard reserves the per-request estimate before dispatch. gitDirty uses top-anchored pathspecs so it no longer depends on the caller's cwd. A refused Anthropic row now records echoedModel and usage. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…replies; quote receipt command The gitDirty probe was named *.tmp, which .gitignore:44 ignores, so git status never saw it and the test only passed on an already-dirty tree. The probe is now gitdirty-probe.txt, asserted not ignored, with a clean-tree precondition. A successful reply costing more than maxUsd now sets stoppedBy=maxUsd. The receipt command quotes whitespace arguments so it can be replayed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…command, request accounting An unreported-cost row no longer hides an over-budget reply: the known spend is compared to maxUsd even when spentUsd is null (retry after a 503, then a $0.11 reply, now sets stoppedBy). testedCommit, gitDirty and constantSha256 are captured by gitIdentity() before the first dispatch, so a non-git directory fails before any spend and a mid-run checkout change cannot misattribute the run. The receipt command is POSIX shell-quoted (semicolons, empty and quote characters replay). The credential is checked before a request is counted, so a missing key records requests:0 and no row. The gitDirty test now runs against an isolated temp git repo and no longer depends on the working tree. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s catch the regressions they name gitIdentity() now passes --untracked-files=all, so a user's status.showUntrackedFiles=no can no longer make an untracked file report gitDirty:false (SPEC-v3 A.0 rule 3 provenance). The gitIdentity test sets that config in the fixture and still expects gitDirty:true. It also asserts the docs/eval receipt leaves the tree clean from both the repo root and docs/ before any root probe file exists (catches a cwd-relative exclude), and constantSha256 equals the sha256 of the committed blob rather than only matching 64 hex characters. The non-git test title no longer claims main() ordering it does not exercise. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… replayable npx command; git tests get a time budget and assert the git error Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… at the estimate The post-request check summed only reported costs, so a 503 with no cost followed by a 0.095 reply (estimate total 0.105 > maxUsd 0.10) ended ok:true, stoppedBy:null. It now uses the same accounting as the pre-dispatch reservation. spentUsd stays null when any row is unknown. Adds the 503-then-0.095 fake-fetch case. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…at that sha constantSha256 used `git show HEAD:...` after a separate `git rev-parse HEAD`, so a checkout between the two calls produced a receipt naming commit A with commit B's hash. Hash `<testedCommit>:<script>` instead; a test moves HEAD between the two calls and asserts the hash belongs to the captured sha. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nking_budget, candidate_count, logprobs) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8b12bea to
f7963e2
Compare
Summary
Refreshes stale model defaults in the live provider smoke and fixes the request shapes the new ids require.
Runtime sites (9: migrate 4, freeze 5, defer 0)
Line numbers in the two tables below are SPEC/inventory coordinates on
origin/main(SPEC-v3 §A.2.3), so a reviewer can match them to the plan. Where the code now sits at headf7963e2: frozen chainscripts/nodeagent-live-provider-smoke.ts:68-71(nex-agi/nex-n2-prodeleted), defaults:212(openai) /:214(anthropic) /:216(gemini), env-chain resolverselectModelCandidates:202-210,openAiCompatibleChat:400.scripts/nodeagent-live-provider-smoke.ts:34z-ai/glm-5.2:35moonshotai/kimi-k2.7-code:36cohere/north-mini-code:free:37nvidia/nemotron-3-ultra-550b-a55b:38nex-agi/nex-n2-pro.env.example:29andREADME.md:172-176:177gpt-5.5gpt-6.1-sol:179claude-3-5-haiku-latestclaude-haiku-5-5(retired 2026-02-19):181gemini-2.0-flashgemini-3.8-flash(shut down 2026-06-01)nodekit.yaml:57E / R sites (5: migrate 1, freeze 3, defer 1)
smoke.ts:173env-chain resolverNODEAGENT_OPENROUTER_MODEL_PRIORITY ?? NODEAGENT_OPENROUTER_MODEL ?? constunchangedsmoke.ts:248openAiCompatibleChatsrc/features/node-agent/providers/piAiAdapter.ts:34src/features/node-agent/protocol/model.ts:60package.json:100pi-ai pinClass N (doc 4, example 2, other 2, test-assertion 1): docs loop =
README.mddefault-priority sentence and.env.examplechain only; no other doc/evidence file names a moved id (evidence trees are hash-bound, untouched).Breaking-parameter fixes
temperature: 0removed from OpenAI-compatible, Anthropic and Gemini bodies (400 on Haiku 5.5, removed on GPT-6, deprecated on Gemini 3.6+).SMOKE_MAX_TOKENS) at all three sites (thinking counts toward the cap).max_completion_tokens+reasoning_effort: "low", notemperature, no tools. OpenRouter branch keepsmax_tokens, notemperature.type === "text"; astop_reason: "refusal"response now throwsprovider_refusalbefore content is read (migration guide Haiku 5.5 checklist, [BLOCKS]).Test proof
tests/liveProviderSmokeRequestShape.test.ts(20 cases: 6 request-shape incl. an Anthropicstop_reason: "refusal"regression, 14 receipt; the request-shape cases also assert the SPEC §1.3 forbidden keys absent: GeminithinkingConfig.thinkingBudget/thinking_budget/candidateCount/candidate_count, OpenAIlogprobs/top_logprobs. Each assertion observed to fail against a mutant that adds the key to the builder, mutant reverted) drives the script's owncallProvider/selectModelCandidates/probeLlm/gitIdentity/receiptIdentitywith a fakefetch. The git-identity cases run against a throwaway temp git repo, so the suite passes on a dirty or untracked working tree (verified with an untracked file in the checkout).Request-shape cases fail against the old literals/shape. Re-proved at head
5ef49d8(figure measured then, not re-measured since: at later heads the not-a-repo test asserts/not a git repository/i, so the exact count may differ) by restoringorigin/main'sscripts/nodeagent-live-provider-smoke.tsunder the test as it was at 5ef49d8: 17 of 18 fail (first run at the first commit: 13 failed:expected 'claude-3-5-haiku-latest' to be 'claude-haiku-5-5',expected {...} to not have property "temperature").Round-5 fixes, each observed to fail without its fix: (a)
gitDirtycase: the probe was*.tmp, ignored by.gitignore:44, so git never saw it and the case passed only on an already-dirty tree. The case now runs against an isolated temp git repo (makeRepo(), probe fileprobe.txt, no dependence on the working tree), asserting a clean tree, then thatdocs/evaloutput is ignored, then that an untracked root file givesgitDirty: truefrom both the repo root anddocs/. With the:(top)anchors replaced by.the case fails withexpected [ true, false ] to deeply equal [ true, true ]. (b) A successful reply costing0.11againstmaxUsd 0.10now setsstoppedBy: "maxUsd"; (c) the receiptcommandquotes whitespace args. Against the previous head's script (3044810), (b) and (c) fail (2 failed | 13 passed).Round-6 fixes: (a)
budget.stoppedBynow compares the known (non-null) spend tomaxUsdeven when another row reported no cost; a retry after an HTTP 503 followed by a0.11reply stops the run (spentUsdstaysnull,stoppedBy: "maxUsd"; the test fails when the oldspentUsd !== nullguard is restored). (b)gitIdentity(cwd)replaces the git calls insidereceiptIdentity; thegitDirtycase uses a temp repo, sonpm test/npm run checkno longer depend on the state of the working tree. (c)main()capturestestedCommit,gitDirty,constantSha256before the first paid dispatch, so a non-git directory fails before any spend and a checkout change mid-run cannot misattribute the run. (d) The receiptcommandis POSIX shell-quoted (;, empty and'arguments replay). (e) A missing provider key is detected before a request is counted:requests: 0, no row.Round-7 fixes: (a)
gitIdentitypasses--untracked-files=all; the test setsstatus.showUntrackedFiles=noin the fixture and still expectsgitDirty: true(fails without the flag). (b) The gitIdentity test now assertsgitDirty === falsefrom the repo root and fromdocs/afterdocs/eval/receipt.jsonis written and before the root probe (fails with a cwd-relative:(exclude)docs/eval), and assertsconstantSha256equals the sha256 of the committed blob. (c) The non-git test is retitledgitIdentity throws outside a git checkout; the call-before-dispatch ordering inmain()is not covered by a test and is not claimed to be.Check output (
npm run prepush, the repo's declared check)Branch rebased onto
origin/maina918e85 (0 behind, 12 ahead). Re-run 2026-10-15 on a clean tree at headf7963e2(git statusclean before the run; no provider keys in the environment).npm run prepushexit 0: 12 test files, 137/137 tests, live smoke SKIP, build ok, 0 vulnerabilities. The request-shape file alone (npx vitest run tests/liveProviderSmokeRequestShape.test.ts): 20/20. The log is local to the author's machine and not part of the PR, so the output below is the evidence (earlier heads' counts, e.g. 135/135 at32c56aa, no longer apply):prepushrewrites fourdocs/eval/*.jsonsmoke outputs; those were restored (not committed). No paid provider calls were made.Notes
.env.example:29is uncommented, so anyone who copied it keeps the old list includingnex-agi/nex-n2-pro; they should re-copy or remove that entry.needs-live-eval(block, not a note): merge waits for keyed manual runs, one per migrated provider. The barenpm run nodeagent:live-provider:smokeis NOT sufficient:selectProviderpicks the first keyed provider (openrouter, openai, anthropic, gemini), so on a machine withOPENROUTER_API_KEYit only tests the frozen chain, and the npm script hard-codes one--json-out. Required runs (all four before the label is removed; run each separately, attach each output):npx tsx scripts/nodeagent-live-provider-smoke.ts --provider openai --model gpt-6.1-sol --json-out docs/eval/nodeagent-live-provider-smoke-openai.json(gpt-6.1-sol)npx tsx scripts/nodeagent-live-provider-smoke.ts --provider anthropic --model claude-haiku-5-5 --json-out docs/eval/nodeagent-live-provider-smoke-anthropic.json(claude-haiku-5-5)npx tsx scripts/nodeagent-live-provider-smoke.ts --provider gemini --model gemini-3.8-flash --json-out docs/eval/nodeagent-live-provider-smoke-gemini.json(gemini-3.8-flash)npx tsx scripts/nodeagent-live-provider-smoke.ts --provider openrouter --json-out docs/eval/nodeagent-live-provider-smoke-openrouter.json(nex removal; frozen chain still reachable)--modelis pinned on the three direct-provider runs becauseloadEnvFilesloadsNODEAGENT_LIVE_MODELandNODEAGENT_<PROVIDER>_MODELfrom.env.localandselectModelCandidateshonours them, so a stale override there would silently test another model (the receipt would be rejected under rule 3, but the paid run is wasted). The OpenRouter run is unpinned (no--model), andselectModelCandidates(scripts/nodeagent-live-provider-smoke.ts:205) checksNODEAGENT_LIVE_MODELfirst, thenNODEAGENT_OPENROUTER_MODEL_PRIORITY, thenNODEAGENT_OPENROUTER_MODEL; any of the three replaces the frozen chain, andloadEnvFilesdoes not override variables already in the process env. Before the OpenRouter run, unset all three in the shell (unset NODEAGENT_LIVE_MODEL NODEAGENT_OPENROUTER_MODEL_PRIORITY NODEAGENT_OPENROUTER_MODEL) and remove them from.env.localand.env; after the run, check that the receipt'scandidateIdsequals the four-entry frozen chain (z-ai/glm-5.2,moonshotai/kimi-k2.7-code,cohere/north-mini-code:free,nvidia/nemotron-3-ultra-550b-a55b), otherwise the receipt is rejected under rule 3 and the run is wasted. Risk the keyed receipt may surface: Haiku 5.5 (adaptive thinking, medium effort by default) and gemini-3.8-flash (default thinking) count thinking tokens toward the spec's 256-token cap, so a reply could end inmax_tokenswith no text and the smoke would fail closed (expected NODEAGENT_LIVE_OK); the migration guide's lever then is effortlowor a larger cap.Estimated cost: a few cents total (4 runs, one ~30-token reply each; the OpenRouter chain may make up to 4 attempts). Not run here (no paid calls).
Receipt schema (§A.0 rule 3) — fixed in this PR. The smoke report now carries
command(POSIX shell-quoted, so semicolons, empty and spaced arguments replay),candidateIds,echoedModels,rows[{requestedModel, resolvedModel, echoedModel, usage, costUsd, fallbackUsed}],testedCommit(git rev-parse HEAD),gitDirty(top-anchored pathspecs and an explicit--untracked-files=all, so it is the same from any working directory and independent of the user'sstatus.showUntrackedFiles; ignoresdocs/eval, the evidence directory),constantSha256(this script as committed attestedCommit, read once from a singlegit rev-parse HEAD; recompute withgit show <testedCommit>:scripts/nodeagent-live-provider-smoke.ts | sha256sum),budget{maxUsd, estimateUsd, spentUsd|null, requests, stoppedBy}andenvironment.echoedModelis the responsemodel(Anthropic/OpenAI/OpenRouter) ormodelVersion(Gemini);fallbackUsedis true when the OpenRouter chain moves past index 0; a budget stop (including a reply whose reported cost exceedsmaxUsd, even when an earlier attempt reported no cost) setsok: false. The git identity is read before the first dispatch. No model site, precedence or frozen site changed. Runtime behaviour does change in the smoke: the OpenRouter chain stops when committed spend (the reported cost, or the$0.01estimate only where a row reports none) plus one more$0.01estimate would exceedmaxUsd $0.10; the cap is in dollars, not attempts, so cheap failed replies that report a small cost can run more than 10 attempts, and a budget stop fails the run (ok: false, exit 1). Fake-fetch tests assert a differing echoed model is recorded. Limits:costUsdis only filled where the provider reports it (OpenRouterusage.cost), otherwisenull, and thenbudget.spentUsdisnull(unknown), never0. Before each dispatch the smoke requires committed spend (reported cost, or the$0.01estimate for an unreported row) plus one more estimate to stay withinmaxUsd, so the cap holds for direct providers too.estimateUsdis a flat$0.01per request, not a price table: it bounds the built-in and frozen ids (gpt-6.1-sol at 256 output tokens is about$0.0026), but an operator-suppliedNODEAGENT_OPENROUTER_MODEL_PRIORITYof pricier ids can overshootmaxUsdby up to one request; such a run endsstoppedBy: "maxUsd",ok: false, so it is flagged, not silent. A refused Anthropic row recordsechoedModelandusagebefore the run fails. Merge still waits for the four keyed runs (not run here, no paid calls); a receipt with a fallback row,gitDirty, orstoppedByis rejected and re-run.SPEC GAP for Homen (§C), rule 10, not introduced by this PR: the smoke's explicit operator overrides pass an unknown id to the provider unchanged.
--model <id>andNODEAGENT_LIVE_MODEL=<id>return[id]fromselectModelCandidates(scripts/nodeagent-live-provider-smoke.ts:204-205at head), andcallProvidersends it as given (reproduced offline with a fake fetch:unverified-review-modelwas sent asmodel). §A.0 rule 10 says any other unknown id throws at the first resolver it reaches. This PR leaves it alone on purpose: the behaviour is identical onorigin/main, §A.0 rule 4 says caller arguments and override precedence are untouched, and §A.2.3 freezes this resolver. Scope of the gap: no unknown id is a default (every built-in default is a §1.2 id or a frozen OpenRouter id), the operator must type the id themselves, and the default chain never turns a free-lane request into a paid one (a free-lane request is never rewritten). Not covered by that: an operator-suppliedNODEAGENT_OPENROUTER_MODEL_PRIORITYlist that mixes a:freeid with a paid id can fall from the free id to the paid one when the free id fails; this is identical onorigin/mainand bounded by themaxUsdcap. Decision needed: either accept operator overrides as an explicit rule 4 carve-out of rule 10 for this smoke, or make the smoke reject ids outside the §1.2 set and the frozen chain in a follow-up (this changes how--modelbehaves).Round-8 fixes (
32c56aa): smoke budget wording no longer claims a hard bound; receiptcommandrecordsnpx tsx ...(replays verbatim); the git-spawning tests have a 30 s timeout (assertions unchanged); the not-a-repo test asserts/not a git repository/i.Round-10 fix (
8b12bea):gitIdentityreadgit rev-parse HEADand then hashedgit show HEAD:<script>in a separate call, so a checkout between the two would give a receipt naming commit A with commit B's hash. It now reads HEAD once and hashes<testedCommit>:<script>. A request-shape test moves HEAD between the two calls (via a pass-throughnode:child_processhook) and asserts the hash is the captured commit's blob; reverting the fix toHEAD:fails it. The PR-body changes in this round:--modelon the keyed commands, the stale-override and thinking-token notes, and the mixed-list sentence. In the verifyingprepushrun, the first attempt had one 5000 ms timeout in the untouchedtests/nodeAgentChatAdapter.test.ts(a load flake, 136/137); the immediate re-run was 137/137, exit 0.Round-11 fixes (
f7963e2, after rebase onto a918e85): the request-shape test now asserts the SPEC §1.3 forbidden keys absent (defect: a mutant addingthinkingConfig: { thinkingBudget: 0 }, candidateCount: 1to the GeminigenerationConfigpassed 20/20; it now fails, as does alogprobs: truemutant on the OpenAI body). The keyed receipts must be taken at headf7963e2or later sotestedCommitis an ancestor of the PR head (rule 3); a receipt taken at the pre-rebase8b12beais not.Round-9 fixes (
b5f8e05): the end-of-request budget check now counts a row with no reported cost at the$0.01estimate, the same accounting as the pre-dispatch reservation. Before, a 503 with no cost followed by a$0.095reply (estimate total$0.105vsmaxUsd $0.10) endedok: true, stoppedBy: null; now it endsstoppedBy: "maxUsd"whilespentUsdstaysnull(a row is unknown). The new fake-fetch case fails against the previous script and passes now.Spec: model-defaults SPEC Part A §A.2 (NodeAgent)
🤖 Generated with Claude Code