Workflow engine for AI agents
Every rule in it exists because the agent invented a failure once. Realm replaces the rules with structure — wrong behaviour becomes impossible, not prohibited.
The agent calls Realm. Every other tool calls the agent.
The inversion
A prompt rule is a request. A state machine is a constraint. Only one of them survives contact with a model having a bad day.
What that buys you
A step cannot run before its dependencies. Not by instruction — by the state machine.
An answer that breaks its step's schema is rejected at the boundary. It never enters the run.
Critical steps pause until the gate is answered, and the answer goes on the record.
Every step that finishes leaves a record of what it returned. For client work, that record is the deliverable.
See it run
Every step records what it returned, a hash of it, and when. Not a log that something ran: a record of what Realm accepted at each step.
The first step is marked auto, so the engine runs it and no model is involved. The agent only learns what comes next.
Agent calls
{
"tool": "start_run",
"workflow_id": "pr-review",
"params": {
"repo": "acme/payments-api",
"pr_number": 42
}
}Realm answers
{
"status": "ok",
"context_hint": "Step 'fetch_pr' completed. 1 step(s) now available.",
"run_phase": "running"
}What the step fetched — Retry failed charge webhooks, 3 files
--- src/webhooks/retry.ts
@@ -12,14 +12,22 @@ export async function deliverWebhook(event: ChargeEvent): Promise<void> {
- await post(event.endpoint, event.payload);
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
+ try {
+ await post(event.endpoint, event.payload);
+ return;
+ } catch (err) {
+ await sleep(1000);
+ }
+ }
}Evidence recorded for fetch_pr · hash 625650d8cdad…
Agent calls
Realm answers
Evidence recorded for fetch_pr · hash
Review the pull request diff and produce a structured code review with risk assessment, key changes, a recommendation, and a review comment ready to post on the PR. Pick what the agent sends back and see what Realm answered.
Review the pull request diff and produce a structured code review with risk assessment, key changes, a recommendation, and a review comment ready to post on the PR. Pick or edit what the agent sends back, choose the step, and press Send.
The schema for write_review — All four are required. Nothing else gets into the record.
risk: "low" | "medium" | "high" key_changes: string[] recommendation: "approve" | "request_changes" review_comment: string
The page sends at most 16 KB and 32 levels of nesting. These limits are the page's own, not Realm's.
Choose a submission.
The agent skips the review and calls the step that posts to GitHub.
Agent calls
{
"tool": "execute_step",
"command": "post_approval",
"params": {}
}Realm refuses
Step 'post_approval' is not eligible in the current run state.
{
"status": "blocked",
"errors": [],
"agent_action": "resolve_precondition",
"context_hint": "Step 'post_approval' is not eligible in the current run state.",
"run_phase": "running",
"blocked_reason": {
"eligible_steps": [
"write_review"
]
}
}After every refusal the run still read completed_steps: ["fetch_pr"], phase running. No step completed. Refusals of a submission's content are counted toward the limit of six; a step sent out of order is not.
The agent submits a review with no risk level.
Agent calls
{
"tool": "execute_step",
"command": "write_review",
"params": {
"key_changes": [
"deliverWebhook now retries a failed POST in a loop instead of…",
"New MAX_RETRIES constant (5) in webhooks/config.ts",
"One new test: a single failure followed by a success"
],
"recommendation": "request_changes",
"review_comment": "Changes requested. The retry loop swallows the final failure:…"
}
}Realm refuses
must have required property 'risk'
{
"status": "error",
"error_code": "VALIDATION_INPUT_SCHEMA",
"errors": [
"Invalid input for step 'write_review'"
],
"agent_action": "provide_input",
"context_hint": "Error during 'write_review'. Run phase: 'running'.",
"run_phase": "running",
"error_details": {
"errors": [
{
"instancePath": "",
"keyword": "required",
"params": {
"missingProperty": "risk"
},
"message": "must have required property 'risk'"
}
]
}
}After every refusal the run still read completed_steps: ["fetch_pr"], phase running. No step completed. Refusals of a submission's content are counted toward the limit of six; a step sent out of order is not.
The agent recommends "merge", which is not one of the two allowed outcomes.
Agent calls
{
"tool": "execute_step",
"command": "write_review",
"params": {
"risk": "high",
"key_changes": [
"deliverWebhook now retries a failed POST in a loop instead of…",
"New MAX_RETRIES constant (5) in webhooks/config.ts",
"One new test: a single failure followed by a success"
],
"recommendation": "merge",
"review_comment": "Changes requested. The retry loop swallows the final failure:…"
}
}Realm refuses
must be equal to one of the allowed values
{
"status": "error",
"error_code": "VALIDATION_INPUT_SCHEMA",
"errors": [
"Invalid input for step 'write_review'"
],
"agent_action": "provide_input",
"context_hint": "Error during 'write_review'. Run phase: 'running'.",
"run_phase": "running",
"error_details": {
"errors": [
{
"instancePath": "/recommendation",
"keyword": "enum",
"params": {
"allowedValues": [
"approve",
"request_changes"
]
},
"message": "must be equal to one of the allowed values"
}
]
}
}After every refusal the run still read completed_steps: ["fetch_pr"], phase running. No step completed. Refusals of a submission's content are counted toward the limit of six; a step sent out of order is not.
The agent adds approved_by, a field the step never asked for.
Agent calls
{
"tool": "execute_step",
"command": "write_review",
"params": {
"risk": "high",
"key_changes": [
"deliverWebhook now retries a failed POST in a loop instead of…",
"New MAX_RETRIES constant (5) in webhooks/config.ts",
"One new test: a single failure followed by a success"
],
"recommendation": "request_changes",
"review_comment": "Changes requested. The retry loop swallows the final failure:…",
"approved_by": "the agent"
}
}Realm refuses
must NOT have additional properties
{
"status": "error",
"error_code": "VALIDATION_INPUT_SCHEMA",
"errors": [
"Invalid input for step 'write_review'"
],
"agent_action": "provide_input",
"context_hint": "Error during 'write_review'. Run phase: 'running'.",
"run_phase": "running",
"error_details": {
"errors": [
{
"instancePath": "",
"keyword": "additionalProperties",
"params": {
"additionalProperty": "approved_by"
},
"message": "must NOT have additional properties"
}
]
}
}After every refusal the run still read completed_steps: ["fetch_pr"], phase running. No step completed. Refusals of a submission's content are counted toward the limit of six; a step sent out of order is not.
All four fields, the right types, nothing extra.
Agent calls
{
"tool": "execute_step",
"command": "write_review",
"params": {
"risk": "high",
"key_changes": [
"deliverWebhook now retries a failed POST in a loop instead of…",
"New MAX_RETRIES constant (5) in webhooks/config.ts",
"One new test: a single failure followed by a success"
],
"recommendation": "request_changes",
"review_comment": "Changes requested. The retry loop swallows the final failure:…"
}
}Realm accepts it
{
"status": "confirm_required",
"context_hint": "Run is paused at gate '5494540b-535e-43d4-8162-5ad03bd1a94e'.…",
"run_phase": "gate_waiting"
}The step is complete, and the run stops at the human gate. Go to the next stage.
Press Send to submit it.
This is what the reviewer was shown. The run is in phase gate_waiting and neither posting step is available to the agent.
Gate message
PR #42 — acme/payments-api Risk: HIGH AI recommendation: request_changes Key changes: • deliverWebhook now retries a failed POST in a loop instead of failing on the first error • New MAX_RETRIES constant (5) in webhooks/config.ts • One new test: a single failure followed by a success Review comment to post: Changes requested. The retry loop swallows the final failure: after the last attempt throws, the loop ends and deliverWebhook returns normally, so a charge webhook that never got through is reported as delivered. Rethrow the last error (or return a failed result) once attempts run out. Two smaller points: `attempt <= MAX_RETRIES` makes six attempts, not five, and the fixed 1 s sleep retries every error, including 4xx responses that will never succeed — back off exponentially and retry only timeouts and 5xx. Please add a test for the all-attempts-fail case; the current test covers only fail-once-then-succeed.
You are the reviewer
Or let the agent try to skip the gate
Agent calls
{
"tool": "execute_step",
"command": "post_approval",
"params": {}
}Realm refuses
Step 'post_approval' is not eligible in the current run state.
{
"status": "blocked",
"errors": [],
"agent_action": "resolve_precondition",
"context_hint": "Step 'post_approval' is not eligible in the current run state.",
"run_phase": "gate_waiting",
"blocked_reason": {
"eligible_steps": []
}
}Agent calls
{
"tool": "execute_step",
"command": "post_changes_request",
"params": {}
}Realm refuses
Step 'post_changes_request' is not eligible in the current run state.
{
"status": "blocked",
"errors": [],
"agent_action": "resolve_precondition",
"context_hint": "Step 'post_changes_request' is not eligible in the current ru…",
"run_phase": "gate_waiting",
"blocked_reason": {
"eligible_steps": []
}
}Gate message
You are the reviewer
Or let the agent try to skip the gate
Answer the gate first. This stage opens once the gate is answered.
request_changes. Realm closes the other branch.On this release the agent still sends the chosen step itself.
The reviewer answers
$ realm run respond <run-id> --gate <gate-id> --choice request_changes Responded: 76a4e5a6-3374-4a76-b186-0e8692879886 | choice 'request_changes' | new state 'running'
The agent tries the branch that was not chosen
{
"tool": "execute_step",
"command": "post_approval",
"params": {}
}Realm refuses
Step 'post_approval' is not eligible in the current run state.
{
"status": "blocked",
"errors": [],
"agent_action": "resolve_precondition",
"context_hint": "Step 'post_approval' is not eligible in the current run state.",
"run_phase": "running",
"blocked_reason": {
"eligible_steps": [
"post_changes_request"
]
}
}Ran: post_changes_request
{
"status": "ok",
"errors": [],
"context_hint": "Run completed (phase: 'completed'). Call get_run_state with r…",
"run_phase": "completed"
}Skipped, with the reason on the record
post_approval: when_false: confirm_review.choice == 'approve' [lhs → "request_changes"]
approve. Realm closes the other branch.On this release the agent still sends the chosen step itself.
The reviewer answers
$ realm run respond <run-id> --gate <gate-id> --choice approve Responded: e164cc71-b9ec-4985-8ec0-281a52da1da7 | choice 'approve' | new state 'running'
Ran: post_approval
{
"status": "ok",
"errors": [],
"context_hint": "Run completed (phase: 'completed'). Call get_run_state with r…",
"run_phase": "completed"
}Skipped, with the reason on the record
post_changes_request: when_false: confirm_review.choice == 'request_changes' [lhs → "approve"]
Answer the gate first. There is no finished record until the gate is answered.
One entry for each step that ran and for the gate answer, each with a hash. Refused calls leave no entry; counted refusals are noted on the step's entry.
Phase completed · sealed by complete at post_changes_request · run 76a4e5a6
fetch_pr success625650d8cdad…write_review accepted after 3 counted refusalse6cb3f2bd8a5…confirm_review gate openede6cb3f2bd8a5…confirm_review human chose request_17e0025f9bd2…post_changes_request successa7297b7f232b…post_approval skippedno evidenceRead it back any time with realm run inspect <run-id>, or take the whole record with realm run export.
One entry for each step that ran and for the gate answer, each with a hash. Refused calls leave no entry; counted refusals are noted on the step's entry.
Phase completed · sealed by complete at post_approval · run e164cc71
fetch_pr success625650d8cdad…write_review successe6cb3f2bd8a5…confirm_review gate openede6cb3f2bd8a5…confirm_review human chose approve12b00114fef2…post_approval successa7297b7f232b…post_changes_request skippedno evidenceRead it back any time with realm run inspect <run-id>, or take the whole record with realm run export.
Recorded on 2026-10-08 from two real runs of examples/08-pr-review on Realm 0.46.0, driven over MCP; the reviewer answered with the CLI. The two runs differ only in the reviewer's answer; the refusals come from the first. GitHub was a local stand-in server, so nothing was posted. Long text is shortened with “…”.
To run in your browser, a few parts that need a server are replaced. Realm's own decisions — what it accepts, refuses and records — are unchanged.
Try it
# install the CLI and the model SDK it calls (Anthropic here)
npm install -g @sensigo/realm-cli @anthropic-ai/sdk
# create a starter workflow, then let a model drive it
# (needs ANTHROPIC_API_KEY in your environment)
realm workflow init my-workflow
realm agent --workflow ./my-workflow --provider anthropic --model claude-sonnet-5-5Runs locally. Apache‑2.0, published on npm; evidence is written to your own machine.