{"_id":"@argszero/cordis-plugin-search-budget","_rev":"2-483291e0002a8d327e35659aa07f840f","name":"@argszero/cordis-plugin-search-budget","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@argszero/cordis-plugin-search-budget","version":"0.1.0","keywords":["cordis","deepseek-harness","dsh","plugin","web-search","budget","cost","quota","guard"],"license":"MIT","_id":"@argszero/cordis-plugin-search-budget@0.1.0","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"a43f6e9025b0f51a7cd6db55c32dccebdbb61d56","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-search-budget/-/cordis-plugin-search-budget-0.1.0.tgz","fileCount":6,"integrity":"sha512-Y6VdSDoPKzF965ecAqPUT9ul6BTDd7Ls2WKQUcQdGLbQjFhb27bete3LYVf6vVLk3owiAxnbu4Rsg0lhOFKrgg==","signatures":[{"sig":"MEUCIEKcDRDmzQ0HIo5bSK2m3iYSBI7iC78obbPJEkDP+/reAiEAp4oMbNiUxKequBxvD+MfOFyjY2vGlr8fX1oC1edKAH4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41086},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"scripts":{"test":"tsc && node --test \"test/*.test.js\"","build":"tsc","prepublishOnly":"tsc"},"_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"_npmVersion":"11.17.0","description":"Cost budget for dsh web_search: bounds the number of billable provider search requests a `web_search` call may initiate (per call / per turn / per session / whole subagent tree) and denies the call with an actionable error once the budget is exhausted. Ex","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.5.0","@types/node":"^26.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-agent":"0.1.5-rc.1","@deepseek-ai/dsh-tools":"0.1.5-rc.1","@deepseek-ai/dsh-session":"0.1.5-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/cordis-plugin-search-budget_0.1.0_1789021087725_0.7797146406606392","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"_id":"@argszero/cordis-plugin-search-budget@0.1.1","dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"515f07c9af833b2e7ac8c336c36fc15f18c41f1f","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-search-budget/-/cordis-plugin-search-budget-0.1.1.tgz","fileCount":6,"integrity":"sha512-zRg1+YCWqFdciHya2nNBYUNwYqD8fNFI6WC2NESHyUzW3HARLRpTVaGGLcVNMwzdJjJwtFYGqhJz+LXo/QOJag==","signatures":[{"sig":"MEUCIFrPajqf1Fu+wKCcf6/vcVpyFJfqQiu68sEYPXUS//uCAiEAl4nCpOuYH8tjdGxj05YoCeEeSDjsy+fnfGjhjslsRtI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEhW/EEhVXrItNtHbrPtAMCtW2ubSUTosqghARt4o1jgAiEA8M8IAEgL8WHLcM/JmZyjeu85K76Hsn29+TCD5TPukvY="}],"unpackedSize":42135},"main":"lib/index.js","name":"@argszero/cordis-plugin-search-budget","type":"module","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"e33193ed0e96e2cb246978a5d39bee58b333ccd5","license":"MIT","scripts":{"test":"tsc && node --test \"test/*.test.js\"","build":"tsc","prepublishOnly":"tsc"},"version":"0.1.1","_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"keywords":["cordis","deepseek-harness","dsh","plugin","web-search","budget","cost","quota","guard"],"_npmVersion":"11.17.0","description":"Cost budget for dsh web_search: bounds the number of billable provider search requests a `web_search` call may initiate (per call / per turn / per session / whole subagent tree) and denies the call with an actionable error once the budget is exhausted. Ex","directories":{},"maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"_nodeVersion":"26.5.0","dependencies":{"@deepseek-ai/schemastery":"^3.18.1"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.5.0","@types/node":"^26.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-agent":"0.1.5-rc.1","@deepseek-ai/dsh-tools":"0.1.5-rc.1","@deepseek-ai/dsh-session":"0.1.5-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cordis-plugin-search-budget_0.1.1_1789693813079_0.753150295001201"}}},"time":{"created":"2026-09-10T06:18:07.590Z","modified":"2026-09-18T01:10:13.351Z","0.1.0":"2026-09-10T06:18:07.849Z","0.1.1":"2026-09-18T01:10:13.170Z"},"license":"MIT","keywords":["cordis","deepseek-harness","dsh","plugin","web-search","budget","cost","quota","guard"],"description":"Cost budget for dsh web_search: bounds the number of billable provider search requests a `web_search` call may initiate (per call / per turn / per session / whole subagent tree) and denies the call with an actionable error once the budget is exhausted. Ex","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"readme":"# @argszero/cordis-plugin-search-budget\n\nA **cost budget for `web_search`** in the [dsh harness](https://github.com/deepseek-ai/deepseek-harness).\nIt caps the number of **billable provider search requests** a `web_search` call may\ninitiate — per call, per turn, per session, and per whole subagent tree — and\n**denies** the call with an actionable message once a budget is exhausted.\n\n## Why\n\n`web-search-deepseek` turns **every query string** into a full, separately billable\nMessages request. `packages/web/web-search-deepseek/src/provider.ts` builds one\nrequest body per search (`Perform a web search for the query: <query>`) and reports\nit through `recordRequest`, which appends one `web/deepseek-search-llm-request`\nsession event per query. That same request then carries a *server-side* search tool\n(`web_search_20250305`, `max_uses: <maxUses>`), so one query can itself fan out into\nup to `maxUses` native searches.\n\nTwo different consumers each hold one factor of the resulting product:\n\n| Knob | Default | What it actually bounds |\n|---|---|---|\n| `tool-web.searchMaxQueries` | `4` | the accepted `queries` **array of one call** — the model may call the tool any number of times |\n| `web-search-deepseek.maxUses` | `5` | server-side searches **inside one request** |\n\nNeither is a total. `tool-web`'s own README states the hole out loud:\n\n> **No native search counter covers the whole batch** — `searchMaxQueries` limits\n> `ctx.web.search` calls, but a provider can perform several native searches inside\n> each call.\n\nSo a subagent whose prompt says *\"use the `web_search` tool repeatedly with many\ndifferent targeted queries\"* walks past both bounds, and with the subagent default\n`maxDepth: 3` a three-level delegation tree issues requests at a rate no single knob\ngoverns. Discussion **#6106** records the outcome on a **three-turn** session:\n**198** `web_search` calls → **854** billable requests inside ~5 minutes (~246 in any\n60-second window), ending in `402 Insufficient Balance`.\n\nThe same report notes a second gap this plugin does not close but leaves visible:\n`dsh-token-meter` subscribes to `session/event` and accounts only `assistant/message`\nusage, so those 668 search requests never appear in local cost reporting.\n\n## What it does\n\nIt registers a [`tools/pre-execute`](https://deepseek-ai.github.io/deepseek-harness/)\nlistener — the same pre-dispatch waterfall the in-tree `guard/timeout-policy` uses.\nFor each `web_search` call it estimates the billable requests the call would\ninitiate:\n\n```\ncost = distinct(queries) × queriesPerRequest     # queriesPerRequest defaults to the provider's maxUses (5)\n```\n\nand charges that estimate to every enabled scope. The first scope that would be\nexceeded **denies** the call (`{ kind: 'deny', reason }`) — the provider is never\nreached, so no request is issued and no charge is incurred.\n\nBecause the gate only ever *rejects*, it can never cause a request that would not\notherwise have happened, and because it decides **before** dispatch it needs no\npost-hoc accounting. The estimate is the whole mechanism.\n\n### Scopes (all optional; `0` disables a scope)\n\n| Config | Default | Meaning |\n|---|---|---|\n| `perCall` | `10` | requests a single `web_search` call may initiate |\n| `perTurn` | `0` (off) | requests one turn may initiate, across every agent |\n| `perSession` | `0` (off) | requests one session may initiate |\n| `perTree` | `60` | requests one session **and all of its subagent descendants** may initiate together |\n\n`perTree` is the one no in-tree knob provides. Lineage is read from the durable\n`SessionHeader.parentSession` chain and resolved through `ctx.agents.get()`, so a\nchild's spend is charged to the top-level ancestor that spawned it. The walk stops\nat the first ancestor that is no longer live; a dead ancestor's budget can no longer\ngrow, but its id still names the tree.\n\n## Install / mount\n\n```sh\nnpm install @argszero/cordis-plugin-search-budget\n```\n\nThe bundle patch mounts the plugin with its defaults:\n\n```yaml\n# inside the package's cordis.patch.yml — already applied by `dsh` bundle install\n- insert:\n    - id: search-budget\n      name: '@argszero/cordis-plugin-search-budget'\n```\n\n### Tuning\n\n```yaml\n# a profile layer / overlay\n- set:\n    - id: search-budget\n      config:\n        queriesPerRequest: 5   # provider maxUses: native searches per query\n        perCall: 10            # requests one call may initiate\n        perTurn: 0             # 0 = off\n        perSession: 0          # 0 = off\n        perTree: 60            # a session + all its subagents together\n        enforce: true          # false = observe-only (logs the would-be denial, never denies)\n```\n\n* Set `queriesPerRequest: 1` for a provider that issues exactly one request per query\n  (e.g. a plain search API, or `web-search-exa`/`web-search-perplexity` semantics).\n* `enforce: false` is the recommended first step on an existing deployment: run it for\n  a day to see what your real workload costs, then set budgets from the log.\n* `toolName` (default `web_search`) retargets the gate if a deployment registers the\n  tool under another name.\n\n## What it deliberately does not do\n\n* **It does not account for actual provider usage.** The estimate bounds admission;\n  an exact total would need per-request usage that the search-LLM-request path does\n  not currently record. Do not treat the estimate as a billing figure.\n* **It does not touch `searchMaxQueries` or `maxUses`.** Those still apply, and they\n  still bound one call and one request respectively. This plugin adds the total that\n  was missing; it does not replace the per-call knobs.\n* **It does not fix the local usage meter.** Making `web/deepseek-search-llm-request`\n  visible to cost reporting is an in-tree change to the token meter.\n* **It does not persist state.** Budgets are a running-deployment rate limit, not a\n  durable quota across restarts. On a restart the counter is fresh.\n\n## Compatibility\n\nBuilt and typechecked against dsh `0.1.5-rc.1` / `@deepseek-ai/cordis` `4.0.2`. The\nharness packages are **type-only** imports, so the plugin has no runtime dependency\non them — it declares only `cordis` as a peer, and no dsh version range at all, so\nthere is nothing for npm to refuse on a different line.\n\n**0.1.1 fixed a packaging defect that made the artifact unloadable outside the\nauthor's tree.** `src/index.ts` imports `@deepseek-ai/schemastery` for value, but\n0.1.0 declared no `dependencies` block: the library resolved only because this\nrepo's own `node_modules` had it hoisted off a sibling devDependency. A consumer\ninstalling into a tree that does not happen to provide it got, at mount time:\n\n```\nError [ERR_MODULE_NOT_FOUND]: Cannot find package '@deepseek-ai/schemastery'\nimported from .../node_modules/@argszero/cordis-plugin-search-budget/lib/index.js\n```\n\n`schemastery` is now a declared dependency, and `test/packaging.test.js` fails if\nany value import in the source is missing from `dependencies`/`peerDependencies` —\na dependency that only resolves in the author's tree is not a declared one.\n\n## Tests\n\n```sh\nnpm test     # tsc + node --test test/*.test.js   (35 tests)\n```\n\nThe suite covers query estimation (including the exact-duplicate collapsing and the\nblank/non-string handling that `tool-web` itself performs), lineage resolution\n(including a cyclic-lineage guard and a dead-ancestor stop), turn detection, the\ncost model, admission and charging per scope, release-on-dispose, the denial text,\na replay of the #6106 fan-out shape against the shipped defaults, and the\npackaging contract (every runtime import is declared in the manifest).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}