Share a worker budget
Run three concurrent workers against one USD 2.00 grant and watch two price sheets publish while the kernel refuses the third.
On this page
Three repricing workers draw on one signed allowance, USD 0.80 per job and USD 2.00 in total, and the kernel settles which jobs fit before the reprice tool writes a row.
This is the repricing service from Handle tool outcomes. Each worker submits its own job against the same capability, and durable admission reserves the job's 80 cents before dispatch. The local credit adapter moves no external money.
StartThe worker reserves USD 0.80 before dispatch
The kernel reserves 80 of the grant's 200 cents, dispatches reprice, and settles the reservation as spent when the sheet publishes. Two admitted workers leave USD 1.60 spent and USD 0.40 available. Budgets and metering.
Three jobs need USD 2.40 and the grant holds USD 2.00, so two are admitted and one is refused before execution. Every worker’s outcome ends in its own receipt, except the one canceled before it reached the kernel.
1. Start the service on an unused allowance
Starts from chio new pricing-desk --example pricing-desk, or the same project as a download. The desk is Larkfield's pricing functions behind small hosts that embed the kernel, so it needs no separate Chio process. Cargo selects Rust 1.94.1 from the project's toolchain file and fetches the Chio crates its manifest pins; the first build takes a few minutes.
chio new pricing-desk --example pricing-desk && cd pricing-desk
CHIO_APP_STATE=state/shared-workers cargo run --locked --bin repricing-serviceThe host keeps its state in state/shared-workers and signs receipts into state/shared-workers/receipts.db. It serves its interface at 127.0.0.1:4312; keep the terminal running.
Before you begin
Use a Unix-compatible system with durable file locking, Rust through rustup, and curl and jq for the client commands. Keep the selected port free. The project README lists every endpoint, credential file, and supported persistence setting.
You should see the repricing interface on a fresh USD 2.00 allowance. Keep this state directory for the whole guide; stop any other process on port 4312 first. The default state/repricing directory holds whatever allowance Handle tool outcomes left, so choose Read allowance before reusing it.
2. Race three workers
Save this client as race.py in pricing-desk. It sends three jobs at once, polls each returned ID, and keeps every result. It needs only Python's standard library.
import concurrent.futures
import json
from pathlib import Path
import time
import urllib.request
token = Path("state/shared-workers/client-token.txt").read_text().strip()
def api(path, body=None):
request = urllib.request.Request(
"http://127.0.0.1:4312" + path,
data=None if body is None else json.dumps(body).encode(),
headers={"Authorization": "Bearer " + token,
"Content-Type": "application/json"})
with urllib.request.urlopen(request, timeout=15) as response:
return json.load(response)
def worker(number):
job = api("/jobs", {"percent": 10, "mode": "complete"})
for _ in range(100):
result = api(job["status_url"])
if not result["active"]:
return result
time.sleep(0.1)
raise TimeoutError("Keep this job ID for inspection: " + job["job_id"])
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
results = list(pool.map(worker, range(1, 4)))
Path("worker-results.json").write_text(json.dumps(results, indent=2))
for result in results:
print(result["job_id"], "published:", result["published"])4a1e2dc3-0078-443f-a63f-0f645ee1c2e3 published: False 04c1cf24-d3ea-4597-a2ab-5a3b5e651063 published: True d9254698-a42e-4555-89d3-988cf40255bb published: True
You should see two price sheets published and one job refused. Which worker loses is scheduling, outside the contract. In the interface, Start three concurrent workers runs the same race.
3. Read the shared account
Ask the host for the allowance the three workers shared.
curl -sS http://127.0.0.1:4312/budget \
-H "Authorization: Bearer $(cat state/shared-workers/client-token.txt)" | jq .{
"available": 40,
"capability_id": "cap-01a12ee0-c430-73e5-865a-a4c1be84038f",
"committed": 160,
"currency": "USD",
"exposed": 0,
"local_credits": true,
"spent": 160,
"total": 200,
"unit": "cent"
}You should see 160 cents spent and 40 available under one capability ID. Amounts are in cents (unit). The fields have separate lifecycles, so read each one on its own:
totalis the grant's USD 2.00 ceiling, shared by every worker.exposedis authority reserved or retained for admitted work, which is neither spent nor refundable on its own.spentis realized spending; two settled USD 0.80 jobs make USD 1.60.committedis the store’s computed committed cost, read as returned.availableis the total minus committed, saturating at zero; 40 cents cannot admit an 80-cent job.
The grant every worker shares
reprice_grant.max_cost_per_invocation = Some(MonetaryAmount {
units: PRICE, // 80 cents
currency: "USD".into(),
});
reprice_grant.max_total_cost = Some(MonetaryAmount {
units: TOTAL, // 200 cents
currency: "USD".into(),
});This is the local-credit accounting of Budgets and metering. Hosts on separate machines share one allowance through a shared admission authority, as in cluster budgets, never through copied database files or separately issued grants.
4. Restart the host and read the same allowance
Stop the host after the workers finish and start it again with the same command and state directory. Read the account with the step 3 command.
You should see the same capability ID with 160 cents spent and 40 available: the host reloads its signed capability, kernel identity, admission accounting, and payment ledger, and keeps the allowance it issued. Compare the capability ID as well as the numbers, because a different grant is a different allowance. Submit one more job; it is refused with 40 cents available, and no new price sheet appears. The captured run shows the account identical before and after restart.
5. Cancel one worker and interrupt another
On an unused allowance, cancel a worker while it is still queued, then start another and kill the host after its first row appears, as a crash would. Restart and read the account.
curl -sS http://127.0.0.1:4312/budget \
-H "Authorization: Bearer $(cat state/shared-workers/client-token.txt)" | jq .{
"available": 120,
"capability_id": "cap-01a12ece-e2d0-777f-bf7a-f801ae3be8b1",
"committed": 80,
"currency": "USD",
"exposed": 80,
"local_credits": true,
"spent": 0,
"total": 200,
"unit": "cent"
}You should see 80 cents exposed and 120 available. The queued worker never reached the kernel: it has a cancellation record, no reservation, no receipt, and left the account unchanged. The interrupted job reads needs_reconciliation: true and keeps its reservation until you resolve it by its job ID; the host never refunds or reruns it. Inspect its request file, partial sheet, and the captured interruption. Clearing admission databases to make the balance look available destroys that evidence.
6. Verify each worker’s receipt
Read the three results race.py saved and match each worker to its signed outcome.
jq -r '.[] | [(.job_id[0:8]), .result.verdict, (.result.receipt_id[0:16]), (.result.reason // "")] | @tsv' \
worker-results.json4a1e2dc3 Deny 0e14593773bbbc3f invocation budget exhausted for capability cap-01a12ee0-c430-73e5-865a-a4c1be84038f 04c1cf24 Allow 24983253ca872bf9 d9254698 Allow 839076427f6042d4
You should see three distinct receipt IDs: two Allow and one Deny naming budget exhaustion for the shared capability. Each worker reads its own result, because one aggregate balance cannot say which requests ran.
Complete source: src/repricing.rs, src/lib.rs, application_ci.py, all in the project download.