Skip to content

self-hosted one container your machine

UsageFoundry

A self-hosted dashboard and headless run orchestrator for Claude Code.

See where your Pro or Max allowance is actually going — read out of Claude Code's own transcripts at ~/.claude/projects/**/*.jsonl — then spend what is left on agents that run to a budget and stop.

Everything runs on your machine, in one Docker container. No account, no telemetry back to us, no third-party service. The port is published on 127.0.0.1.

what it does

Six things, precisely

  • Track the allowance

    Parses Claude Code's own transcripts into exact token volumes and costs, and reads the real utilisation percentage from the same first-party endpoint claude /usage calls.

    5-hour window, weekly quota, burn rate, projected exhaustion. Breakdowns by model, project, agent, skill and reasoning effort — plus which tools filled those contexts, measured in characters.

  • Spend it deliberately

    Point a run at a folder, give it a task and a budget; it drives claude -p headlessly in a loop.

    The guard is re-checked before every work cycle, and the run stops when the money, the cycles, the clock or the window runs out.

  • Keep runs out of each other's way

    Each run gets its own git worktree and branch, so several agents work one repository at once.

    Review the diff, resolve conflicts and merge from the UI.

  • Chain the work

    Saved workflows are graphs of run blocks — including blocks that decide what to run next, blocks that repeat a task until the agent reports done, and blocks that land what the earlier ones built.

    Schedulable, under a budget covering the whole graph.

  • Bring your own tooling

    Claude Code plugins in mounted folders are found and listed.

    Switching one on puts it on every work cycle from the next one onward.

  • Drop spent tool output

    Winnow is wired in at the two places it can act — an intake filter in front of the API, and a pruner at the work-cycle boundary.

    Both ship off. The filter sends a spent tool result in full once, past the last cache_control breakpoint so it is never cached, then a pointer; the pruner edits the transcript at the one moment the edit is free.

01 · usage

Where the allowance went

Claude Code already writes down everything it does. UsageFoundry reads those transcripts where they sit — ~/.claude/projects/**/*.jsonl, from the last byte offset it saw — and turns them into token volumes and costs that are exact, because they are the numbers in the file.

The percentages are a different kind of number. They come from GET /api/oauth/usage, the first-party endpoint claude /usage reads, so they cover the whole account and not just this machine. Three windows: the rolling five-hour session, the week, and the weekly wall scoped to one model family.

Breakdowns by model, project, agent, skill and reasoning effort. Tool composition is measured in characters rather than tokens, because a tool_result carries no usage block — it is a shape, never a cost.

Illustration of the usage view. A 5-hour window meter fills from 18 to 96 per cent, a weekly meter from 21 to 33 per cent, and a 24-hour install limit with no ceiling set is drawn hatched rather than empty. Below them, projected exhaustion moves earlier until it falls before the window reset at 16:45. Illustration, on invented figures. In the app the volumes and costs are exact and the percentages are the provider’s own, read from the endpoint claude /usage reads.

Cowork, the desktop app and the web app spend the same limits and write no local transcripts. Work done there is invisible here, so every locally-derived figure is a floor on real consumption. It fails unsafe, and the docs say so rather than smoothing it over.

A window with no ceiling configured renders hatched, not at 0%, and a budget guard written as a fraction of a limit is refused outright rather than assumed.

02 · runs

Spend it against a budget

A run is a folder, a task and a budget. Each work cycle is one claude -p … --output-format stream-json invocation; the loop keeps going until the agent reports done or a guard refuses the next one.

Five of the policy's fields are ceilings: maxIterations, maxRunCostUSD, maxDurationMinutes, maxWeeklyFraction and maxSessionFraction. At least one of the first and third is required: without a monotone terminus the loop has no reason to end.

Illustration of a budgeted run with a $2.00 ceiling and a cap of 6 cycles. The guard passes before each of five work cycles, then refuses the sixth at $2.04 — $0.04 above the ceiling, because the check happens between cycles rather than inside one. Illustration, on invented costs, in the between-cycles mode.

