Bundle
dsh-plugin-agent-budget
Shared token budgets for DeepSeek Harness agent trees
- Source
- winter-street
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-plugin-agent-budget
Shared token budgets for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
agent trees. A root agent, its one-shot and continuable subagents, and workflow
descendants can spend from one durable budget.
> **Status**: experimental, verified against DSH `0.1.0-rc.5`. The package is
> not published to npm yet; it is developed as an open-source contribution to
> the DSH plugin ecosystem.
## Highlights
- treats a whole **agent tree** as one budget account (`scope: tree`), or each
session independently (`scope: session`);
- stores the ledger in a **plugin-owned sidecar** under `~/.dsh/agent-budget/`,
never in session logs, so uninstalling the plugin cannot break sessions;
- keeps the ledger append-only, replayable, and recoverable;
- gives the model itself a read-only `budget_status` tool, not just a human
command;
- ships a **settings page panel** to list scopes, adjust limits, and reset
usage without touching ledger files by hand;
- has **no external service** dependency and can be installed as a plain
bundle;
- keeps a narrow scope: durable, replayable, fail-closed token accounting for
agent trees.
## Install
> The package is not published to npm yet. The commands below assume the
> package is available in your profile workspace (for example via a local
> checkout with `dsh plugin --profile <name> add -w .`, or from a GitHub
> bundle once this repository is public).
Install as a bundle into a profile (recommended after publishing):
```bash
dsh plugin --profile <name> add dsh-plugin-agent-budget
```
Or install directly with npm/pnpm:
```bash
pnpm add dsh-plugin-agent-budget
```
For git installs, the package builds during install via its `prepare` script.
pnpm ≥10 blocks build scripts for git dependencies by default, so the first
`add` fails; add the exact package key pnpm printed to the profile's
`pnpm-workspace.yaml` and re-run:
```yaml
allowBuilds:
dsh-plugin-agent-budget: true
```
The package declares `dsh.bundle`, so `dsh plugin` adds it to the profile's
`dsh.profile.bundles` automatically and `--dump-config` shows the
`# == dsh-plugin-agent-budget` layer. The layer is provided by the root
`cordis.patch.yml`:
```yaml
- insert:
- id: agent-budget
name: dsh-plugin-agent-budget
config:
maxTokens: 200000
missingUsage: exhaust
scope: tree
```
`maxTokens` is required and must be a positive safe integer. `missingUsage`
defaults to `exhaust`; set it to `ignore` only when a provider intentionally
omits usage and you accept incomplete enforcement. `scope` defaults to `tree`;
set it to `session` for one independent budget per session. `storageDir` is
optional and defaults to `~/.dsh/agent-budget/`.
## Export shape
The plugin exports four named members and **no default export**:
- `name: 'agent-budget'`
- `inject: ['llm', 'sessions', 'tools']`
- `apply(ctx, config)` — the function-plugin entry point
- `Config` — the loader-facing config schema
## Model Experience
The model can call the read-only `budget_status` tool. It returns the limit,
used and remaining tokens, exhaustion state, all four usage buckets,
`meteringComplete`, and `unmeteredCalls`. It cannot change the limit.
## Settings panel and HTTP API
The web client registers a `Token 预算` section in Settings. It lists every
open budget scope, shows limit/used/remaining/exhausted state with a progress
bar, and polls every 30 seconds.
Host HTTP API under `/agent-budget/api`:
- `GET /scopes` → `{ ok, scopes: [{ scopeKey, limitTokens, usedTokens, ... }] }`
- `POST /adjust-limit` with `{ scopeKey, limitTokens }` → overwrites the scope's
limit
- `POST /reset` with `{ scopeKey }` → clears usage but keeps the current limit
`adjust` and `reset` are appended to the sidecar ledger as new event lines.
Old `open`/`sample` lines are never modified, so replaying the ledger after a
reload produces the same result as incremental updates.
## Semantics
- With `scope: tree` (default), the plugin resolves the tree root using DSH's
**runtime agent ownership** first, falls back to the durable `parentSession`
chain, and finally falls back to an independent budget with a warning when
neither is available. It never merges unrelated sessions into one account.
- With `scope: session`, every session has an independent budget, including
subagents.
- Every `llm/stream` call with a `sessionId` is included: conversation,
subagent, workflow, compaction, and title-generation calls.
- Uncached input, cache reads, cache writes, and output are disjoint buckets.
Reasoning tokens are already part of output and are not added again.
- The limit is captured by a scope's first open ledger record. A later plugin
reload does not mutate an existing budget.
- Calls admitted concurrently may finish above the limit. Once settled usage
reaches the limit, later calls fail before provider dispatch with
`TOKEN_BUDGET_EXHAUSTED`.
- Calls without a `sessionId` are outside any agent tree and are not metered.
## Storage and uninstall
The plugin stores its ledger in:
```text
~/.dsh/agent-budget/
ledger.jsonl append-only ledger (open/sample/unmetered)
scope-index.json sessionId -> scopeKey index
```
- New versions **never write `budget/*` events into session logs**.
- After uninstalling the plugin, DSH can open every session normally.
- To fully remove budget data, delete `~/.dsh/agent-budget/`.
- One `storageDir` is intended for one DSH process at a time. If `headless`
and `web` run concurrently, give each profile a distinct `storageDir` or do
not run them against the same ledger simultaneously.
## Migrating legacy session logs
Versions before this sidecar design wrote `budget/*` events into session logs.
Run the migration once before upgrading a profile that used those versions.
Migrate them with:
```bash
node scripts/migrate-session-log.mjs
```
The migration tool:
1. scans `~/.dsh/sessions/**/session.jsonl(.zstd)`;
2. converts `budget/*` events into `ledger.jsonl` and `scope-index.json`;
3. removes those events from each session log;
4. creates a `.bak` backup before writing.
Stop DSH processes that use the affected profile before running it.
## Known Limitations
- The `agent/request-error` handler matches `failure.code === 'TOKEN_BUDGET_EXHAUSTED'`
globally and swallows any error carrying that code. Today only this plugin
produces it; if another plugin ever reuses the code, this boundary needs to
be tightened.
- `scope: tree` may fall back to an independent budget in extreme cold-start
cases when no parent can be resolved. This prefers under-sharing over
incorrectly locking unrelated chats together.
- Verified against DSH `0.1.0-rc.5`. When upgrading DSH, re-check: the
`llm/stream` hook signature, the `agent/request-error` payload shape, and the
`ctx.agents` runtime ownership API.
- Concurrent admission can overshoot the limit by design (see Semantics).
- Metering is fail-closed by default: providers that intentionally omit usage
need `missingUsage: 'ignore'`.
## Repository layout
```text
src/index.ts plugin implementation
tests/ unit + integration tests (deterministic mock streams)
cordis.patch.yml default bundle layer for dsh plugin profiles
scripts/ lint, test, pack verification, legacy migration
docs/design.md design decisions and compatibility notes
.github/workflows/ci.yml CI on Node.js 22 and 24
```
See [CHANGELOG.md](CHANGELOG.md) for release history.
## Development
```bash
pnpm install
pnpm check
```
The test suite uses a deterministic mock stream. `pnpm test:smoke` additionally
runs a small real DeepSeek request when `DEEPSEEK_API_KEY` is present and skips
otherwise. CI covers Node.js 22 and 24.
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
## License
MIT
Install
dsh plugin --profile web add github:winter-street/dsh-plugin-agent-budget
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-plugin-agent-budget from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.