@webhound/contractor
v0.1.1
Published
Budget-to-effort runner for coding and CLI agents
Readme
Contractor
Contractor is a budget-to-effort runner for coding and CLI agents.
It exposes one primitive:
agent + task + budget -> repeated work until the budget is spentYou give Contractor an agent, a task, and an effort budget. Contractor launches that agent in cycles, accounts for each cycle based on the model and telemetry available, and keeps sending the agent back to work until the target budget has been used.
The budget is the effort knob, not necessarily your literal invoice. A $1 run is a quick pass. A $50 run should keep improving implementation, tests, docs, cleanup, and verification until about $50 of accounted agent effort has happened. A $500 run is the same primitive with more runtime. There is no special upper bound in Contractor itself; the practical limits are provider limits, wall-clock time, machine resources, and the isolation level you choose.
Contractor is not another coding agent, model gateway, or prompt format. It is the supervisor around the agents this package supports today: Codex CLI, Claude Code, Cursor Agent, Pi/OpenRouter, local scripts, or any command you can run from a terminal.
It does four things:
- Runs agents in budgeted cycles until the target effort is reached.
- Accounts for effort from exact CLI cost, token usage, model prices, normalized token pricing, or an explicit fallback.
- Keeps changes reversible by default through isolated workspaces and patch artifacts.
- Writes a durable report that shows what ran, what changed, where the changes live, and how another agent can inspect or apply the result.
Why This Exists
Coding agents already know how to edit, test, and explain code. What they usually lack is a simple external control for effort.
Turn counts are a poor proxy. Prompts like "try harder" or "do a thorough pass" are vague. Contractor makes effort budget the control surface:
contractor run "clean up this repo until the budget is used" --budget 50 --agent codex --writeThat run should not stop because the first pass looked complete. If the initial fix is done, the next cycles should look for useful follow-up work: tests, edge cases, documentation, simplification, cleanup, and verification. The job is finished when the budget is used, an overage hard limit is reached, the run is stopped, or a safety cap such as --max-iterations is hit.
Contractor is also designed for agents to call. A parent agent can spawn a Contractor run, leave it working in an isolated workspace, and later read report.md, ledger.jsonl, transcript.md, and artifacts/changes.patch to decide what to apply.
Quick Start
cd /Users/mfkhalil/CustomSearch/contractor
npm test
node bin/contractor.js agents
contractor run "clean up this repo and keep improving tests until the budget is used" \
--budget 5 \
--agent auto \
--cwd /path/to/repo \
--writeBy default --agent auto is subscription-first:
- Try signed-in
codex. - Then signed-in
claude. - Then signed-in
cursor. - If all subscription routes are missing, blocked, rate-limited, or reject the requested model, fall back to paid
pithrough OpenRouter.
Explicit subscription agents participate in the same route. For example, this tries Cursor first, then the rest of the subscription route, and only then paid Pi:
contractor run "fix the failing tests" \
--budget 10 \
--agent cursor \
--model qwen/qwen3.7-max \
--cwd /path/to/repo \
--writeThe requested --model is passed through unchanged to subscription fallbacks. If a subscription route rejects the model or hits auth/rate-limit/server errors, Contractor records that route attempt and continues.
For Codex with ChatGPT sign-in, omit --model unless you know the model is supported by that account. Contractor will let the local Codex config choose the model. Use --auth api --model <model> when you intentionally want API-key billing.
Subscription-backed Codex runs may report token usage without a billable dollar cost. In that case Contractor converts tokens into normalized effort using --effort-model instead of pretending the run was free. If there is no usable token telemetry, Contractor falls back to --fallback-cost per cycle.
For a generic agent command:
contractor run "research the docs and write notes" \
--budget 1 \
--agent generic \
--model qwen/qwen3-coder-plus \
--command "my-agent --prompt {{prompt}}"Use --no-paid-fallback when you want only subscription-backed routes. Use --no-fallback when you want the selected adapter to run directly with no route behavior.
Artifacts are written to:
<target repo>/.contractor/runs/<run-id>/Each run contains state.json, control.json, events.jsonl, ledger.jsonl, stdout.log, stderr.log, per-cycle logs, transcript.md, watch.md, activity.md, report.md, and an artifacts/ directory with the generated patch and changed-file manifest.
Effort Budget Semantics
Contractor can meter effort in either dollars or tokens:
contractor run "clean up this repo" --budget 10 --agent codex --write
contractor run "clean up this repo" --token-budget 500000 --agent codex --writeThe primitive is the same in both cases: keep running until the selected effort target is reached. Dollar budgets are easiest when the provider exposes cost. Token budgets are useful for subscription-backed tools where the user wants to control effort even when there is no literal per-run invoice.
Contractor uses the strongest effort signal available:
- Exact CLI cost, such as Claude Code
total_cost_usdor OpenRouterusage.cost. - Parsed token usage multiplied by the model price.
- Parsed token usage multiplied by
--effort-modelwhen the actual model is unpriced or hidden by subscription auth. - Text-size estimate using the model or effort-model price.
--fallback-costor--fallback-tokenswhen no telemetry or model price exists.
Contractor starts new cycles while accounted effort is below the target budget. If a cycle crosses the budget, Contractor lets that cycle finish and then stops before planning more work.
Close-enough stopping is opt-in. By default, Contractor treats the budget as the effort target and keeps launching cycles until the target is reached or a hard blocker occurs. If you want “good enough” stopping at cycle boundaries, set a threshold explicitly:
target = $30.00
close enough = $3.00 remaining
$27.92 spent -> stop before launching another cycleEnable that with:
contractor run "..." --budget 30 --completion-threshold-percent 0.05
contractor run "..." --budget 30 --completion-threshold-usd 0.25
contractor run "..." --token-budget 500000 --completion-threshold-tokens 25000By default, the hard limit is 10% above the target budget:
target = $10.00
hard limit = $11.00Change that with:
contractor run "..." --budget 10 --overage-percent 0.25
contractor run "..." --budget 10 --overage-usd 5
contractor run "..." --token-budget 500000 --overage-tokens 50000Change the normalized effort conversion with:
contractor run "..." --budget 10 --agent codex --effort-model gpt-5.4 --writeThe hard limit is a supervisor stop rule. If an individual agent call reports its cost or tokens only after it finishes, Contractor cannot interrupt that already-running call at the exact cent or token; it stops before launching another cycle.
Contractor is not a payment processor. It meters agent effort locally using the best signal it can observe. With subscription-backed tools such as Codex authenticated through ChatGPT, Contractor can supervise the local CLI and burn down the effort budget from token usage even when no literal dollar charge is exposed.
During every cycle Contractor writes a live budget file into the execution workspace:
<execution cwd>/.contractor/live-budget.jsonThe child agent also receives CONTRACTOR_RUN_DIR, CONTRACTOR_LIVE_BUDGET_FILE, and CONTRACTOR_ITERATION_BUDGET_HINT. Agents that can read files while they work can inspect that file to see budget unit, total budget, spent, remaining, hard limit, close-enough threshold, suggested slice for the current iteration, and wrap-up mode.
For adapters that stream JSONL usage or CONTRACTOR_USAGE markers, Contractor updates the live budget file while the child is still running. If live telemetry crosses the hard limit, Contractor requests a graceful stop and then terminates the process group if needed. For black-box CLIs without live telemetry, Contractor relies on post-cycle accounting and stops before launching the next cycle when the budget is spent.
Contractor does not use per-iteration wall-clock timeouts as a budget primitive. Auth errors, unsupported models, explicit stops, hard-limit crossings, and max-iteration safety caps still stop the run.
Reversible By Default
Contractor avoids asking the agent to undo itself. It controls where the agent works.
Default --isolation auto behavior:
- Clean git repo: create a disposable git worktree under
~/.contractor/workspaces. - Dirty git repo or non-git folder: copy the folder into a snapshot workspace and commit a synthetic baseline there.
- Explicit escape hatch:
--isolation noneruns directly in the target directory.
At the end of the run, Contractor writes:
<run-dir>/report.md
<run-dir>/artifacts/changes.patch
<run-dir>/artifacts/changed-files.txt
<run-dir>/artifacts/workspace-manifest.jsonInspect the result:
contractor report <run-id>Apply changes to the original folder:
contractor apply <run-id>Reverse an applied patch:
contractor apply <run-id> --reverseFile changes inside the workspace are reversible. External effects are not magically reversible: network calls, hosted databases, global package caches, credentials, daemons, temp files outside the workspace, and other services need stronger isolation such as containers or VMs.
Stop And Status
The control plane is file based, so it does not need the agent to cooperate.
contractor status
contractor watch
contractor watch <run-id> --follow
contractor list
contractor report <run-id>
contractor stop <run-id>contractor watch is the live view. It renders current spend, workspace paths, and recent child-agent activity such as planning, shell commands, file writes, search steps, tool results, and iteration boundaries. Contractor also keeps a markdown copy of that view inside the run directory:
<run-dir>/watch.md
<run-dir>/activity.md
<run-dir>/activity.jsonlFor adapters with verbose JSONL streams, Contractor summarizes tool calls instead of dumping large tool payloads. For example, a huge CSV write appears as Writing bay_area_concert_venues.csv with a byte count rather than the full CSV body.
On macOS/Linux, Contractor starts child agents in their own process group and sends signals to the group. On Windows it falls back to taskkill /T.
Subscription-First Routing
The v1 route is intentionally small:
| Adapter | Billing posture | Status | Invocation shape |
| --- | --- | --- | --- |
| codex | subscription preferred | verified | codex exec --json ... |
| claude | subscription preferred | verified | claude -p ... --output-format json |
| cursor | subscription preferred | installed CLI verified; auth may be local | agent -p ... --output-format stream-json --trust |
| pi | paid fallback | verified with OpenRouter | pi --mode json -p --session-dir ... --session-id ... --approve ... |
| generic | caller-defined | verified | any shell template or env-driven command |
The built-in product route is only codex,claude,cursor, then paid pi. Other coding agents can still be wrapped with generic or a config-defined command.
Change the subscription order:
contractor run "..." \
--budget 10 \
--agent auto \
--subscription-agents cursor,codex,claudeDisable paid fallback:
contractor run "..." --budget 10 --agent auto --no-paid-fallbackDisable all routing:
contractor run "..." --budget 10 --agent codex --no-fallbackPass extra adapter args after --:
contractor run "fix lint" --budget 2 --agent codex -- --sandbox workspace-writePi is the paid fallback. It supports provider/model passthrough. For OpenRouter Qwen Max:
npm install -g @earendil-works/pi-coding-agent
pi --version
export OPENROUTER_API_KEY="..."OPENROUTER_API_KEY=... contractor run "write hello.txt" \
--budget 1 \
--agent pi \
--provider openrouter \
--model qwen/qwen3.7-max \
--writeIf the key already lives in another local app's .env.local, export or source it into the shell before running Contractor. Contractor only needs OPENROUTER_API_KEY in the child process environment; it does not store the key in the run artifacts.
You can also let Contractor load a trusted local env file without printing secrets:
contractor run "write hello.txt" \
--budget 1 \
--agent pi \
--provider openrouter \
--model qwen/qwen3.7-max \
--env-file /absolute/path/to/.env.local \
--writeSmoke test:
mkdir -p /tmp/contractor-pi-smoke
contractor run "write hello.txt containing exactly hello and nothing else" \
--budget 1 \
--agent pi \
--provider openrouter \
--model qwen/qwen3.7-max \
--cwd /tmp/contractor-pi-smoke \
--max-iterations 1 \
--writeThe Pi adapter parses JSONL session and message events, including provider, model, token usage, cache usage, and usage.cost.total when Pi reports it. It has been smoke-tested with Pi 0.79.1 and OpenRouter qwen/qwen3.7-max.
Pricing
Contractor includes a small bootstrap price table for Qwen, GPT-5.x, and Claude models. For current OpenRouter prices, refresh the local cache:
contractor prices qwen --refresh-openrouter
contractor prices qwen/qwen3.7-maxPrices are printed as dollars per 1M input, output, cache-read, and cache-write tokens. The CLI searches the local table and cached OpenRouter catalog; if a specific searched model is not found and you did not already refresh, it tries a live OpenRouter refresh once.
OpenRouter model prices are stored in:
<target repo>/.contractor/cache/openrouter-models.jsonGeneric Telemetry
Any wrapped command can emit a telemetry marker on stdout or stderr:
CONTRACTOR_USAGE {"usage":{"prompt_tokens":1200,"completion_tokens":800},"total_cost_usd":0.0123,"session_id":"abc"}Contractor will prefer this over estimates.
Config
Create a local config file:
contractor initThis writes:
<target repo>/.contractor/contractor.config.jsonCustom agents from config can be invoked by name:
{
"defaultAgent": "auto",
"subscriptionAgents": ["codex", "claude", "cursor"],
"tokenBudget": null,
"fallback": true,
"paidFallback": true,
"paidFallbackProvider": "openrouter",
"paidFallbackModel": "qwen/qwen3.7-max",
"isolation": "auto",
"overagePercent": 0.1,
"overageTokens": 0,
"completionThresholdPercent": 0,
"completionThresholdUsd": 0,
"completionThresholdTokens": 0,
"effortModel": "gpt-5.4",
"fallbackIterationTokens": 10000,
"envFiles": [],
"agents": {
"my_agent": {
"command": "my-agent --prompt {{prompt}}",
"model": "qwen/qwen3-coder-plus"
}
}
}Then:
contractor run "improve this package" --budget 5 --agent my_agentenvFiles can include absolute or repo-relative .env files:
{
"envFiles": [
{
"path": "/absolute/path/to/.env.local",
"required": true,
"keys": ["OPENROUTER_API_KEY"]
}
]
}Contractor loads those values into child processes without printing secret values. When keys is set, Contractor imports only those variables from that env file. Use this for shared env files that contain unrelated credentials.
Validation
Validate the local install without spending provider credits:
contractor validate --agents codex,claude,cursor,pi,genericRun real smoke tests with a tiny budget:
contractor validate \
--real \
--budget 0.05 \
--agents codex,claude,cursor,pi,generic \
--env-file /absolute/path/to/.env.localValidation writes:
<target repo>/.contractor/validation/<timestamp>/validation-summary.md
<target repo>/.contractor/validation/<timestamp>/validation-summary.jsonStatuses are explicit:
passed: command builder and, for--real, smoke run worked.blocked_missing_binary: install the CLI or put it onPATH.blocked_auth: sign in, configure the provider, or set the required API key.failed_runtime: Contractor saw a runtime failure that did not look like auth, quota, rate limit, or unsupported model.
Cursor Agent uses the agent binary. If contractor validate --real reports Cursor auth blocked, run:
agent loginCurrent Validation Snapshot
This checkout was validated on June 10, 2026 with:
npm run check
npm test
contractor validate --agents codex,claude,cursor,pi,generic \
--env-file /Users/mfkhalil/CustomSearch/skill-registry/.env.local
contractor validate --real --budget 0.05 \
--agents codex,claude,cursor,pi,generic \
--env-file /Users/mfkhalil/CustomSearch/skill-registry/.env.localResults on this machine:
| Surface | Result | Notes |
| --- | --- | --- |
| Unit/integration tests | passed | Node tests, including MCP run/status/report/apply/reverse/stop/validate |
| Static adapter validation | passed | codex, claude, cursor, pi, generic |
| Codex real smoke | passed with overage_limit_reached | Subscription/normalized usage works; tiny budgets can overshoot one turn |
| Claude real smoke | passed | Uses --permission-mode acceptEdits in Contractor write mode |
| Cursor real smoke | blocked_auth | Local CLI says: run agent login or set CURSOR_API_KEY |
| Pi/OpenRouter real smoke | passed | qwen/qwen3.7-max through SkillHound .env.local |
| Generic command real smoke | passed | CONTRACTOR_USAGE telemetry path works |
Real-money Pi burn-in was run with OpenRouter qwen/qwen3.7-max across 10 task fixtures at $0.02 per case. Total accounted spend was $0.256740 before the API-docs rerun; replacing that failed case with the fixed rerun brings the accounted total to about $0.277374. Nine original suite cases stopped with overage_limit_reached; the API-docs case initially exposed a stdout error-classification bug, was fixed, and then passed on rerun at $0.0256335.
The burn-in artifacts are under:
/Users/mfkhalil/CustomSearch/contractor/.contractor/burn-in/pi-openrouter-qwen-2026-06-10-v3/Three issues found during real burn-in were fixed:
- Empty snapshot workspaces now create an empty baseline commit so patches are not empty.
- Structured zero-cost Pi telemetry from timed-out streams now uses fallback accounting instead of bogus raw JSON text estimates.
- Generated stdout containing phrases like
API keyis no longer misclassified as a non-retriable auth error.
MCP
Contractor includes an MCP server for agent-to-agent use:
contractor-mcpIt exposes:
contractor_runcontractor_statuscontractor_reportcontractor_watchcontractor_stopcontractor_applycontractor_agentscontractor_pricescontractor_validate
The MCP server reads .contractor/contractor.config.json, applies configured envFiles, and uses the same subscription-first route as the CLI. contractor_run accepts either budget or token_budget and returns immediately by default with the run id, run directory, workspace path, report path, watch.md path, activity.md path, current spend, remaining budget, route attempts, and live budget file path. Pass background: false if the caller explicitly wants to wait for the run to finish before returning. contractor_prices returns per-1M token prices for matching models.
Design Boundary
Contractor is not a model gateway, a planner, or a promise that all side effects can be undone. It is the durable supervisor around agents that already know how to code. The first version optimizes for:
- Budget-first loops.
- Reversible file changes.
- Inspectable run reports.
- Fast stop/status.
- Portable process supervision.
- Focused adapters for Codex, Claude, Cursor, Pi, and generic commands.
- Durable artifacts that another agent can inspect after the run.
