Approval Service¶
clauz3 run has a separate approval step between proof and execution. The
approval service is the user-controlled process that receives the proved
program, shows the contract and code, records the user's decision, and returns
that decision to clauz3 run.
The important trust-boundary rule is that the agent should not own this process. An agent may write the program and guarantees, but a user or harness should start and inspect the approval service.
Runtime flow¶
clauz3 run follows this order:
- Parse and prove the requested target,
mainby default. - Stop immediately if any proof fails. No approval request is submitted.
- Build a JSON approval request containing the program, declared guarantees, proof summaries, trusted roots, import roots, and program hash.
POSTthat request to the configured approval service.- The approval service records the request as pending and shows it in the UI.
- The user approves or rejects the request, optionally with a reason.
- The original
POSTreturns that decision toclauz3 run. clauz3 runexecutes the target only if the decision is approved and has a receipt.
The approval artifact is the verified contract, not the agent's prose summary. The program source is included for inspection and shown behind an expandable section in the bundled UI.
Current local service¶
The bundled clauz3 approval-service is a small localhost FastAPI service with
a REST API and browser UI. It keeps requests and decisions in memory. It is
meant for local demos, integration tests, and harness-controlled approval
experiments.
It is not a hardened network service. Bind it to 127.0.0.1 unless you have
added your own authentication, authorization, storage, and network controls.
Start the service¶
From a project or example directory:
Command-line arguments:
| Argument | Default | Meaning |
|---|---|---|
--host HOST |
127.0.0.1 |
Host address to bind. Use localhost for normal use. |
--port PORT |
8765 |
TCP port to listen on. |
There is no decision config for the real approval service. Decisions come from the user through the UI or the decision API.
On startup the service prints the environment variable value that clauz3 run
can use:
Open the browser UI at:
User decisions¶
The bundled UI offers these decision paths:
| Decision | Meaning |
|---|---|
approved_once |
Approve this exact request and issue a receipt. |
rejected_contract |
Reject because the proved contract says the program will do something the user does not want. |
request_more |
Reject for now because the contract or explanation is too vague. The agent should revise and submit a new request. |
rejected |
Reject without a more specific subtype. |
Only these approved decisions let clauz3 run execute:
approved_onceapproved_rememberauto_approved
All other decisions are treated as not approved. The current UI issues
approved_once; future policy layers may issue approved_remember or
auto_approved.
The optional reason field is stored as feedback in the JSON response. It is
user-authored decision text, not startup config.
REST and UI routes¶
| Route | Method | Purpose |
|---|---|---|
/health |
GET |
Health check. |
/ or /ui |
GET |
Browser request list. |
/requests or /api/requests |
POST |
Submit an approval request and wait for a decision. |
/requests or /api/requests |
GET |
List recorded requests. |
/requests/{request_id} or /api/requests/{request_id} |
GET |
Fetch one recorded request. |
/requests/{request_id}/decision or /api/requests/{request_id}/decision |
POST |
Record the user decision. |
/ui/requests/{request_id} |
GET |
Browser detail view for one request. |
The decision API accepts JSON:
{
"decision": "request_more",
"feedback": "The contract should say exactly which files will be written."
}
For approved decisions, the service returns a receipt. If the decision API does
not provide one, the local service generates local-<request_id>.
Connect clauz3 run¶
clauz3 run discovers the approval-service URL in this order:
CLAUZ3_APPROVAL_SERVICECLAUZ3_APPROVAL_URL.clauz3/approval-service.jsonin the current working directory
The recommended path for interactive use is to export the value printed by the service:
For a repo-local discovery file:
The .clauz3/approval-service.json file must contain a non-empty string url.
It is read by clauz3 run; it is not passed to clauz3 approval-service.
Example run from examples/email:
clauz3 run waits for user approval. The default wait is 300 seconds and can be
changed with:
If no approval-service URL is configured, clauz3 run exits with:
Mock approval service¶
clauz3 mock-approval-service is the config-driven service. Use it for tests,
automation, and local demos where no user will click the UI.
Start it with a JSON decision config:
printf '%s\n' '{"decision": "approved_once"}' > approval.json
uv run clauz3 mock-approval-service --config approval.json --port 8765
Mock command-line arguments:
| Argument | Required | Default | Meaning |
|---|---|---|---|
--config PATH |
yes | none | JSON decision config described below. |
--host HOST |
no | 127.0.0.1 |
Host address to bind. |
--port PORT |
no | 8765 |
TCP port to listen on. |
Mock config keys:
| Key | Type | Meaning |
|---|---|---|
decision |
string | Decision returned to clauz3 run. Defaults to approved_once when omitted. |
receipt |
string | Optional receipt returned for approved decisions. Defaults to mock-<request_id> when omitted. |
feedback |
string | Optional mock feedback returned with the decision. |
require_program_sha256 |
string | Optional exact program hash gate. If the request hash does not match, the mock returns rejected. |
Example mock denial:
The mock server supports POST /requests and prints
CLAUZ3_APPROVAL_SERVICE=... on startup, but it does not provide the FastAPI
browser UI or request-list routes.
Protocol shape¶
A clauz3 run request looks like this:
{
"schema_version": 1,
"kind": "clauz3.run",
"request_id": "clr_...",
"program_sha256": "...",
"source_name": "cases/only_bob_pass.py",
"target": "main",
"trusted_roots": ["tools/email/trusted"],
"import_roots": ["."],
"guarantees": ["emails.only(['bob@example.com'])"],
"proofs": [
{
"name": "main",
"conclusion": "proved!",
"description": "assertion, guarantee"
}
],
"program": "..."
}
The service response must be a JSON object with at least a string decision:
For approved decisions, clauz3 run requires a receipt before it executes the
target. For not-approved decisions, clauz3 run exits without executing and
prints the decision plus any feedback.
Troubleshooting¶
error: no approval service configured; set CLAUZ3_APPROVAL_SERVICE-
The service URL is not visible to
clauz3 run. Export the printedCLAUZ3_APPROVAL_SERVICE=...value, or create.clauz3/approval-service.jsonwith aurlkey. approval service is unavailable-
The approval service is not running, the URL points at the wrong port, or the service stopped before the user decision was recorded.
approval: request_more-
The user or mock service responded with a not-approved decision. Read the printed feedback or inspect the request in the browser UI, strengthen the program guarantees, and run again.