# Automations

> Build versioned agent jobs that run on schedules, connector events, durable outputs from other automations, or explicit requests.

An **automation** is a versioned, repeatable job owned by an agent. It can wake on a schedule, respond to a connector event, run from a declared event output produced by another automation, or start on demand.

When it runs, Lobu gives the agent a bounded view of durable data. The agent follows the automation's task prompt and pinned skills, produces a validated result, and can then update shared memory, notify someone, request approval, reply to the source, or run a typed reaction.

Use an automation for work that should continue without someone starting a chat each time.

## How an automation runs

For a window-based automation, the loop is:

1. **Activate** — a schedule, an event from a connector or the workspace, or a manual request starts a run.
2. **Read** — Lobu computes the pending window and returns its content, named sources, bound entities, output schema, and prior feedback.
3. **Decide** — the owning agent follows the automation's prompt and version-pinned skills.
4. **Complete** — the agent submits structured output against the window token. Lobu validates it and advances the window.
5. **Promote or react** — Lobu can merge extracted rows into entities, send configured notifications, and run an optional TypeScript reaction.

The automation defines the standing job. The agent supplies judgment inside the run; Lobu supplies activation, durable context, validation, policy, and history around it.

## Activation modes

- **Schedule** — a cron trigger starts the full window-analysis flow. Scheduled runs coalesce while busy and skip unchanged data by default.
- **Event turn** — a normalized connector event becomes the agent's immediate input. The result stays in Lobu or replies through the source connector, depending on the trigger.
- **Event window** — a matching connector event starts the full read, analyze, and complete-window flow.
- **Workspace-sourced event** — a newly persisted declared event output from another automation starts a turn or window. Window execution and coalescing are the defaults.
- **Manual** — an automation with no triggers runs on demand using its stored prompt and skills.

Triggers answer *when to run*. Sources answer *what additional durable data the run can read*. A source never becomes a subscription merely because its query returns a new row. Entity bindings are a scope boundary: for a workspace-sourced event, an automation attached to entity instances only activates when the output event is linked to at least one of those same entities. An unattached automation is workspace-wide.

In the UI, choose **Event**, then select **Current workspace** or an installed connection in **Source**. New Event triggers default to the current workspace. The searchable **Events** field can subscribe one trigger to several catalog events when they share the same source, filters, and run options.

For **Current workspace**, the Events field combines event kinds declared by the organization's entity types, including Lobu's built-in kinds. Declaring an event output does not create a new catalog kind by itself. The optional **Entity type filter** under **Trigger options** narrows the subscription to outputs linked to at least one entity of that type—it does not select one entity instance. Only newly persisted declared Automation outputs can activate the subscription; ordinary saved events and historical rows remain data. Workspace event subscriptions do not cross organization boundaries.

## Three concrete examples

### Reconcile accounts every morning

A scheduled finance automation wakes at 06:00 on weekdays. It reads new ledger and bank events, identifies mismatches, and promotes each exception into a keyed `variance` entity. A reaction sends the high-risk exceptions to the finance channel and leaves the rest in the daily report.

This is a **schedule + window + entity promotion + reaction** automation.

### Triage an incoming issue immediately

A GitHub connector emits an `issue.opened` event. An event-turn automation gives that normalized event directly to the support agent. The agent classifies the issue, checks its skills for the triage policy, and replies to the source when the trigger allows it.

This is an **event turn**: one message comes in, one agent response goes out. It does not run the extraction-window flow.

### Build a customer-risk picture over time

A matching CRM or support event starts an event-window automation. The automation reads all new signals in the pending window plus account context, extracts risk evidence, and merges the result into the existing customer entity. Its reaction can request approval before opening an escalation or contacting the account owner.

This is an **event window + durable context + shared memory** automation. Many input events contribute to one evolving record.

### Hand a durable result to a specialist

A scheduled risk detector persists an `observation` event with `metadata.namespace = "account-risk"`. A second automation subscribes to that declared output, reads the exact observation plus its own account sources, and investigates the risk. Several specialist automations can subscribe independently.

This is a **workspace-sourced event + durable pipeline**. The append-only event is the audit boundary between two versioned jobs; no copied payload or hidden mutable workflow state is required.

## What you define

An automation brings together six concerns:

- **Owner** — exactly one agent supplies the workspace, providers, tools, skills, and guardrails.
- **Activation** — schedule, an `event` whose source is a connector or the workspace, or no trigger for manual runs. Attached entity instances also scope workspace-event activation.
- **Instructions** — a literal task `prompt`, reusable `skills`, or both. Lobu does not template data into the prompt. Skills are pinned to the automation version when you apply the config.
- **Context** — named, read-only SQL `sources` plus any entities attached to the Automation. The same entity attachment scopes every event read, including authored `sources`.
- **Output** — a free-form summary, a reaction-owned input schema, or declared entity and event outputs.
- **Effects** — notifications, reply-to-source delivery, and an optional typed `reaction` that runs after a window completes.

