Overview
QWED Agent Verification provides:- Pre-execution checks before agents act
- Budget enforcement to limit costs
- Risk assessment for each action
- Activity logging for audit trails
Registering an agent
These examples use
AgentService from the qwed package. It runs in your own process and keeps agents, budgets, and conversation state in memory. The Python SDK’s QWEDClient has no agent methods. A QWED server registers agents with POST /agents/register and verifies their requests with POST /agents/{agent_id}/verify.agent_id.
Verifying actions
Before an agent executes an action, you must provide anActionContext with a conversation_id and a monotonically increasing step_number. These fields are required — requests without them are rejected.
Conversation controls
QWED enforces runtime guardrails that prevent agents from replaying actions, running in infinite loops, or exceeding conversation length limits. These checks run automatically on everyverify_action call.
How it works
Each call toverify_action must include a conversation_id (identifying the current session) and a step_number (a positive integer that increases with each action in that session). QWED uses these fields to enforce four controls:
Incrementing steps correctly
Thestep_number must be strictly greater than any previously committed step within the same conversation. If an action is denied (for example, due to a loop), that step number is not consumed — you can retry the same step with a different action.
Progress-aware doom loop detection (LOOP-004)
New in v5.1.0
pre_action_state_hash and state_source in the action context:
UPDATE, DELETE, DROP or TRUNCATE is critical risk, which a supervised agent is denied, so this example uses a SELECT.
The guard tracks a sliding window of the last 20 action+state fingerprints per conversation. If the same fingerprint appears 3 or more times, the action is halted with QWED-AGENT-LOOP-004.
Accepted state_source values:
Validation rules:
pre_action_state_hashmust be a 64-character lowercase hex SHA-256 digest- Both
pre_action_state_hashandstate_sourcemust be provided together — supplying only one is rejected - During gradual rollout, both fields are optional. When the server enables
DOOM_LOOP_GUARD_REQUIRED, they become mandatory
LOOP-004 fingerprints are only committed to the sliding window when the action decision is
APPROVED. Denied and pending actions do not affect the history, preventing false positives from rejected retries.Trust levels
Tool approval policy
Changed in v5.0.2
ToolApprovalSystem classifies every tool call into one of three categories before execution:
Unknown tools are denied by default, regardless of their heuristic risk score. Previously, unknown tools with a low risk score (below 0.3) were auto-approved. This fail-closed behavior ensures that new or unexpected tools cannot execute without being explicitly added to the allowlist.
When an unknown tool is blocked, the response includes the tool name and the computed risk score for debugging:
ToolApprovalSystem configuration.
Risk assessment
A tool action takes its risk fromTOOL_RISK_LEVELS. An engine action is critical when its query (or code) contains DROP, DELETE, TRUNCATE or UPDATE, or eval(, exec(, os.system or subprocess; otherwise it is low.
Decision matrix
With the defaultrisk_threshold="medium", and when the permission checks pass:
Critical risk needs trust level 3. Risk above
risk_threshold is PENDING below trust level 2. Pass risk_threshold="low" to verify_action to send medium-risk actions to PENDING too.
Tool approval policy
Changed in v5.0.2
Unknown tools are always denied, regardless of their computed risk score. The blocked response includes the tool name and risk score for debugging:
Adding tools to the allowlist
The safe and dangerous lists are set inToolApprovalSystem.__init__ (safe_operations and dangerous_operations). To allow a custom tool, add it to safe_operations on the instance that approves your tool calls:
AgentService stores a permissions["allowed_tools"] list but doesn’t check it: it checks only blocked_tools and allowed_engines. To let an agent use a custom action type, register it in ACTION_ENGINES or TOOL_RISK_LEVELS; see Registered action enforcement.
Budget enforcement
verify_action enforces two of these limits: the daily cost (QWED-AGENT-BUDGET-001) and the requests per hour (QWED-AGENT-BUDGET-002). The daily cost is an estimate, not your actual spending: each approved action adds a fixed $0.01, whatever it costs to run. Enforce your real spending limits separately. max_per_day and current_day are reported but not enforced, so an agent that stays under its hourly limit isn’t stopped at its daily request count.
Activity logging
AgentService keeps an in-memory log of the decisions that reach its permission and risk checks: APPROVED, PENDING, and policy DENIED. It returns the other denials without logging them: an unregistered or suspended agent, a context or loop check (such as the replay above), an unknown action type, and a budget limit. Don’t treat this log as a complete audit trail. Read an agent’s entries with get_agent_activity:
AgentService doesn’t register agents with the server, so register the agent with POST /agents/register and set QWED_AGENT_ID and QWED_AGENT_TOKEN to the agent_id and agent_token it returned before you run this example.
GET /agents and read any of those agents’ activity with an API key or a signed-in session, without the agent’s token:
limit accepts 1 to 200. See GET /agents/{agent_id}/activity for the access rules and status codes.
Runtime hardening
New in v5.0.0
Action context enforcement
Everyverify_action call requires an ActionContext with:
The step number must increase with each action in a conversation. Attempts to reuse or decrement step numbers are rejected.
Registered action enforcement
New in v5.1.1
verify_action call must reference an action_type that QWED has registered semantics for. Action types must be either bound to a verification engine or registered as a governed tool with a known risk level. Engine examples: calculate → math, execute_sql → sql. Tool examples: database_read, send_email. Action types outside those two registries are denied with QWED-AGENT-ACTION-001 before any risk assessment runs.
ACTION_ENGINES or add it to your tool registry with an explicit risk level. Tools registered this way are reported with engine: "tool_control" in the verification response.
Replay and loop detection
The agent service detects and blocks four types of problematic patterns:
Actions are fingerprinted deterministically using
action_type, query, code, target, and parameters. When pre_action_state_hash is provided, the fingerprint also incorporates the world state hash. If a loop is detected, the conversation state is not advanced — the agent can recover by submitting a different action at the same step number.
Fail-closed for unknown action types
QWED denies anyverify_action call whose action_type has no registered engine binding or tool risk level. Action verification is deterministic — actions without explicit semantics cannot be risk-assessed or routed to a verification engine, so the kernel returns a denial instead of falling back to a permissive default.
QWED-AGENT-ACTION-001 releases the in-flight step reservation, so the agent may retry the same step number with a registered action type. Registered action types include the entries in ACTION_ENGINES (execute_sql, execute_code, calculate, verify_logic, and verify_fact) and tools listed in TOOL_RISK_LEVELS.
In-flight reservation system
While QWED processes averify_action call, it reserves the step number so concurrent requests cannot claim the same step. QWED releases the reservation if the action is denied, allowing the agent to retry with a different action at the same step.
Budget denial behavior
When a budget check fails, the conversation step is not consumed. This means the agent can retry the same step number after the budget resets without triggering a replay detection error.Fail-closed rate limiting
The Redis-backed sliding window rate limiter fails closed when Redis is unavailable. If the Redis backend encounters an error, all requests are denied rather than allowed, preventing uncontrolled access during infrastructure failures. When Redis is entirely absent at startup, a local in-memory fallback limiter is used instead.Environment integrity verification
On API server startup, QWED runs an environment integrity check (viaStartupHookGuard) before initializing the database. If the environment is compromised, the server refuses to start. This prevents operation in tampered runtime environments.
Timing-safe token verification
Agent token verification useshmac.compare_digest for constant-time comparison, preventing timing side-channel attacks against agent authentication.
Fail-closed on unknown actions
QWED only verifies actions whoseaction_type has explicit, registered semantics. If you submit an action_type that is not bound to a verification engine or a known tool, the request is denied with QWED-AGENT-ACTION-001 before risk assessment runs.
Registered actions fall into two categories:
Anything else — including custom action names, typos, and forward-compatible names that the runtime does not yet recognize — is denied:
- No
verificationblock is returned. Unknown actions never receive an engine label or aVERIFIEDstatus — there is no generic"security"fallback. - The step reservation is released. Because the action was denied, the same
step_numbercan be retried with a registered action (noQWED-AGENT-LOOP-002replay error). - Map custom intents to registered actions. If your agent needs to perform a domain-specific operation, route it through one of the registered verification engines or tool calls rather than inventing a new
action_typestring.
Framework integration
These integrations verify through the QWED API withQWEDClient. They don’t take an agent ID, so the AgentService checks on this page don’t apply to them.