Skip to main content
Activation status: Source-ready, with compatible Worker code deployed. Required production database activation and authenticated approval canaries remain pending. The live partner-preview baseline already supports durable task execution, run reads, and SSE; do not depend on public approval events until activation is confirmed.
The first public approval boundary is intentionally narrow. Praxa projects an approval only for an immutable browse_session_act action whose exact instruction or steps, target host, and action digest can be re-derived at every boundary. Generic tool approvals and candidate turn approvals are not available. mode:"turn" returns 400 invalid_request, and /v1/chat is not routed.

The golden rule

Render summary exactly as supplied. Do not truncate, paraphrase, translate, reorder, or reconstruct it. The public read, event, and mutation paths all enforce the same invariant: render == record == gate-match. action_digest is the 64-character lowercase SHA-256 digest that binds the decision to that exact stored action. Copy it from the current pendingApproval or approval.required projection; never compute it in the browser.

1. Listen for the durable event

Open GET /v1/runs/{run_id}/events with a key carrying runs:read. Resume a disconnected stream with its last committed numeric SSE id in Last-Event-ID.
The same fields can appear in GET /v1/runs/{run_id} as pendingApproval while status is awaiting_approval. If Praxa cannot prove the exact presentation, it emits no approval event, omits the pending projection, and refuses a decision.

2. Render and collect a decision

Show summary verbatim in an expandable or scrollable region. Offer two explicit controls: Approve and Deny. There is no default and no public “approve all” path. If expiresAt is present, show it and disable stale controls; the server remains authoritative.

3. Post the exact digest

Use a key carrying runs:write:
The public request literal is exactly "approve" or "deny". A successful response is the updated run projection; it means the decision was recorded, not that the action or run has finished. Continue reading events until run.completed, run.failed, or run.cancelled.
Late, replayed, unsupported, foreign-tenant, changed-state, expired, and digest-mismatched decisions fail closed. A stale or mismatched digest returns 409 conflict rather than authorizing whatever is currently parked.

4. Compact React example

This component expects validated durable run events from your SSE reader. It does not place a secret API key in browser code; call onDecision through your authenticated backend.
If an approval.required webhook wakes your backend, fetch the current run and require a matching pendingApproval before rendering. The webhook records a resolved decision as approved or denied; those webhook payload values are different from the public mutation literals approve and deny.

Next steps

Last modified on August 14, 2026