Skip to main content
Every action that changes application state passes through a multi-layer safety gate before the daemon dispatches it to the target runtime. The gate is not advisory — it actively rejects requests that don’t meet the requirements for the action’s reversibility tier. Understanding these tiers and how to satisfy their requirements is the most important safety topic for anyone building production agents on NSP.

The Three Reversibility Tiers

Each action schema declares a reversibility field that classifies what the action does to application state. Treat this classification as a binding safety contract, not a hint.
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_draft
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_draft
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
  • verify must be true
  • A verify_expression must be provided
Examples: send_reply, delete_permanently, purchase, submit_form, send_message

Confidence Gating Table

The daemon checks the confidence 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 every irreversible_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 that reference fetch(, eval(, XMLHttpRequest, import(, or document.cookie are rejected by the expression validator before execution. Expressions are evaluated inside the daemon process, not inside the browser tab.
Expressions are limited to 2048 bytes by default and must evaluate to a boolean.

Verification Timeout

Use verify_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.
If the expression is still false after the timeout, the daemon returns 422:

The Daemon’s Three-Layer Safety Gate

Requests to POST /substrate/v1/action pass through three sequential checks before reaching the action dispatcher:
The heuristic gate in Layer 3 catches action names containing words like 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 call session.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.
Catch 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 raises NSPConfidenceTooLowError. 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.