> ## Documentation Index
> Fetch the complete documentation index at: https://docs.automate.ax/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Automate.ax automations are TypeScript programs.
> Use Bun for installation and command examples.
> Use Automate.ax for the product name and automate.ax for the package, CLI, and domain.
> Treat documented public APIs as current; do not invent transitional or deprecated names.

# Query runs

> Search Automate.ax runs, failures, actions, logs, and outputs with local SQL.

The Automate.ax CLI queries a private local PostgreSQL copy of the runs you can access. Use ranked full-text search to find likely matches, then use SQL for exact filtering and joins across failures, fan-out, events, signal coordination, actions, logs, and explicit automation outputs.

## Inspect a run in the dashboard

Open a recent execution to follow its causal timeline from left to right. Trigger occurrences and action invocations appear in execution lanes; connections show branches and joins. Long gaps are compressed and labeled with their actual duration. Select an operation output to preview and navigate its value.

A run appears **Waiting** while an internal delay has not been delivered. The initiating context may already have settled. The delayed delivery starts an independent root; the dashboard connects it to the initiating occurrence only after correlation creates a shared child.

[`markSignificant`](/reference/runtime/signal-operators#marksignificant) highlights the causally connected run when a chosen signal emits. Recent runs then hides insignificant runs by default. Turn on **Show insignificant runs** to see the complete history. Automations without a significance marker remain unfiltered.

## Query runs locally

For a one-time investigation, log in and synchronize the last seven days for the nearby project:

```bash theme={null}
bunx automate.ax login
bunx automate.ax data sync
```

The command always synchronizes one project. Without an explicit scope, it uses a nearby project config or asks you to choose.

Pass a project ID or name to select it directly. Pass an organization ID, name, or slug to limit project selection to that organization. You can also select a different history window:

```bash theme={null}
bunx automate.ax data sync --project proj_01... --since 30d
bunx automate.ax data sync --org org_01... --since 2026-08-01T00:00:00Z
bunx automate.ax data sync --project proj_01... --since all
```

The default seven-day window keeps the first download bounded. `--since all` is an explicit request for every retained execution in the selected scope. The server applies your account's project access before synchronization. Local options can narrow that access but can't widen it.

A bounded window also includes the older ancestors of matching contexts. This keeps causal lineage and fan-out traversal intact without synchronizing unrelated old runs.

While testing an automation, keep `data follow` running in a separate terminal:

```bash theme={null}
bunx automate.ax data follow --project proj_01... --since 1h
```

Wait for `Following execution data` before invoking the automation. Press <kbd>Ctrl</kbd>+<kbd>C</kbd> to stop after the investigation. The local database remains available for later queries.

Local execution-data commands coordinate database access. Queries, searches, status checks, exports, synchronization, and cleanup wait for an in-progress local write, while `data follow` releases the database between updates. Other terminals and agents can run those commands concurrently against the same local dataset.

Electric makes each synchronized resource current at its own shape offset. It doesn't provide one transaction boundary across all shapes. The manifest reports `consistency: "eventual"`, and `data status --json` reports the offset, row count, and synchronization time for every resource. A local update replaces one resource and its freshness marker atomically. When a query joins resources, compare their markers if the newest cross-resource changes must already agree.

## Wait for a background HTTP run

For work that fits within the HTTP response window, pass the trigger's `requestId` to `respondToHttpRequest` so the invocation itself returns the result.

Otherwise, an HTTP trigger returns a `202` receipt containing an `eventId`. Capture that exact event instead of guessing how long the run needs or selecting the newest timestamp:

```bash theme={null}
response=$(curl --fail-with-body --silent --show-error "$TRIGGER_URL")
event_id=$(jq -er '.eventId' <<<"$response")
```

One event can start several root contexts when a shared endpoint has several active subscriptions. Fan-out and coordination can add descendant contexts beneath each root. Delayed deliveries and protected callbacks create their own roots, and correlation can create a child beneath both the initiating and delivery roots. `automate.root_contexts` contains only roots. `automate.context_descendants` maps each root to its current descendants, including the root at depth `0`.

In SQL, use *root context* for that stored lifecycle record. In explanations to end users, call the causally connected workflow a *run*. `root_contexts` is query terminology, not product language.

With `data follow` still running, use Bash and `jq` to poll until the event has at least one synchronized context, every current context has either settled or failed, and no internal delay is pending or waiting for its delivery context to synchronize. The query follows delivered delays through their invocation provenance. This example stops after five minutes. Increase the deadline for intentionally long automations.

<Warning>
  Invocation provenance covers internal delays, not external callbacks or other
  provider deliveries. When the expected result depends on one of those roots,
  poll for its terminal action or explicit output instead of treating settled
  current contexts as workflow completion.
</Warning>

```bash theme={null}
poll_deadline=$((SECONDS + 300))
while true; do
  result=$(bunx automate.ax query --json --sql "
    WITH RECURSIVE run_contexts(context_id) AS (
      SELECT DISTINCT context_id
      FROM automate.event_links
      WHERE event_id = '$event_id'

      UNION

      SELECT related.context_id
      FROM run_contexts AS current
      CROSS JOIN LATERAL (
        SELECT descendants.descendant_context_id AS context_id
        FROM automate.context_descendants AS descendants
        WHERE descendants.context_id = current.context_id

        UNION

        SELECT delivery_links.context_id
        FROM automate.actions
        INNER JOIN automate.automation_invocations AS invocations
          ON invocations.idempotency_key = actions.id
        INNER JOIN automate.event_links AS delivery_links
          ON delivery_links.event_id = invocations.event_id
        WHERE actions.context_id = current.context_id
          AND starts_with(invocations.entrypoint, '__\$delay:')
      ) AS related
    )
    SELECT true AS complete
    FROM run_contexts
    INNER JOIN automate.contexts AS contexts
      ON contexts.id = run_contexts.context_id
    HAVING count(*) > 0
      AND bool_and(contexts.status IN ('settled', 'failed'))
      AND NOT EXISTS (
        SELECT 1
        FROM automate.actions
        INNER JOIN automate.automation_invocations AS invocations
          ON invocations.idempotency_key = actions.id
        WHERE actions.context_id IN (SELECT context_id FROM run_contexts)
          AND starts_with(invocations.entrypoint, '__\$delay:')
          AND (
            invocations.processed_at IS NULL
            OR (
              invocations.event_id IS NOT NULL
              AND NOT EXISTS (
                SELECT 1
                FROM automate.event_links
                WHERE event_links.event_id = invocations.event_id
              )
            )
          )
      )
  ") || exit

  if jq -e '.results[0].rows[0].complete == true' <<<"$result" >/dev/null; then
    break
  fi
  if ((SECONDS >= poll_deadline)); then
    echo "Run did not finish within five minutes." >&2
    exit 1
  fi
  sleep 1
done
```

After the query reports no pending internal delay, search for resources that mention the event ID or known failure text. Verify the expected terminal action, output, or external side effect before reporting the run complete:

```bash theme={null}
bunx automate.ax search "$event_id"
bunx automate.ax search '"gateway timeout" -retry'
```

Use this exact join when you need every context and action associated with the request:

```bash theme={null}
bunx automate.ax query --sql "
  WITH roots AS (
    SELECT DISTINCT context_id
    FROM automate.event_links
    WHERE event_id = '$event_id'
  ),
  family AS (
    SELECT
      roots.context_id AS root_context_id,
      descendants.depth,
      descendants.descendant_context_id AS context_id
    FROM roots
    INNER JOIN automate.context_descendants AS descendants
      ON descendants.context_id = roots.context_id
  )
  SELECT
    family.root_context_id,
    family.depth,
    contexts.id AS context_id,
    contexts.status AS context_status,
    contexts.created_at AS context_created_at,
    contexts.settled_at,
    contexts.error AS context_error,
    actions.name AS action_name,
    actions.status AS action_status,
    actions.completed_at AS action_completed_at,
    actions.error AS action_error,
    actions.output AS action_output
  FROM family
  INNER JOIN automate.contexts AS contexts
    ON contexts.id = family.context_id
  LEFT JOIN automate.actions AS actions
    ON actions.context_id = family.context_id
  ORDER BY
    family.root_context_id,
    family.depth,
    contexts.created_at,
    actions.created_at
"
```

## Build a run-history client

The CLI and REST endpoint read the same public execution-data manifest with the same bearer session. Inspect the REST response when building another agent interface:

```bash theme={null}
curl --get https://automate.ax/api/v1/execution-data/manifest \
  --header "Authorization: Bearer $AUTOMATE_SESSION_TOKEN" \
  --data-urlencode "projectId=proj_01..." \
  --data-urlencode "since=2026-08-01T00:00:00Z"
```

The response includes the validated scope, local schema version, eventual-consistency mode, opaque profile ID, and one fixed authenticated Electric URL for every resource. Organization and project filters must exist, be related when supplied together, and be visible to the signed-in user. The Electric URLs require that same user session; they don't accept arbitrary table, column, or predicate parameters.

## Record structured logs

The action runtime exposes the durable `runtime.log()` method:

```ts theme={null}
import { defineAction } from "automate.ax"
import { z } from "zod"

export const processOrder = defineAction("Process order")
  .input(z.object({ orderId: z.string() }))
  .retry({ replaySafety: "unsafe" })
  .handler(async ({ input, runtime }) => {
    await runtime.log({
      fields: { orderId: input.orderId },
      level: "info",
      message: "Charging order",
    })
  })
```

Await `runtime.log()` when persistence must finish before the action continues. Each action attempt can write 1,000 logs. Messages allow 16,384 characters, and field maps allow 64 entries and 64 KiB encoded. Each field key allows 128 characters. Exceeding a limit fails the method. Deterministic per-attempt indexes make repeated delivery idempotent while preserving logs from every retry.

Logs retain their action, context, and retry-attempt provenance. They don't replace or change explicit automation outputs.

## Query runs with SQL

Pass a statement directly or use a SQL file:

```bash theme={null}
bunx automate.ax query --sql "
  SELECT project_name, automation_identity_key, status, created_at
  FROM automate.root_contexts
  WHERE status = 'failed'
  ORDER BY created_at DESC
  LIMIT 20
"

bunx automate.ax query --file investigation.sql --json
```

## Search runs

Search synchronized logs, failures, actions, outputs, event payloads, IDs, providers, projects, and automations without writing SQL:

```bash theme={null}
bunx automate.ax search '"gateway timeout" -retry'
bunx automate.ax search cus_01... --limit 50 --json
```

`automate search` ranks each result and includes its resource, resource ID, context ID, timestamp, and a matching excerpt. Search uses PostgreSQL's `simple` text-search dictionary so provider names, action names, and identifiers aren't stemmed as English prose. Titles and human-authored text receive more weight than structured metadata and IDs. Exact and partial identifier matches receive an additional ranking boost, while `pg_trgm` recovers misspellings and partial terms.

The derived `automate.search_documents` table rebuilds from the synchronized tables during `data sync` and when its local schema changes. `data follow` refreshes only the search resource affected by each high-volume execution update. The table contains no independent server state.

Use the same index directly from SQL when you need custom filtering or joins:

```sql theme={null}
WITH input AS (
  SELECT websearch_to_tsquery('simple', 'gateway timeout -retry') AS query
)
SELECT
  resource,
  resource_id AS id,
  context_id AS context,
  created_at AS timestamp,
  ts_rank_cd(
    ARRAY[0.05, 0.2, 0.5, 1.0]::real[],
    search_vector,
    input.query,
    32
  ) AS rank,
  content
FROM automate.search_documents
CROSS JOIN input
WHERE search_vector @@ input.query
ORDER BY rank DESC, timestamp DESC
LIMIT 20;
```

Combine trigram word similarity with a partial identifier match for typo-tolerant investigations:

```sql theme={null}
SELECT
  resource,
  resource_id AS id,
  context_id AS context,
  created_at AS timestamp,
  greatest(
    word_similarity('gatewy timeout', title),
    word_similarity('gatewy timeout', content)
  ) AS rank,
  content
FROM automate.search_documents
WHERE 'gatewy timeout' <% title
   OR 'gatewy timeout' <% content
   OR identifiers ILIKE '%cus_01...%'
ORDER BY rank DESC, timestamp DESC
LIMIT 20;
```

The full-text vector and trigram text columns use generalized inverted (`GIN`) indexes. Automate.ax uses native cover-density ranking and doesn't load the optional `PGlite` BM25 extension.

Queries can only read synchronized tables and views in the `automate` schema. `query` accepts one or more PostgreSQL `SELECT` statements and `EXPLAIN` for a `SELECT`. It rejects session changes, transactions, modifying common table expressions, row locks, and every data-definition or data-modification statement. Each query also runs as a restricted role inside a read-only transaction.

Inspect the available objects and local status:

```bash theme={null}
bunx automate.ax data schema
bunx automate.ax data schema --sql
bunx automate.ax data status --json
```

## Trace fan-out

`automate.context_ancestors` and `automate.context_descendants` contain the transitive context relationships, including each context itself at depth `0`.

Find every output emitted beneath one context:

```sql theme={null}
SELECT
  descendants.depth,
  outputs.context_id,
  outputs.type,
  outputs.data,
  outputs.created_at
FROM automate.context_descendants AS descendants
INNER JOIN automate.outputs
  ON outputs.context_id = descendants.descendant_context_id
WHERE descendants.context_id = 'actx_01...'
ORDER BY outputs.output_seq;
```

The `automate.root_contexts` view contains contexts without a parent, joined to their automation, project, and organization identity. The `automate.contexts` table contains roots and coordinated or fan-out child contexts.

## Trace delayed and scheduled work

`automate.automation_invocations` records work scheduled by an action. Join `idempotency_key` to `automate.actions.id` to find the scheduling action and its origin context. `run_at` is the requested delivery time. `processed_at` remains `NULL` until delivery processing finishes, and `event_id` identifies the delivered event when delivery creates one.

Find scheduled invocations originating in a context family:

```sql theme={null}
WITH family AS (
  SELECT descendant_context_id AS context_id
  FROM automate.context_descendants
  WHERE context_id = 'actx_01...'
)
SELECT
  invocations.id,
  actions.context_id AS origin_context_id,
  actions.id AS scheduling_action_id,
  invocations.entrypoint,
  invocations.run_at,
  invocations.processed_at,
  invocations.event_id,
  delivery_links.context_id AS delivery_context_id
FROM family
INNER JOIN automate.actions
  ON actions.context_id = family.context_id
INNER JOIN automate.automation_invocations AS invocations
  ON invocations.idempotency_key = actions.id
LEFT JOIN automate.event_links AS delivery_links
  ON delivery_links.event_id = invocations.event_id
ORDER BY invocations.run_at;
```

This provenance doesn't make the delivery context a child of the origin. Delays, programmatic invocations, callbacks, provider events, and replies still arrive as ordinary roots. Traverse `context_parents` only after correlation creates an actual shared child. A pending internal delay can make the origin run appear **Waiting** in the web app. A scheduled programmatic invocation remains independent work.

## Investigate logs

Find recent warnings and errors with their action-attempt provenance:

```sql theme={null}
SELECT
  created_at,
  level,
  message,
  context_id,
  action_invocation_id,
  attempt,
  fields
FROM automate.logs
WHERE level IN ('warn', 'error')
ORDER BY created_at DESC
LIMIT 100;
```

## Execution tables

| Object                            | Contents                                                                                                                   |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `automate.projects`               | Projects in the synchronized scope.                                                                                        |
| `automate.automations`            | Automation identity and enabled state.                                                                                     |
| `automate.automation_invocations` | Scheduled invocation timing, scheduling-action key, processing time, and delivered event.                                  |
| `automate.contexts`               | Root and child context lifecycle.                                                                                          |
| `automate.context_parents`        | Direct context directed acyclic graph edges.                                                                               |
| `automate.event_links`            | Events attached to contexts with hook provenance.                                                                          |
| `automate.events`                 | Event metadata and decoded payload projection.                                                                             |
| `automate.actions`                | Action lifecycle, dependencies, failures, retry policy, replay safety, and output projection.                              |
| `automate.action_attempts`        | Logical attempt number, delivery identity, disposition, failure, and provider-directed retry time.                         |
| `automate.signal_invocations`     | Signal lifecycle, dependency IDs, selected index, failure, and output projection.                                          |
| `automate.signal_coordinators`    | Durable coordinator identity, location, configuration hash, and parent precedence.                                         |
| `automate.signal_partitions`      | Exact coordinator keys, operational state, expiry, and wake timing.                                                        |
| `automate.signal_offers`          | Input lanes, dependency provenance, eligibility, settlement, and outcome status.                                           |
| `automate.signal_decisions`       | Emitted child contexts within coordinator partitions.                                                                      |
| `automate.signal_decision_offers` | Ordered selected-offer membership for each decision.                                                                       |
| `automate.logs`                   | Ordered user-authored logs with action-attempt provenance.                                                                 |
| `automate.outputs`                | Explicit ordered outputs from automation code.                                                                             |
| `automate.search_documents`       | Weighted full-text and trigram search documents that rebuild from synchronized resources.                                  |
| `automate.root_contexts`          | Root contexts joined to automation and project identity.                                                                   |
| `automate.context_ancestors`      | Transitive parent relationships and depth.                                                                                 |
| `automate.context_descendants`    | Transitive child relationships and depth.                                                                                  |
| `automate.signal_outcomes`        | Joined signal, decision, coordinator, and selected-offer results for races, correlations, collects, fan-outs, and funnels. |

## Read non-JSON values

Automate.ax payloads support values that JSON can't represent directly. The `payload`, `output`, `key`, `data`, `value`, and `fields` columns use an extended JSON projection:

* Ordinary objects, arrays, strings, booleans, numbers, and `null` remain ordinary JSON.
* Values such as `bigint`, `Date`, `Map`, `Set`, typed arrays, files, requests, and responses use an object with a `$type` field.
* An ordinary object that already owns `$type` is wrapped as `{ "$type": "object", "value": ... }`, so its original tag can't be confused with the projection format.
* Binary values up to 64 KiB include base64. Larger values include their size, an `omitted` marker, and an `encodedRef` identifying the exact stored value.
* Corresponding `*_encoded` columns retain the exact encoded bytes. These columns hold the local copy of server truth. Automate.ax derives the JSON columns for queries.

Query projected columns with PostgreSQL JSON operators:

```sql theme={null}
SELECT id, payload->>'customerId' AS customer_id
FROM automate.events
WHERE event_type = 'stripe.customer.updated';
```

## Protect local run history

The database can contain sensitive provider data. Automate.ax isolates it by app origin and signed-in profile, creates its directory with owner-only permissions, and deletes every local profile for that app origin when you log out—even when the server session has expired.

Export or clear the local database:

```bash theme={null}
bunx automate.ax data export --output ./execution-data.tar.gz
bunx automate.ax data clear
```

`data export` archives may contain provider payloads, action values, and logs; store them as sensitive data. `data clear --yes` supports non-interactive cleanup.

If a local execution-data command fails unexpectedly, including with a low-level error such as `Aborted()`, clear the derived database and download a fresh copy:

```bash theme={null}
bunx automate.ax data clear --yes
bunx automate.ax data sync
```

Add the `--project`, `--org`, and `--since` options needed to restore your intended scope. Clearing this cache doesn't change production execution data.

When PostgreSQL reports explicit database corruption, `data sync` removes that profile's cache and rebuilds it once automatically. Local read commands remove recognized unusable caches and direct you to synchronize again. The command-line tool preserves other server, authorization, and local filesystem errors.
