Concepts · 05

Event-driven autonomy

The difference between an agent that responds and an agent that operates is who decides when to run. The first waits for a person. The second wakes up when the world changes. This page is how we make the second safe.

The thesis: agents react, they don't poll

Every state change in every Ergon service emits a typed event with a stable schema: {service}.{entity}.{action}. A workflow item moved to a new phase. A document finished processing. A row was updated. A message arrived on a channel. A conversation was created. A grant was revoked. These events are durable, ordered per-aggregate, and published to the platform event bus.

Automations is the platform's reaction substrate. It subscribes to every service's event stream. When an event arrives that matches a rule the organization has configured, Automations dispatches a step graph — a small directed graph of conditions, switches, actions, and waits — that decides what should happen next.

An agent does not poll for things to do. An agent does not need to know about the rest of the company. The agent is the action at the end of a step graph triggered by an event the agent never had to subscribe to. The platform routes the reaction.

The shape of a reaction

Every automation is the same shape:

trigger:  workflows.items.routed             (an event from the workflows service)
condition: item.to_phase == "needs-research"  (a guard so the rule fires only when relevant)
action:    agents.invoke                       (call the Researcher agent)
           input:
             prompt: "Surface authorities for matter {item.id}"
wait:      conversations.message.received      (until the Researcher posts back)
action:    workflows.items.transition          (move the item to "research-ready")

The same shape covers a hundred patterns. When a ticket comes in, classify it and assign. When a contract uploads, extract the parties and stakeholders into a worksheet. When a row in the AR worksheet stays unpaid for 14 days, draft a chasing email and queue it for review. All three are triggers, conditions, actions, waits.

The step graph

An automation's body is a graph of nodes connected by edges. Five node kinds cover everything we have needed:

trigger

An event from any service the automations engine subscribes to. The starting point. Every graph has at least one trigger node.

condition

A boolean expression over the trigger payload (and previous step outputs). Single edge out — runs the next step only if true.

switch

Multi-way branching. Edges are labeled by case; one branch runs based on the value of an expression.

action

A call out to a service: invoke an agent, send a channel message, transition a workflow item, write a worksheet row, upload a document. Actions are typed; the platform knows their inputs and outputs.

wait

Pause until a downstream event arrives or a timeout fires. The run holds in a 'waiting' state, durable across restarts. Resume happens when the matching event comes in or the timeout sweeper fires.

Why a graph and not a script

A script encourages embedding business logic in code that only one engineer understands. A graph forces the rule to be visualized, validated, and edited by anyone who understands the business. The graph is the artifact you show to the head of operations. The script is the artifact you show to the engineer who wrote it.

Guards against runaway loops

The first thing anyone building an event-driven autonomy layer worries about is loops. Agent A reacts to event X by doing thing Y; thing Y emits event Z; an automation reacts to event Z by invoking agent A again. Without guards, this is how an organization wakes up to a five-figure invoice.

Three structural guards stop this:

Self-loop firewall

Lifecycle events emitted by Automations itself (automations.*) are filtered out by the subscription worker before rule matching. An automation cannot trigger itself by changing its own configuration. This is set at the substrate, not configurable per rule.

Per-automation depth limit

Every automation declares a max_trigger_depth (default 3, max 10). When an automation invocation cascades into another automation invocation through events, the depth counter increments. When it hits the limit, the run is marked depth_limited and stops. The cascade can be visualized in the activity log.

Idempotency keys

Action calls (agent invokes, workflow transitions, channel sends) accept an Idempotency-Key header. When set, repeated calls with the same key return the original result instead of running again. Step graphs use this to make retries safe.

Time as a trigger

Not every reaction is to a state change. Some are to a calendar. Automations supports schedule triggers — cron-style or interval-based — that emit a synthetic automations.schedule.fired event at the configured time. From the rest of the engine's point of view, this is just another event with a payload, so the same conditions, switches, actions, and waits apply.

This is how an organization expresses “every Monday at 8am, run the weekly compliance check and post a summary to the partners' channel.” The agent does not need a clock.

Depth limits and trigger budgets

The autonomy boundary of an organization can be tuned by two knobs without touching the agents or the rules:

  • max_trigger_depth on the automation. Lower means a tighter blast radius — an agent action that produces an event will not be allowed to cascade as deeply. Set this aggressively low when first deploying an automation.
  • agent IAM grants. The invoked agent's grants define which tools it can use when the rule runs.

Both are configuration. Neither requires redeploying code. That is what we mean by autonomy expressed as policy.

A mid-sized firm runs a “deadline watcher” automation against its matters. The rule is conceptually simple but chains many primitives:

  1. Trigger. Every morning at 6am, an automations.schedule.fired event arrives.
  2. Action. Query the matters worksheet for all rows where filing_deadline is within 7 days and filed is false.
  3. Switch. For each matching row, branch on days_until_deadline: 7 days → notify lead partner only; 3 days → notify partner and managing partner; 1 day → notify all partners and automatically open a “rush filing” workflow item.
  4. Action — invoke Researcher. For 3-and 1-day branches, invoke the Researcher agent on the matter to surface any new authorities since the last research memo. The agent writes the result to the matter's draft folder.
  5. Action — channel send. Send a templated email to the relevant partners with a link to the matter and the new research memo if any.
  6. Wait. If the partner does not transition the matter to “in progress” within 24 hours, escalate.

What this replaces

A spreadsheet with deadlines, a recurring calendar reminder a paralegal has to act on, an email template someone has to remember to send, and a research task that gets delayed because everyone is busy. The automation runs every morning. The Researcher agent acts under the firm's grants. The partners are in the loop on every escalation. The audit log records all of it. No engineer was involved.

Notice that the agents in this example never knew about the schedule, never knew about the worksheet structure, never had to be told “today is Tuesday, check deadlines”. The automation routed the reaction. The agents did the part of the work that benefits from judgment.

Now go deeper

The concept pages describe the worldview. The rest of the documentation describes how to do these things in practice.