feat(eval): add the DeepSeek Harness benchmark arm - #2971
Conversation
|
I found five blockers / follow-ups before using this arm for the full cohort:
CI is green and the focused smoke is useful, but these paths are not exercised by that smoke and can change full-cohort reliability or cause destructive local behavior. |
|
I fixed the interrupted-settlement usage gap on top of this PR. The implementation is available as commit Root cause: external provider usage only lived in the wrapper process until the final result frame was emitted. Framework timeout, wrapper interruption, or result-frame loss could therefore discard already-confirmed usage. The first real-machine pass also found that Fix:
Verification:
The real-machine result should be treated as authoritative for the container ownership and Harbor lifecycle behavior. |
|
Thanks — all five are addressed. Pushed as three commits, plus one that predates the review but changes what several of these findings touch, so I'll start there. The arm no longer patches a stock profile. It previously inherited 5 — exhaustive profile admission. I went with a This one earned its keep immediately: moving the preparers into a module-level 4 — metering read before requests settle. Replaced the eight getters with one 3 — undici's 300s inactivity timeout. The dispatcher now sets 1 — destructive I did not add build-to-temp-then-publish. It removes no failure mode the guard leaves open, and the publish step still has to delete the previous build, so the destructive operation moves rather than disappears. 2 — identity did not pin the dependency tree. Dependencies now install with The complete-tree digest half of your suggestion was already in the branch: the fingerprint is The lockfile is 9,035 of the diff's 10,636 lines. Another 900 are the task entries, byte-identical to the eight-arm spec (the CLI has no subject filter, so a single-arm cohort needs its own file). Roughly 700 lines are actual logic. Also still open, and not from this round: Re-verified end to end after the changes: Harbor trial on |
|
Ported as
Applied unchanged. Rewritten. The checkpoint invariants your decoder enforces hold by construction here, because every counter mutation is synchronous with no await between the paired increments — One change to your version. I first put the opening Verified locally: 33/33 Eval Node tests, including your abrupt-exit regression test running against the rewritten proxy. Typecheck, lint and format clean. I did not reproduce your fault smoke — your VMSS run stands as the authority for the container ownership and Harbor lifecycle behavior. The README now documents the checkpoint and the complete/lower-bound distinction, so the deferral note in my previous comment no longer applies. |
|
One more thing on the branch: Comparing the single-arm harness spec against the eight-arm cohort means comparing two runs on two occasions, which carries a difference no setting removes. This spec puts both arms in one task group instead — each task runs one Maka container and one harness container at the same moment, same executor, metering proxy, egress policy, verifier and budget. Benchmark, tasks, verifier, budget and executor config are taken unchanged from the existing specs; only the subject list, the toolchain mounts and the concurrency limit differ. 89 tasks, 178 cells, 64 groups so the pair meets the same 128 concurrent trials the eight-arm cohort reaches at its declared 16. Would you be willing to run it on the VMSS host? I have no benchmark machine on my side — the alternative is standing one up from scratch, which costs more in setup than the run does. You already have the pier environment, the egress proxy image and both toolchains there. Two things worth flagging if you do: The toolchain has to be built on whichever machine runs the cohort, and the fingerprint currently pinned in
The build compiles If a subset would be more useful than the full 89 first — say 20 tasks at lower concurrency to see the shape before committing the machine time — say so and I will cut one. |
36f8f11 to
e5d274c
Compare
|
@hqhq1025 three things for when you pick this up — two of them are about your PRs, not this one. Your #2963 and my #2963 also adds Correction to what I said earlier about the fingerprint. I told you it has to be rebuilt on whichever machine runs the cohort. That is only true of a rebuild. The build product is already The head-to-head cohort now on the branch is blocked on three of your and our open PRs, which the PR body now spells out: #2960 (the isolation contract is bypassable and contamination rules never matched the hostname), #2962 (the Maka arm gets cut off by a local idle timeout), and #2963 (admission classification). Running before those land produces numbers we would have to discard. #2967 can land after. So there is no rush on the run itself — the blocking work is those three, not machine time. No hurry on any of this tonight. One smaller thing from your #2963 that I applied here: fencing the task prompt with |
bab9bca to
92d0651
Compare
|
@hqhq1025 morning summary, so you do not have to reconstruct last night from the thread. Nothing needs running yet. The head-to-head cohort is blocked on code, not on machine time, so there is no rush on the VMSS host. Status of the four as of now:
#2963 is the one that needs you. It has been at Why the other two block: #2960 — the isolation contract is bypassable and contamination rules never matched the hostname, so numbers taken before it cannot rule out contamination. Note the scope is narrower than "then it's clean": per that PR's author, the fixed filter addresses a subject that stumbles onto an answers page, while #2976 and #2977 leave channels outside the audited path entirely, so it stays a blocklist rather than a proof of isolation. #2962 — the Maka arm gets cut off by its own idle timeout, which would depress one arm for reasons unrelated to the arm. On this PR since your last look: the interrupted-settlement port ( Also, correcting myself from yesterday: the toolchain does not have to be rebuilt on the benchmark host. The build product is already |
DeepSeek Harness ships no benchmark runner of its own: BENCHMARK.md points at the checked-in examples/jsonrpc-agent minimal composition and says to give each task its own workspace and session. This arm reproduces that composition's model-facing surface on the published CLI, so the toolchain stays an ordinary npm install directory like every other external arm rather than a second toolchain shape carrying its own Python interpreter. The patch layer was validated against the upstream composition request-for-request over a recording proxy: identical tool names, byte-identical tool schemas, and a byte-identical system prompt. Two deliberate deviations: reasoning is pinned to max because the upstream default resolves to reasoning_effort=high on the wire and would leave this arm reasoning less than the rest of the cohort, and the headless profile's extra 64-token session-title call is disabled because Maka's Eval profile has no equivalent and its cost would land in this arm's metering. The harness prints only the final assistant message, so completion is classified the way zcode already is: a clean exit with output. The toolchain fingerprint is a placeholder until the benchmark host builds the pinned 0.1.0-rc.6 toolchain; verification fails closed until then. Generated-by: Claude Code
…able The wrapper and the subject adapter each carried their own hand-written list of external profile names. Adding an arm meant editing three places, and a type predicate hides the omission from the typechecker: the wrapper rejected the new profile at startup, and bundledProfile silently skipped its toolchain verification instead of failing. TOOLCHAIN_IDENTITIES already has to name every admissible profile, so it is the registry. Both call sites now ask it. Generated-by: Claude Code
The harness's minimal composition depends on two native modules, and node-pty publishes no Linux prebuild, so the toolchain cannot be installed on a macOS host and mounted into the linux/amd64 task image. It is built inside a matching container, which also supplies the pinned Node. The toolchain now carries that Node and the subject executes it directly. Resolving the executed path inside the mounted root is what makes the preflight identity check meaningful; the previous arrangement found Node through PATH, leaving the executed binary outside the harness toolchain root and silently taking the unverified-command path. Verified end to end: verifyToolchainDirectory accepts the built tree, and a Harbor trial reports the real version and fingerprint rather than the test-command bypass. Generated-by: Claude Code
The arm's two upstream deviations, the container-only toolchain build, and the timeout-with-reward outcome are all decisions a later reader would otherwise have to reconstruct from the patch layer and the executor. Generated-by: Claude Code
…list The arm inherited the stock `headless` profile and disabled eighteen plugins to reach upstream's minimal tool surface. A blacklist only holds while the bundles it subtracts from stay still: an upstream release adding a plugin would have widened this arm's tools with nothing to report it, and reviewing only the disables meant never reviewing the defaults of what stayed. Two of those defaults had already diverged from upstream unnoticed — the sandbox ran `workspace-write` instead of `danger-full-access`, and every request carried a runtime-context snapshot upstream suppresses. A profile whose manifest declares no bundles composes over an empty entry list, so the checked-in composition names every entry the model can observe. Drift becomes impossible rather than detectable, and a missing service is a boot failure rather than a silent downgrade. Verified against upstream request-for-request: identical tool names, byte-identical tool schemas, byte-identical system prompt, identical message sequence. The model now reaches the harness through that composition. It previously travelled as a `--model` flag `dsh` does not accept, which the wrapper stripped back out by index and re-exported as `DSH_MODEL` — a variable no harness code reads, so the arm ran on the adapter's default and only matched by luck. The toolchain fingerprint was a hash of the script's own spec literal, making verification an identity. It is now the digest of `checksums.sha256`, so the committed constant pins the manifest and the manifest pins the tree. Task groups hold every subject for one task, so a single-arm cohort at the eight-arm concurrency limit would have run at an eighth of its machine load. Generated-by: Claude Code
Three defaults were standing in for decisions this wrapper has to make. `prepareProfile` dispatched through an if/else chain ending in an unguarded `else` that prepared zcode. Since profile admission became registry-derived, any name added to TOOLCHAIN_IDENTITIES is accepted, so a profile whose branch was never written would have been prepared as zcode and run. A table keyed by ExternalProfile makes that a compile error instead. The table has to be initialized before the argv-driven code that calls it, so it sits above it. Metering was read through eight getters immediately after the child exited, while a final provider request could still be in flight — admission, usage and cost could all be computed from an incomplete count, and the settle that `close()` already performs came too late to help. Reading is now a single `report()` that settles first, so an unsettled read is no longer expressible. Provider requests inherited Undici's 300-second headers/body inactivity timeout. A reasoning model may legitimately be quiet for longer, and aborting locally would turn a slow answer into an infrastructure failure; the benchmark's own agent timeout is meant to be the only deadline. Generated-by: Claude Code
`npm i @deepseek-ai/dsh@0.1.0-rc.6` re-resolved several hundred caret ranges on every build, so one harness version could mean two different installed trees while claiming the same benchmark identity. Dependencies now come from a reviewed lockfile through `npm ci`, the lockfile ships inside the toolchain for later inspection, and the build refuses to start when the lockfile and the spec disagree about which harness version is being installed. `--out` is the one destructive path in this script: it was resolved and removed recursively before anything was validated, so a mistyped `--out .` would have taken the repository with it. It now refuses `/`, the repo root, the working directory and the home directory outright, and otherwise only removes a directory that is empty or holds a previous build of this toolchain. Generated-by: Claude Code
External provider usage lived only in the wrapper process until the final result frame was emitted, so a framework timeout, a killed wrapper, or a lost frame discarded usage that had already been confirmed on the wire. A run that is cut off is exactly the run with the most tokens spent. The metering proxy now writes its snapshot at the start and the settlement of every request, renaming it into place so a reader sees one whole snapshot or the previous one. /logs/agent is created under umask 077, which silently downgrades the mode argument to 0600 and leaves the checkpoint unreadable by the host Eval process, so the mode is restated after the rename. The executor recovers complete or lower-bound usage and cost from the trial's live agent/ directory, records the provenance and the in-flight and missing-usage counts that establish it, retains the verified toolchain identity on recovery paths so scored attempts stay reusable, and continues to the verifier when model work was admitted before the frame was lost. One snapshot shape serves both readers, so a settled report and an on-disk checkpoint cannot disagree about what a field means. Ported from hqhq1025's 86091ae on codex/pr2971-failure-usage, rebased onto the settled report() and profile registry that landed after it. Generated-by: Claude Code
The single-arm harness spec and the eight-arm cohort are separate runs on separate occasions, so comparing them carries a difference no setting can remove. This spec puts both arms in one task group instead: each task runs one Maka container and one harness container at the same moment, against the same executor, metering proxy, egress policy, verifier and budget. Benchmark, tasks, verifier, budget and executor configuration are taken unchanged from the existing specs; only the subject list, the toolchain mounts and the concurrency limit differ. Two subjects per group means 64 groups reaches the same 128 concurrent trials the eight-arm cohort reaches at 16. Generated-by: Claude Code
The other arms separate the task prompt from their own flags so a prompt that starts with a dash cannot be read as one. This arm did not, and the asymmetry invited the reader to conclude the arm had been overlooked. It had not been broken: `dsh` combines allowUnknownOption with passThroughOptions, so a dash-leading prompt already reaches the profile verbatim, verified against the commander build the toolchain ships. What the separator adds is the case where the prompt exactly matches a known option -- `--help` alone would print usage and exit -- and one rule that reads the same across every arm. Generated-by: Claude Code
Two properties of the paired cohort do not follow from the framework, and reading its results without them produces a confident wrong answer. Task groups are an execution unit. Cells are grouped by task and repetition to schedule concurrency, but result selection is per cell and drops a cell with no selectable attempt on its own, so losing one arm leaves the other arm's observation in the data as an unmatched sample. Loss rises with container lifetime and request count, so the survivor is systematically the arm that finished sooner. Averaging each arm's surviving cells reads that bias as a result; re-pairing by task and discarding unmatched observations does not. The egress audit is symmetric -- egressProxy is executor configuration and no subject overrides it -- but what it proves is bounded. The URL policy is a blocklist for known contamination surfaces, which addresses a subject that stumbles onto an answers page and does not address one deliberately looking. Issues #2976 and #2977 describe channels outside the audited path. Generated-by: Claude Code
92d0651 to
f3b9310
Compare
|
Real-machine follow-up from the 89-task DSH run: I found and fixed two Eval-specific reliability defects, then reran every affected cell. Root causes:
Fix: commit
Regression verification:
Real VMSS results using complete-attempt replacement:
All seven verifier logs are free of the former dpkg/curl/uvx setup failure. DSH moves from 61/89 (68.5%) to 65/89 (73.0%). Artifacts:
|
The DSH profile inherited a five-minute deadline on both terminal-bash layers. Under cohort load apt-get was killed while dpkg was configuring tzdata, leaving iU packages behind, and six verifiers then failed before reaching their tests with a broken dpkg, a missing curl, or a missing uvx. Removing the deadline showed the install was not slow but waiting on an interactive tzdata prompt, so the profile also has to declare a noninteractive package environment. The harness owns its persistent PTY process tree and kills every descendant on normal shutdown, which removed a task's own background service before the shared-environment verifier could reach it. The toolchain build now applies a fail-closed patch to @deepseek-ai/dsh-subprocess-local that skips the group kill only under DSH_PRESERVE_BACKGROUND_PROCESSES, and the relay preserves the subject process group only for successful exits; failure, cancellation, transport loss, and framework timeout still quiesce it. Command lifetime now defers to the benchmark deadline, so the framework remains the only authority on when a subject is out of time. The pinned fingerprint follows the patched toolchain. Ported from hqhq1025's 28ebe09 on codex/dsh-eightarm-integration, rebased onto the profile registry and the isolation probe that landed after it. Verified on that branch against the real cohort: four cells moved 0 -> 1 and the remaining three failed on their own merits, taking DSH from 61/89 to 65/89. Generated-by: Claude Code
|
Ported both fixes here as Four conflicts, none of them mechanical, so the resolutions are worth naming:
That last one is the only thing needing a decision before the run. The pinned constant now names your Verified here: Eval Node 34/34, Blocker status after this: #2960 merged, so #2962 and #2963 are what remain. #2962's CI is green now and only the merge gate is left. #2963 is still at 两个修复已移植为 四处冲突都不是机械冲突,解法说明如下:
最后一条是开跑前唯一需要拍板的:pin 的常量现在指向你构建的 本地验证:Eval Node 34/34, 阻塞项现状:#2960 已合并,剩 #2962 和 #2963。#2962 CI 已绿,只差合并门。#2963 仍停在 Drafted with Claude Code; the port, the conflict resolutions, and the verification above were reviewed by me. |
Three defects in this arm turned out to be one mistake made three times: a fact that crosses a process boundary was represented twice, replaced by a proxy, or recorded too late to cross at all. The pinned toolchain fingerprint was copied. Verification compared the constant against the value the tree reported for itself in manifest.json, so a directory of arbitrary content with an internally consistent checksums.sha256 was accepted as the pinned toolchain, and the pin bound nothing. The digest is now recomputed from the checksum manifest on disk. This affects every external arm, not only this one. The test that covered it asserted the acceptance, which is why the hole survived review; it now proves both directions, including a file changed together with its own checksum line. The subject's semantic status was replaced by a proxy. The relay decides whether to tear down the subject's process group, and a task's own background service has to survive that to reach the shared-environment verifier. The relay reads an exit code, but the wrapper exited zero whatever it had classified, so the status was carried instead by a flag set before the subject ran — and set for one profile only. A failed DeepSeek Harness subject therefore kept its services through verification while every other arm lost theirs, which is a framework capability difference inside a comparison whose independent variable is supposed to be agent behaviour. The wrapper now exits nonzero unless the subject completed, the relay decides from that alone for every subject, and the protocol field is gone. Where the result frame is readable it remains the authority. Provider admission was recorded too late. It was counted after the response stream finished, so a wrapper killed mid-stream left a checkpoint claiming no admission, and the executor retried a cell whose model work had already been done and billed. Admission is a fact about the provider, not about the request finishing: it is now recorded and persisted when observed, and the recovery invariant that made "admitted while in flight" unrepresentable is gone. The checkpoint also carried four derived values — settled requests, missing usage, completeness, and cost — which the reader then validated against their own definitions. Cost had drifted: the settled path gated it on completeness and the checkpoint path did not, so an interrupted attempt reported a lower-bound cost as if it were settled. The checkpoint now carries only observed counts and both sides derive the rest through one function, which deletes the validation along with the possibility of disagreement. Package installs run unattended in every container, so DEBIAN_FRONTEND and TZ move from this arm's profile to the shared execution environment. Three tests are removed rather than adapted: two searched source and data files for string literals, and the third fed the toolchain patcher a copy of its own anchor text. The patcher test now asserts what earns its keep, that upstream drift fails the build. Found by adversarial review; the process-group asymmetry and the fingerprint hole were each found independently by two reviewers. Generated-by: Claude Code
The relay cannot read a subject's result frame, so it decides from the exit code alone whether the subject's process group may outlive it. The external wrappers were changed to exit nonzero on a failed run, but the Maka shim still exited zero, which only moved the asymmetry rather than removing it: Maka became the arm allowed to leave background services standing through the verifier. Project the same status from both, and drop the adapter's exit-code re-check now that the frame it already has carries the same projection. Also fix two defects the same pass left behind: - `_finalize_exited_scope` lost a parameter, but one caller in `test_relay_lifecycle.py` still passed five. That test is skipped without `setsid`, so it only failed on Linux. - The toolchain `--out` guard treated any directory containing a `manifest.json` as its own and removed it. Directories are now claimed by a `producedBy` marker this script writes, so an unrelated tree is refused instead of deleted. Generated-by: Claude Code
The relay tore the subject's process group down whenever the subject exited nonzero, so a task's own background service reached the verifier only when Eval had classified the run as completed. That makes the measurement depend on the classification, and only for the subjects the classification condemns -- the verifier scores the environment the task was left in, and editing that environment for one part of the sample is not a rule, it is a bias. It was also not the rule it claimed to be. Signalling the recorded process group does not reach a service started through a PTY: `forkpty` makes the shell a session leader, and an interactive shell puts each background job in a group of its own. Measured here, both removes are real -- a PTY-hosted service survives the group teardown while an ordinary one dies -- so the "same rule for every subject" held only for the frameworks that happen not to use a PTY, which is exactly the arm this branch adds. Both go away by not tearing down an exited subject at all. Nothing is waiting on those processes: `environment.exec` has already returned, so the teardown never unblocked anything. Cancellation and framework timeout still quiesce, because there the subject has not stopped and the trial is being abandoned rather than scored. The exit-code projection stays, re-grounded: the executor prefers the result frame and falls back to the exit code only for a frame carrying no status of its own. The relay no longer reads it. Replaces the test that restated `_finalize_exited_scope` line by line with one that drives the relay's own flow over both exit codes and asserts no teardown command is issued. Generated-by: Claude Code
The checkpoint's writes fail soft -- a run that finished must not become an infrastructure failure over a file that exists only in case it does not finish -- and that left two ways for a stale file to be read as a current one. The host derived completeness from `inFlightRequests === 0`, which a checkpoint written between two requests satisfies just as well as a final one. So a wrapper whose later writes failed and was then killed recovered as `usageComplete` with a cost covering half the requests it actually made, entering the result kernel indistinguishable from a settled figure. Settlement is something only the proxy observes and no arrangement of the counts implies it, so it now crosses the boundary as its own raw fact, written by `report()` after the proxy stops. The wrapper also attached a size-and-digest artifact for that same file, read with an unguarded `statSync`. A failed write therefore threw from inside the success path and turned a completed run -- with its full usage already recovered -- into `infra_failed / setup failed`. The file is redundant wherever the frame exists, which is exactly when that artifact was built, so it is gone rather than guarded. Generated-by: Claude Code
verified-toolchain path in tests `readdir` fails the same way for a path that does not exist and for one that is a regular file, so `--out some/file.txt` took the "nothing here" branch and removed the file. The path's kind is now established before its contents, verified against a file that survives. The toolchain tests also lost their only positive case when verification started recomputing the fingerprint: no fake tree can match the pinned constant, so `prepare` -> `canReuse` -> identity-in-the-child-environment went uncovered. The test moves the pin onto its own tree for its duration, which is the one fact a real tree cannot supply, and asserts the plumbing after verification passes. Also corrects two claims and one message that had drifted: the checksum manifest pins the content of the files it names, not the closure of the tree; the DSH CLI takes more than the task text and --help; and an overlap with the unattended execution variables is not an egress policy conflict. Generated-by: Claude Code
The DeepSeek price table existed three times: once in the model config handed to the agent frameworks, and once inside each of two identical cost functions, one per arm. Both functions subtracted cache-write tokens out of the input total and then never charged them, so a cache write cost nothing -- the table beside them prices it at the input rate. The head-to-head compares the two arms' cost, so a shared formula that is wrong the same way on both sides is not a defence. The table now lives once in `provider-metering.ts`, the config spreads it, and both arms bill through one function. The test that pinned the old figure as a literal now derives it, and a new one sums a million tokens of each kind so an unpriced kind reads as the zero it is. Generated-by: Claude Code
The relay already stopped tearing down an exited subject's scope on the ordinary path: the verifier scores the environment the task was left in, and a task that has to leave a service running is scored on whether that service answers. The cancellation path still tore it down whenever the framework's terminal projection was anything but an unavailable scope -- so a subject that finished, and whose result the relay is about to report, had its background processes killed before the verifier ran. Nothing is left to settle for a subject that already exited, so the teardown is skipped there too. Only a subject still running is brought to a stop, which is the one case where the scope is not yet what the verifier will score. Generated-by: Claude Code
The metering proxy reported settlement by awaiting a snapshot of the requests in flight and then latching `settled`. A request admitted during that drain is added to the set after the snapshot was taken, so it is never waited for, and the checkpoint then claims settlement over counts that no longer describe the run. This is not a hypothetical arrival: the relay now leaves an exited subject's processes running, and those processes reach this proxy on the same bound port. Admission is closed first, so settlement is made true rather than asserted about a moment. A request arriving afterwards is refused with 503 and never forwarded, which keeps the counts honest -- the provider was never asked, so there is nothing to bill. Closing the socket instead would not do this: it leaves established connections free to send another request, and waits on idle keep-alive sockets that may never close. Generated-by: Claude Code
The Maka adapter took `costUsd` from the subject's own result when it offered one, and only priced the usage itself as a fallback. The subject computes that figure from the runtime's price table, which differs from Eval's: a reported cost could then differ between arms for a reason no agent behaviour caused. Eval prices what Eval compares, from one table, for every arm. What the subject billed itself is kept as a `subject-reported-cost` artifact rather than discarded -- the two are computed from the same usage, so a disagreement means a price table has drifted, and recording it is what makes that visible instead of silent. Generated-by: Claude Code
DeepSeek's chat-completions responses report cache reads as `prompt_cache_hit_tokens`, which the proxy's usage decoder did not look at; the runtime's own adapter reads it ahead of the OpenAI-shaped field. The tokens are not lost when it is missed -- they stay inside `prompt_tokens` and are then billed as uncached input, at fifty times the cached rate. Generated-by: Claude Code
The checkpoint gained a `settled` field, so `maka.external_provider_usage.v1` now names two incompatible shapes. The decoder requires an exact key set and would reject the older one anyway; the version is what says why. Generated-by: Claude Code
The price table's comment said the agent frameworks are handed it. Only Pi is; the others are given no cost block at all, which is why Eval bills every arm from this table rather than reading theirs. The toolchain tests mutate a process-wide environment variable, which is only safe because the runner executes a file's tests serially. That dependency is now stated where it would be broken. Generated-by: Claude Code
The table read 0.145 / 0.29 / 0.0029 per million input / output / cache-read tokens. DeepSeek publishes 0.14 / 0.28 / 0.0028 -- a uniform 3.57% over, which every reported cost carried. `packages/runtime/src/telemetry/ model-pricing.generated.ts` already held the published figures, so the two tables in this repository now agree. `cacheWrite` stays in the table at the cache-miss rate rather than being removed. DeepSeek bills input in two kinds, hit and miss, and charges nothing for writing the cache -- a written token is a miss token, so this is the rate that applies to it. For DeepSeek itself the field never comes into play: the API reports only `prompt_cache_hit_tokens` and `prompt_cache_miss_tokens`. It exists so a provider that does report cache writes separately is not billed at zero for them. The lifecycle test now pins the framework's cost block to the table itself instead of repeating its four literals, which is the transcription drift the test is there to catch. Generated-by: Claude Code
|
Requesting review. The PR body carries the full account; this is a reading order for it. @hqhq1025 — your five findings are answered under "Review response", and your two real-machine fixes are ported in What changed since the last round (
Cost accounting is more accurate than it was ( Not in scope here, but timely: DeepSeek moves this model to peak / off-peak billing at 16:00 UTC on 16 August, at 3–5× the current rates. A static table stops being able to express cost then. Two things still blocking the head-to-head cohort: #2962 and #2963. Neither is a request for changes here. Local: 35/35 Node across the three affected files, 14 Python (2 skipped), lint, format, typecheck. CI green except the two Windows jobs still running. |
EnglishLGTM — this looks ready to approve for its stated purpose. Three non-blocking follow-ups are worth tracking:
Since #2962 and #2963 still need to land first, please also verify that the final conflict resolution preserves DeepSeek Harness recovery and admission handling. 中文LGTM——按照这个 PR 的既定目的,我认为可以 approve。 有三个非阻塞 follow-up 值得继续跟进:
由于 #2962 和 #2963 仍需先合入,也请在最终解决冲突后确认 DeepSeek Harness 的 recovery 和 admission 逻辑没有丢失。 |
DeepSeek's published pricing changed since the table was written, and it is now time-of-day dependent: peak hours (01:00-04:00 and 06:00-10:00 UTC) bill double the off-peak rates. The old flat table (0.14 miss / 0.0028 hit / 0.28 output) matches neither band and understated real spend by roughly 2.4x on a mostly off-peak run. The metering checkpoint records aggregate usage without request timestamps, so a flat table remains the only representable shape. Normalize at the off-peak band (0.22 miss / 0.007 hit / 0.66 output) and say so in the comment: reported cost is a normalized API-equivalent, not an invoice, and a run that straddles peak hours bills up to 2x. Follow-up from the #2971 review. Generated-by: Claude Code
#3153) DeepSeek's published pricing changed since the table was written, and it is now time-of-day dependent: peak hours (01:00-04:00 and 06:00-10:00 UTC) bill double the off-peak rates. The old flat table (0.14 miss / 0.0028 hit / 0.28 output) matches neither band and understated real spend by roughly 2.4x on a mostly off-peak run. The metering checkpoint records aggregate usage without request timestamps, so a flat table remains the only representable shape. Normalize at the off-peak band (0.22 miss / 0.007 hit / 0.66 output) and say so in the comment: reported cost is a normalized API-equivalent, not an invoice, and a run that straddles peak hours bills up to 2x. Follow-up from the #2971 review. Generated-by: Claude Code
Summary
Adds DeepSeek Harness as an Eval subject, so its Terminal-Bench 2.1 numbers come from the same executor, metering proxy, and egress policy as every other arm.
It is carried as a complete harness profile, not a patch over a stock one:
harbor/deepseek-harness-profile/declares no bundles, so every entry the model can observe is named in one file and an upstream release adding a plugin cannot widen this arm's tool surface.One deliberate deviation: upstream configures neither
thinkingnorreasoningEffort, and the adapter default resolves toreasoning_effort=highon the wire, which would leave this arm reasoning less than every other arm.Refs #2947
Three changes that reach beyond this arm
7fa6d1fc5— the pinned toolchain fingerprint bound nothing, for every external arm. Verification compared the pinned constant against the value the tree reported for itself inmanifest.json, then checked files against that tree's ownchecksums.sha256— both pass for arbitrary content carrying an internally consistent manifest. The digest is now recomputed from the manifest on disk.055327076— the relay's process-group teardown is deleted, for every arm. What the verifier scores is the environment the task was left in, so tearing it down because Eval classified the run as failed makes the measurement depend on the classification, for the condemned half of the sample only. It also never reached a PTY-hosted service, and nothing waited on those processes.3256b97e2finishes this on the cancellation path.4852209e5— the price table read 0.145 / 0.29 / 0.0029 per million input / output / cache-read tokens. DeepSeek publishes 0.14 / 0.28 / 0.0028, whichmodel-pricing.generated.tsalready held; [codex] docs(eval): publish nine-arm Terminal-Bench 2.1 leaderboard #3004's costs are 3.45% over, ranking unchanged.97f117765also makes Eval price every arm from that one table instead of taking the Maka subject's own figure, kept as asubject-reported-costartifact so a drift stays visible.The rest of four review rounds is in the commit history with its reasoning.
Blocking the head-to-head cohort
Both arms run in one task group, one container of each per task at the same moment. #2962 and #2963 must merge first — scheduling, not a request for changes here.
#2963 overlaps in three places, better resolved deliberately than at merge time: it adds
isSuccessfulInferenceResponseto the admission branch this PR rewrote into a settledreport(); it reads the provider-usage checkpoint from the relay that this branch reads from the host side (schema is nowmaka.external_provider_usage.v2, exact-key decoder, so a v1 file is rejected rather than misread); and it fences task prompts with--, whichbfc248bcfalso does here.Real-machine findings, ported in
f7f972349@hqhq1025 found two defects on VMSS hitting this arm only: a five-minute deadline that killed
apt-getmid-dpkgwhile it waited on an interactivetzdataprompt (DEBIAN_FRONTENDandTZare now set for every arm), and the harness killing the task's own background services before the verifier ran. DSH goes 61/89 → 65/89, the row in #3004. Those four cells were recovered by the deadline fix, not the service fix — every other arm passes them under a relay that killed each process group at exit.Review response
@hqhq1025's five findings, all addressed: destructive
--out(refuses reserved paths outright, otherwise only removes a directory carrying this build's marker); identity did not pin the dependency tree (npm cifrom a lockfile shipped inside the toolchain, version cross-checked); Undici's 300s inactivity timeout (headersTimeout: 0,bodyTimeout: 0); metering read before requests settle (eight getters replaced by onereport(), plus a checkpoint the host recovers when the wrapper is killed); profile admission not exhaustive (Record<ExternalProfile, Preparer>, so a missing preparer is a compile error).Two not acted on. Fail-soft checkpoint writes do weaken "persist before forwarding the chunk", but the alternative fails every transient write error into an infrastructure failure on a request billed regardless. The three-way classification does treat the same evidence differently in two branches, because the evidence differs: in one the relay observed the subject exit, in the other the execution call threw and the subject may never have started.
Verification
Composition compared against upstream request-for-request through a recording proxy: tool names and schemas, system prompt, message sequence, model,
max_tokens, thinking,reasoning_effort,stream— all identical.Harbor trial,
sqlite-with-gcov, real container and credentials:completed, 740s, score 1, 43/43 requests admitted,usageComplete: true, real toolchain identity.modelsandtoolNamesobserved on the wire, not asserted from config.Also run: 35/35 Eval Node tests across the affected files,
test_relay_lifecycle.py(14, 2 skipped),test_relay_contract.py(18), typecheck, lint, format. Not run: the full 89-task cohort — the head-to-head has not been run by anyone yet.Operating notes
The pinned fingerprint is the digest of
checksums.sha256, recomputed from the manifest on disk at verification time. The pinned build is @hqhq1025's/mnt/deepswe/toolchains/deepseek-harness-0.1.0-rc.6-bullseye-v3,linux/amd64including the compiledpty.node— copy it to the benchmark host rather than rebuilding, since native modules need not come out byte-identical elsewhere and verification fails closed until constant and tree agree. A rebuild re-pins:Then export
MAKA_EVAL_DEEPSEEK_HARNESS_TOOLCHAIN_PATH. Task groups hold every subject for one task, so each arm adds a container; this spec raises its own concurrency limit accordingly.Deferred
checksums.sha256skips symlinks and verification is one-directional — existing design shared by all eight arms.Checklist
Does this PR entail a change in behavior?