An agent proposes. Something else decides.
Four stages, always in order. No way around them.
The four stages
01
Propose
An agent names an action. Nothing more.
02
Evaluate
The engine answers permit, hold or deny.
03
Execute
Only a clean permit reaches the connector.
04
Record
Every verdict goes to a hash chain.
Cannot reach a connector or a credential.
Never executes.
Retries carry a key. No duplicates.
Refusals count as much.
- 01Agentproposes · holds no credentials
- 02Decision enginepermit · hold · deny · your rules, not the model
- 03Executepermit → Connector
- 04A personhold
- 05Nothing happensdeny
Propose
An agent names an action. Nothing more.
Evaluate
The engine answers permit, hold or deny.
Execute
Only a clean permit reaches the connector.
Record
Every verdict goes to a hash chain.
After a permit
- ATTEMPT 1 OF 3
- ATTEMPT 2 OF 3
- ATTEMPT 3 OF 3
- Dead letter
Nothing reached the connector.
Record: 0 rows
Each row carries the hash of the row before it. The real trail is kept and verified by the decision engine.
Change a word, then verify
Pick a row and change its verdict. It keeps the hash it was written with, so the hash no longer matches what the row says.
Propose something first. The chain starts with the first verdict.
When something goes wrong
Pick a row to run it
- The engine times outNothing executes. Recorded as a deny.A slow answer is not a permit.Run it
- The engine returns an errorNothing executes. Recorded as a deny.An error means no rules applied.Run it
- The engine is unreachableNothing executes. Recorded as a deny.Governance is never traded for speed.Run it
- The verdict is holdIt waits for a person.Resolving it alone would defeat it.Run it
- The verdict is denyIt stops, recorded as denied.What it was stopped from doing matters.Run it
- The connector fails after a permitThree tries with a backoff, then the dead letter.The decision was clean; only delivery failed.Run it
- The connector refuses for goodOne try, then the dead letter.A retry cannot fix a refusal.Run it
Two fixed contracts, no free-text channel.
What an agent proposes
- action
"messaging.send"The abstract operation. Never a vendor name.- parameters
{"to": "demo-ent-marcela", "body": "demo-draft-0421"}What it needs. Structured and validated.- agent_id
"demo-agent-support"Who proposed it.- tenant_id
"demo-tenant"Whose workspace. Rules resolve per tenant.- context
{"open_conversation": true, "last_inbound_at": "2026-08-21T02:14:00Z"}What the agent saw. A reviewer reads this.- idempotency_key
"demo-reply-marcela-1"The intent's identity. Two deliveries run once.
What comes back
- decision
"hold"permit, hold or deny. Nothing partial.- reason
"example_rule.hold_reply_open_conversation"Why, in terms of the rule.- trace_id
"demo-trace-0001"Ties the verdict to its audit entry.- latency_ms
nullDecision time. Null here: no engine is called.- active_campaigns
[]Standing rules in force, kill switch included.- shadow
falseWhether rules are being proven, not enforced.
Around the seam
The review queue
Built and testedA hold carries the proposal, context and rule. Approving releases one action.
Holds resolve asynchronously
Built and testedWebhooks and polling both. An unanswered day becomes an insight.
Shadow mode
Built and testedNot a dry run: the action runs, the verdict is only written down.
The decision trail
Built and testedAppend-only and hash-chained, with a verify endpoint.
Outside assistants
Built and testedSame four stages, strictly fewer permissions than an agent here.
Decisions stay human
Built and testedNo permission accepts a decision, answers a hold or executes.
Where this stands today
The seam
Built and testedAgents cannot build an executor; the test suite enforces it.
Two gates, one interface
Built and testedProduction calls the engine. Local permits all, shown as ungoverned.
Your actions and guardrails
Not builtWritten with you. Until then, rules propose only human work.