Execution settings, cooldowns, tags, and approval policy control how that definition behaves operationally.

## Define one

Automations live in `lobu.config.ts`, declared at the project level with `defineAutomation`:

```ts

const reconciliationMonitor = defineAutomation({
  agent: finance,
  slug: "reconciliation-monitor",
  name: "Reconciliation monitor",
  triggers: [every("0 6 * * 1-5", { timezone: "Europe/London" })],
  skills: ["reconciliation-policy"],
  prompt:
    "Find new reconciliation exceptions and reporting risks. Lead with items that need human review.",
  notification: { priority: "high", channel: "both" },
  minCooldownSeconds: 3600,
  tags: ["finance", "reconciliation", "daily"],
});
```

`every("…")` is shorthand for the raw `{ kind: "schedule", cron: "…" }` trigger.
The config API also ships `on(connectorKey, eventType, opts?)` for connector
events and `context("…")` for reference-only SQL sources — see the
[`lobu.config.ts` reference](/reference/lobu-config/#trigger-and-source-shorthands).

The prompt is plain text delivered verbatim. Reusable procedure belongs in a [skill](/getting-started/skills/), so several automations can share it without copying a long playbook into every prompt.

`lobu apply` diffs the declaration against the organization's current state and publishes a new version when version-owned fields change.

## Read durable context with sources

An automation with no explicit sources receives Lobu's default event source for its pending window. The default excludes internal configuration changes and tool-audit rows.

Add named SQL sources when the job needs a narrower dataset or additional context:

```ts
const gmailTxAutomation = defineAutomation({
  agent: personalFinance,
  slug: "gmail-tax-events",
  name: "Gmail financial-event extractor",
  triggers: [every("*/30 * * * *")],
  sources: {
    gmail_messages:
      "SELECT id, title, payload_text, occurred_at FROM events WHERE connector_key = 'google.gmail' ORDER BY occurred_at DESC",
  },
  prompt:
    "Review the Gmail messages in this window. Skip noise and extract transactions, disposals, dividends, and tax documents.",
});
```

The query result arrives separately as `sources.gmail_messages` in the knowledge payload; it is not interpolated into the prompt. Queries over events participate in windowing. A `context("…")` source supplies reasoning context without adding its rows to the window's event set (the raw `{ query, context: true }` form works too).

Sources are read-only views. A run creates new events or updates entities through Lobu's SDK rather than mutating its input rows.

## Chain automations through durable outputs

Use the shared `event` trigger with `source: "workspace"` when the semantic result of one automation should activate another immediately. Connector events use the same primitive with `source: "connector"`; older connector triggers may omit it, but Lobu writes the explicit source when normalizing the definition.

The event type must already exist in the workspace event-kind catalog. `observation` below is a built-in kind. Declare a custom kind with an entity type's `eventKinds` in `lobu.config.ts`; that makes it subscribable. To produce the custom kind, attach the producer Automation to at least one entity of a type that declares it. The output declaration chooses a kind but does not register one.

```ts
const detectRisk = defineAutomation({
  agent: accountAgent,
  slug: "detect-risk",
  prompt: "Find material risks and emit observations in the account-risk namespace.",
  triggers: [every("0 * * * *")],
  outputs: {
    risks: { event: "observation" },
  },
});

const investigateRisk = defineAutomation({
  agent: accountAgent,
  slug: "investigate-risk",
  prompt: "Investigate the exact risk observation and recommend the next action.",
  triggers: [
    {
      kind: "event",
      source: "workspace",
      event_types: ["observation"],
      match: { namespace: "account-risk" },
      execution: "window",
      active_run: "coalesce",
    },
  ],
});
```

Only newly persisted **declared event outputs** activate this trigger. Ordinary memory saves, connector ingestion, and historical events remain data. This explicit producer boundary prevents every knowledge write from becoming a workflow command.

The handoff carries durable event pointers, not a copied payload. Lobu commits the output event and activation task together and makes up to five activation attempts. Exact inputs are resolved under the consumer Automation's authorized run context and effective read policy; for headless runs, the Automation's durable creator identity determines connection visibility. A coalesced run accepts up to 25 event pointers and 25 root events; exceeding either bound creates another durable run.

The bounds are explicit delivery semantics, not deferred work. After duplicate delivery and pending-run coalescing have been ruled out, a subscriber inside `minCooldownSeconds` skips the new activation without rescheduling it. At most the first 32 matching Automations, ordered by Automation ID, are considered for activation; cooldown can reduce the number queued. Later matches are skipped and the limit is logged. The root producer output is depth one, and depth eight queues nothing further. An Automation cannot re-enter its own causal path. Coalescing splits before its inherited causal set would exceed 256 distinct Automations; once a path already contains 256, no further downstream Automations are queued. If an output is superseded before its activation task runs, Lobu skips the stale output without activating subscribers or consuming retry attempts.

This composes well for sequential enrichment, exact-metadata routing, and bounded fan-out. It is not a hidden general workflow engine: branch joins, correlated approval waits, per-instance deadlines and escalation, compensation, in-flight version migration, and end-to-end exactly-once external effects still need first-class workflow-instance state.

## Choose an output contract

Window automations compose their declared output contracts:

- **Entity output** — a named entity array derives its schema from the target entity type and promotes keyed rows into shared memory.
- **Declared event output** — a named `{ event: "semantic_type" }` array persists standard append-only event drafts and can activate downstream automations subscribed to workspace-sourced events.
- **Reaction input** — a reaction can export an `input` schema. It can add reaction-owned fields or refine a declared output with the same name.
- **Summary fallback** — only when no declared output or reaction schema exists, the worker falls back to a free-form `{ summary }` result.

One automation can declare entity and event outputs together; they are properties of one composed completion schema, not mutually exclusive tiers.

Entity promotion needs both a destination and a stable identity:

```ts
outputs: {
  transactions: {
    entity: transactionType,   // a defineEntityType handle or slug
    key: ["provider_id"],      // one to four fields forming the identity tuple
    name: ["description"],     // optional: fields for the readable entity name
  },
}
```

The entity type owns the metadata schema. The `key` fields identify the same record across windows — use durable source IDs, not editable labels. `outputs` may also be an event output (`{ event: "semantic_type" }`) that assigns a type to standard event drafts. See [Memory](/getting-started/memory/) for entity types, supersession, and shared state.

## Act with reactions and guardrails

A [reaction](/guides/author-a-connector/#reactions) is a sibling TypeScript module that runs in an isolated V8 isolate after a window completes. It receives validated extracted data, the automation and window metadata, and a scoped Lobu client.

Use a reaction for deterministic effects such as sending a notification, executing a connector operation, updating an entity, or writing a derived event. Lobu evaluates those writes under the owning agent's autonomous policy; guarded actions can pause for approval.

Runs and reaction calls are recorded for observability. If another automation should consume a semantic result immediately, declare it as an event output and subscribe with `kind: "event", source: "workspace"`. An external side effect does not become an activation event automatically.

Events are append-only. Corrections create a successor that supersedes the old row, preserving history while keeping the current view clean.

## When to use an automation

Reach for an automation when the work:

- should run repeatedly or in response to durable events;
- requires agent judgment rather than only fixed control flow;
- needs bounded context, shared memory, or structured extraction;
- may take governed action without waiting for someone to open chat.

Use something simpler when that better matches the job:

- **A one-off answer or collaborative task** — chat with the agent.
- **Pull data from or act on an external system** — use a connector.
- **A fully deterministic transformation** — use ordinary typed code.
- **A durable judgment-and-action loop** — use an automation.

## Familiar mental models

### If you know ETL and data warehouses

The closest mapping is:

- **Connectors** are ingestion and CDC: they extract changes from operational systems and load normalized events.
- **The append-only event log** is the raw landing zone.
- **An automation window** is an incremental transform over the new slice of data.
- **Entity promotion** materializes modeled, keyed state that later runs can query.
- **A reaction** is the operational path back out: reverse ETL, a notification, an API write, or another durable event.

The important difference from an ordinary scheduled model is that an automation can make a judgment and take governed action. That makes replay, identity, approvals, and audit history part of correctness—not just orchestration details. [The Modern Data Stack for Agents](/blog/the-modern-data-stack-for-agents/) develops the warehouse analogy and where it stops transferring.

### If you know the actor model

The agent is the actor. Connector-sourced events and workspace-sourced declared outputs are messages from different trust boundaries. They share one public event primitive while retaining explicit provenance and separate internal delivery paths. An automation is a versioned rule for which messages wake that actor, what durable context it reads, and what it may emit in response.

Event turns look like ordinary message handling. Window automations look like durable aggregators: related messages accumulate, the agent processes a bounded batch, state advances, and new messages or effects may follow. The analogy is useful because it emphasizes encapsulation, asynchronous messages, sequential state updates, and supervision rather than shared mutable context.

Gordon Brander's [Agents are actors](https://newsletter.squishy.computer/p/agents-are-actors) is a useful introduction to that framing and to actor patterns such as request-response, supervision, scatter-gather, worker pools, and aggregation.

## Next

- [Skills](/getting-started/skills/) — reusable procedures pinned into automation versions
- [Memory](/getting-started/memory/) — events, entity schemas, and shared state
- [Reactions](/guides/author-a-connector/#reactions) — typed code that runs after an automation extracts
- [Connectors](/guides/author-a-connector/) — feeds and operations for external systems
- [Concepts](/getting-started/architecture/) — runs, events, entities, and automations in the canonical model
- [The Modern Data Stack for Agents](/blog/the-modern-data-stack-for-agents/) — the ETL and warehouse analogy in depth
- [Agents are actors](https://newsletter.squishy.computer/p/agents-are-actors) — an actor-model lens for multi-agent systems
