Chio/Docs
LOGIN · JOIN

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.

Guide chio@5f63e6e

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.

Start
Shared allowance outcomesChoose an outcome.

The 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.

One worker's repricing job against the shared grant: USD 0.80 per job, USD 2.00 in total. Amounts, reasons, and the interrupted job's exposure come from the captured runs; which worker wins a race is scheduling, so worker identities are left out.

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.

Terminal · pricing-deskbash
chio new pricing-desk --example pricing-desk && cd pricing-desk
CHIO_APP_STATE=state/shared-workers cargo run --locked --bin repricing-service

The 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.

race.py · run python3 race.pypython
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"])
Output
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.

Terminal · pricing-deskbash
curl -sS http://127.0.0.1:4312/budget \
  -H "Authorization: Bearer $(cat state/shared-workers/client-token.txt)" | jq .
Output
{
  "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:

  • total is the grant's USD 2.00 ceiling, shared by every worker.
  • exposed is authority reserved or retained for admitted work, which is neither spent nor refundable on its own.
  • spent is realized spending; two settled USD 0.80 jobs make USD 1.60.
  • committed is the store’s computed committed cost, read as returned.
  • available is the total minus committed, saturating at zero; 40 cents cannot admit an 80-cent job.
The grant every worker shares
src/repricing.rs · when the host issues the grantrust
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.

Terminal · pricing-deskbash
curl -sS http://127.0.0.1:4312/budget \
  -H "Authorization: Bearer $(cat state/shared-workers/client-token.txt)" | jq .
Output
{
  "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.

Terminal · pricing-deskbash
jq -r '.[] | [(.job_id[0:8]), .result.verdict, (.result.receipt_id[0:16]), (.result.reason // "")] | @tsv' \
  worker-results.json
Output
4a1e2dc3	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.

Next: trace a repricing job to its receipt