The Three Reversibility Tiers
Each action schema declares areversibility field that classifies what the action does to application state. Treat this classification as a binding safety contract, not a hint.
read — No state change
read — No state change
The action reads data from the application without modifying anything. There is no confidence gate and no verification requirement. These actions are safe to call at any confidence level, including during probe warm-up.Examples:
get_thread, search_inbox, list_contacts, read_document, get_draftreversible_write — Undoable change
reversible_write — Undoable change
The action changes application state, but the change can be undone (for example, moving an email to a folder, starring a message, or creating a draft). A minimum confidence score of 0.80 is required. A
verify_expression is not mandated but is strongly recommended.Examples: archive, label, move_to_folder, mark_read, star, create_draft, update_draftirreversible_write — Permanent change
irreversible_write — Permanent change
The action causes a permanent side effect that cannot be undone once it executes. Sending an email, deleting a record permanently, making a purchase, and submitting a form all fall into this tier.This tier has the strictest requirements:
- Confidence must be ≥ 0.95
verifymust betrue- A
verify_expressionmust be provided
send_reply, delete_permanently, purchase, submit_form, send_messageConfidence Gating Table
The daemon checks theconfidence field of the current SubstrateState before dispatching any write action. If confidence falls below the required threshold, the action is rejected.
The effective confidence used for gating is
min(schema.confidence, state.confidence). If the probe ran in DOM fallback mode, state.confidence may be 0.70–0.85, which blocks irreversible_write actions until a higher-quality V8 probe cycle completes. Call GET /state?fresh=true to trigger a fresh probe cycle immediately.The verify_expression Requirement
For everyirreversible_write action, you must provide a verify_expression in your request body. After the action executes, the daemon waits post_action_wait_ms (default: 200 ms) for the application to settle, then evaluates the expression against the updated SubstrateState.objects. If the expression evaluates to false, the daemon returns 422 with a verification_failed error.
verify_expression Syntax
Expressions use a lightweight, restricted evaluator — not arbitrary JavaScript. The following patterns are supported:
Expressions are limited to 2048 bytes by default and must evaluate to a boolean.
Verification Timeout
Useverify_timeout_ms to control how long the daemon polls for the post-action state to satisfy your expression. Set this to match the expected latency of the underlying operation — email sends typically need 3–8 seconds; database writes might need up to 30 seconds.
false after the timeout, the daemon returns 422:
The Daemon’s Three-Layer Safety Gate
Requests toPOST /substrate/v1/action pass through three sequential checks before reaching the action dispatcher:
delete, remove, destroy, drop, purge, wipe, reset, clear_all, empty, terminate, shutdown, or kill when the daemon cannot resolve the target process to a known schema. This is a safety net for race conditions during probe warm-up; it is not a substitute for proper schema reversibility declarations.
Error response when verify_expression is missing for an irreversible action:
How the Python SDK Enforces Action Safety
The Python SDK adds a client-side pre-flight check that runs before any HTTP request is made. When you callsession.execute(), the SDK inspects the cached action schema. If the action’s reversibility is IRREVERSIBLE_WRITE and you passed verify=False (or omitted verify_expression), the SDK raises NSPIrreversibleNotConfirmedError immediately — no network round-trip is wasted.
NSPIrreversibleNotConfirmedError at the call site if you need to surface a clear error to users or logs:
Handling NSPConfidenceTooLowError
If confidence falls below the required threshold at execution time, the SDK raisesNSPConfidenceTooLowError. Refresh the probe and retry:
Best Practices for Production Agents
1
Always pass verify=True for irreversible actions
The SDK and daemon both enforce this, but making it explicit in every call site documents your intent and prevents confusion when reading the code later.
2
Write specific verify_expression strings
Vague expressions like
"inbox.sent_count > 0" will pass even if a previous send already incremented the counter. When you know the pre-action state, use it to write a tighter assertion.3
Set verify_timeout_ms to match the operation's expected latency
Network-dependent actions (email sending, form submissions, API calls) may take several seconds to settle. Default
verify_timeout_ms is 5000 ms, which suits most UI interactions. Raise it for slower back-end operations.4
Handle NSPConfidenceTooLowError gracefully
Don’t let a transient low-confidence state crash your agent. Wait for the probe to reach full confidence before retrying irreversible actions — forcing a
fresh=true probe cycle is the fastest way to recover.