None of the three enforcement modes is a hard cap. Between cycles is the cheapest and overshoots by up to one work cycle; live interrupts the process mid-cycle and throws that cycle’s work away; live-resume interrupts and picks the session back up.

03 · isolation

Several agents, one repository

Every run gets a real git worktree and a branch of its own, cut from your last commit and parked under .uf-worktrees/ inside the mount. Your checkout is not touched, and neither is anybody else's.

Landing is a review step, not a button. The diff is yours to read, the merge is previewed in memory before anything is written, and it refuses to run unless your checkout is clean and still on the branch the run was cut from. Conflicts get resolved on the run’s branch — never in your working copy.

Diagram. A main branch with three commits; three run branches — uf/retry-1, uf/docs-2 and uf/flaky-3 — each branch off it into its own worktree directory, and each rejoins at a single landing point. Illustration. Sixty-four worktree slots per repository, one merge at a time per repository, up to four repositories at once.

Worktrees keep concurrent runs off each other’s commits, not out of each other’s files. Every run’s checkout is writable by every other run in the same container. The product documents this as a gap rather than as a guarantee.

04 · workflows

Chain the work

A saved workflow is a graph of blocks. A run block is one task in one folder. An orchestrator block takes a brief instead, spends up to ten minutes reading the repository, and starts the runs it decides on — under a fan-out cap you have to set. A repeating block re-runs a task until the agent reports done or the pass cap is reached. A merge block lands what the others built.

One spending limit covers the whole graph, and a workflow can be scheduled. A merge block is never told which branch to land onto — it uses what each run recorded when it started.

Diagram of a workflow graph. A run block feeds an orchestrator block, which fans out to three runs under a required cap, which all feed a merge block that lands each branch onto the base its own run was cut from. Illustration. The graph picks what work to do. Something a person wrote — settings, hooks, permissions — picks what an agent may do.

05 · plugins

Bring your own tooling

Claude Code plugins already sitting in your mounted folders are found and listed, with what each one actually carries — hooks, agents, skills, commands, an MCP server. Switching one on applies from the next work cycle onward, including runs already in flight. Nothing is installed into ~/.claude.

An agent is a saved role — a name, a description, a prompt and optionally a model — that reaches the CLI as --agents and --agent. Start a run as one and the run answers to a different brief.

Mock-up of the Plugins settings tab, listing three plugins found in mounted folders with what each carries: a skill, two hooks and a command, and an agent with an MCP server. Two are switched on. Illustration of the Plugins tab.
Mock-up of the Agents settings tab, showing one saved agent named reviewer with a model and a two-line description. Starting a run as an agent changes who the run is. It never changes what the run may do.

06 · context

Stop re-sending spent tool output

Most of a long session is tool output. UsageFoundry wires winnow in at the two places it can act: an intake filter in front of the API, and a pruner — run from a ref pinned into the image — at the work-cycle boundary.

The filter is a loopback proxy the entrypoint starts and points every agent's ANTHROPIC_BASE_URL at. Position is the whole mechanism: a tool result a rule marks as spent goes out in full on the one request the model acts on it, placed past the last cache_control breakpoint so the API never writes it to the cache, and is a pointer on every request after that. It is the full send that sits past the breakpoint — never cached, so never re-read.

The pruner acts between cycles, on a transcript nobody is holding. It removes tool output and keeps the conversation, rather than replacing the conversation with a summary.

Measured in tokens, never bytes: winnow frees more file than it removes from what is actually sent, and its own token readout can report zero for a prune that took out a large fraction of the context.

Illustration of context control. Five requests go out: two carry a tool result in full, and three carry a pointer instead, each marked with the winnow rule that fired — C1, C3 and B2. A context meter fills from 145 to 201 thousand tokens, passes the 200-thousand ceiling, and the cycle ends there; a prune then leaves 129 thousand for the next cycle to resume. Illustration, on invented sizes. The ceiling is the product's own 200,000 tokens. Both halves are off in a shipped install.

Cutting context does not automatically save money

