Docs · Autopilot
The gateway’s database
Status: Live on hangar-dev since the R-4 cutover (2026-11-03): gateway-v0.4.0 writes this database and calls
clearance v0.29.0’s core; autopilot-api serves the runtime reads from it (GATEWAY_API_DSN, gateway_* only).
The gateway keeps runtime state, not evidence, in a Postgres database of its own (GATEWAY_DSN). The evidence chain,
the deployment gate’s changes and the core’s definitions stay in clearance-pg, which only the Clearance core (the one
writer) and clearance-api (read-only) open. The gateway has no DSN for it.
| Table | What it holds | Was (clearance-pg) |
|---|---|---|
gateway_sessions |
every session: grant, budgets used and reserved, breaker, run, input, outcome | clearance_sessions |
gateway_human_requests |
human.request questions and their answers |
clearance_human_requests |
gateway_results |
a finished run’s answer, 30 days | clearance_results |
gateway_artifacts |
artifact.put text per task, 7 days |
clearance_artifacts |
gateway_events |
what the trigger bridge received and did, 30 days | clearance_events |
gateway_holds |
people’s holds on an agent’s triggers | clearance_holds |
gateway_definitions |
the cluster-pinned definition set (the gateway’s copy) | clearance_definitions (the core keeps its own) |
gateway_outbox |
records and outcomes owed to the core, in order (engine/outbox.py) |
new |
gateway_trigger_launches |
the trigger ledger: one row per (agent, trigger key), claimed before a triggered session opens, with the decision; 30 days (trigger-durability.md) | new (A-5) |
gateway_event_cursors |
the event bridge’s cursor (newest event id handled) and the ledger’s floor, per source | new (A-5) |
Same columns as before (src/autopilot/engine/pg.py SCHEMA), created at start if missing. One writer: the gateway
takes advisory lock apg0 and a second one stops at start.
The outbox
An entry is a record (POST /v1/records) or an outcome (POST /v1/decisions/{id}/outcome), with why it waits:
outage (a T0 read allowed during a core outage, posted as a coverage-unavailable tool.call), core-unavailable
(the core could not take it when it was written), held (a kind the core does not accept yet) and refused (the
core answered and refused it; kept with the error, never retried). Since clearance v0.28.0 the core takes every kind
the gateway writes (run.finish, session.end, trigger.release, the outage tool.call among them), so nothing new
is held; a row an older gateway held is posted by the next drain. The definition set is not in the outbox: the
gateway publishes it whole to the core (PUT /v1/definitions) and makes the publish again on the next poll if the
core did not take it. At start the gateway deletes pending held definition.* rows an older gateway left (the
core writes those records itself and refuses them from a provider). States: pending, done (with the record’s seq),
refused. The expiry sweep posts pending entries oldest first and stops at the first the core cannot take; a new
record waits behind them.
To see what waits:
SELECT id, op, kind, reason, state, created_at, error FROM gateway_outbox WHERE state <> 'done' ORDER BY id;
The cutover copy (R-4)
Used once, at the hangar-dev cutover on 2026-11-03: it copied 24 sessions, 2 human requests, 145 events, 2 results
and 1 definition. Kept as the record of the move; the steps below are what was run. The read API no longer reads the
clearance_* runtime tables (its GATEWAY_API_TABLES=clearance option is retired), so they can be dropped.
tools/cutover/copy_runtime_tables.py copies the seven runtime tables from clearance-pg into the gateway’s database,
row for row: clearance_<name> to gateway_<name>. It deletes and changes nothing in clearance-pg.
-
Scale the running (Python) gateway to 0, so nothing writes the runtime tables.
-
Create the gateway’s database and role (apron’s template), then:
python tools/cutover/copy_runtime_tables.py --from "$CLEARANCE_DSN" --to "$GATEWAY_DSN" --dry-run python tools/cutover/copy_runtime_tables.py --from "$CLEARANCE_DSN" --to "$GATEWAY_DSN"It reads clearance-pg in one read-only snapshot, writes the target in one transaction holding the gateway’s writer lock (it refuses to run beside a gateway), refuses a target table that already has rows (
--replaceoverwrites), checks every row count, and prints counts only. -
Start the new gateway with
GATEWAY_DSNandCLEARANCE_CORE_URL. -
Once the run is verified, the
clearance_*runtime tables in clearance-pg can be dropped (not by this script).
Tested on scratch databases (tests/test_gateway_pg.py, CI’s postgres service): rows as clearance v0.26.1 wrote
them, a dry run that writes nothing, an exact copy of every table, the evidence left behind, both refusals, and the
gateway opening the copied tables.