The engine owns the graph and the record. An LLM only answers agent steps. The same engine sits behind all three ways of driving a run: MCP, realm agent, and realm listen.
Overview
One engine, three front doors
Every path ends at the same engine call. The dashed arrow is the one hand-off between paths: realm listen starts a run, then a separate realm agent process drives it.
Step kinds
The four step kinds
autoThe engine runs a handler or service adapter. No LLM.
agentThe driver supplies the output: an LLM, or you over MCP.
guardThe engine checks a condition inline and continues or aborts.
finalizerCleanup the engine runs after the run ends.
A gate is not a fifth kind: a step can carry a gate, and the run then parks until a person answers.
After every result, the engine validates and records it, then runs every auto and guard step it can reach. It stops at the next agent step, at a gate, or at the end. After a person answers a gate, the steps the engine owes are named in next_actions as one call, advance_run (or realm run advance from a shell). Step order comes from the definition (depends_on, when, trigger_rule), never from the LLM.
Path 1 · MCP
The external agent drives
Realm makes no model call. The client's own model does the agent steps and follows the instructions the engine returns in each reply.
Path 2 · realm agent
Realm drives and calls the model API
The loop lives in packages/cli/src/agent/run-agent.ts. It uses the same engine call an MCP client uses.
Steps run one at a time today. Parallel branches are recorded correctly, but not called at the same time.
Path 3 · realm listen
A webhook front door that starts runs
realm listen does not drive anything itself. It accepts a request, creates the run and hands it to realm agent. Expired gates are settled only if it was started with --sweep-expired-gates.
The busy limit (--max-concurrent, default 20) counts requests being handled, not runs being driven. The spawned drivers are detached, so nothing currently caps how many run at once on a machine.