The conversation sits in the API's cached prefix, where a read bills at a tenth of the input rate on most models — Claude Fable 5.1 and Claude Mythos 5.1 read at 0.025×, because the multiplier belongs to the model — and editing it invalidates everything after the cut. At a tenth a prune pays 1.9·S − 2·D once and earns 0.1·D a turn back, so it breaks even after 19·(S/D) − 20 further turns. What decides is the ratio, not the size of the session.

Both prunes are gated on that arithmetic, and the boundary is the permissive one: the invalidation is refunded there — the resume was going to rewrite that suffix anyway — so it cuts whenever it cannot predict otherwise. A cycle ended early to make a boundary pays the invalidation in full and is held to a longer horizon. The filter never pays one at all: nothing is edited.

Illustration of when a context cut pays for itself. Bars measure the further turns a cut must survive, against a horizon of 18. Removing a tenth of the suffix needs 170 turns and removing a quarter needs 56, both past the horizon; removing a half needs exactly 18 and removing two thirds needs 9. The same half cut taken at a work-cycle boundary needs none, because the resume was going to rewrite that suffix anyway. The intake filter has no break-even at all. Illustration, on an invented 120,000-token suffix. The arithmetic is the product's own: a cache read bills at 0.1× on every model but Claude Fable 5.1 and Claude Mythos 5.1, the one-hour write this install measured bills at 2.0×, so an edit pays 1.9·S − 2·D once and earns 0.1·D a turn back. S/D decides — the same four bars are drawn for a session ten times the size.

Both halves ship off. contextPruning is false in the defaults and a blank WINNOW_FILTER is the shipped state, so an install that sets neither behaves exactly as it did before either existed.

Switching pruning on also changes what stops a runaway cycle. It replaced --autocompact 200000, which is no longer on any cycle's argv — so with pruning off, a long cycle runs to the model's whole window, and with it on, the cycle is ended at 200,000 tokens and pruned instead of summarised.

Neither half is spend. The filter's figure reaches no meter, no guard and no window: every meter here is priced from usage frames, and a usage frame reports the request the filter had already rewritten — so the money is already absent from the figures next to it rather than arriving as a credit.

The two also overlap. The product's own card adds them rather than implying they are disjoint, and its own docs/verification.md records what that overstates by: 4.06% of pruned tokens, itself an upper bound.

trust boundary

Self-hosted, and what that costs

There is no account and no service. One container, on your machine, reading files you already have. Nothing leaves it except the calls Claude Code was going to make anyway.

That is also the whole of the isolation. The server starts as root so it can drop each child to a lower uid; a work cycle runs as whoever owns the mounted credentials, because it has to read them. Every folder you mount is inside one blast radius, and a token you put in the environment reaches the agents.

There is a UF_SANDBOX=1 path meant to confine a work cycle further. It is off by default, and the docs are blunt that no work cycle has yet run inside a sandbox that started — one install spent fifteen hours with it set and the sandbox silently not starting. Do not treat it as a security boundary yet.

The server refuses to start without UF_AUTH_TOKEN unless you set UF_ALLOW_NO_AUTH=1, which logs a block at boot and banners every page until you undo it.

quick start

Clone it, fill in two variables, bring it up

git clone https://github.com/Xapicc/UsageFoundry.gitcd UsageFoundrycp .env.example .env# edit .env:  UF_WORKSPACE (required — the code you want agents to work on)#             UF_AUTH_TOKEN (required: openssl rand -hex 32) docker compose up --buildopen http://localhost:3000

UF_WORKSPACE is the code you want agents to work on. UF_AUTH_TOKEN is a secret you generate — openssl rand -hex 32 is fine. Both are required.

The dashboard has your usage as soon as it can see ~/.claude. Runs need one more step: Settings → Claude account → Sign in, inside the container.

The utilisation percentage is read from Anthropic’s own endpoint, so it covers the whole account. The docs say where each number is exact, where it is estimated, and where it is a guess.

DATA_DIR holds the only copy of your runs, settings, workflows and schedules, and is checked before the server serves anything. docker-compose.yml sets it; if it is not writable the container exits naming the path and the uid.