Durable-State Externalization & Multi-Host Operation¶
Concepts: AU-OS.state.unified-durable-state-externalization (unified state store), AU-OS.state.cross-host-daemon-leadership (daemon leadership), AU-OS.state.fleet-supervisory-plane-at (fleet supervisory plane at scale), AU-KG.ingest.cross-host-safe-kg (cross-host task queue), AU-ORCH.session.durable-goal-registry-goals (durable goal registry), ORCH-1.45 (queue-driven agent dispatch — see Queue-Driven Agent Dispatch)
The problem¶
The platform's operational support state historically lived in per-host SQLite files:
| Store | File | Consumer |
|---|---|---|
| Sessions / turns / fleet metadata | agent_terminal_ui.db |
core/sessions.py, gateway/fleet.py |
| Queue delivery + staging rows | XDG-scoped queue stores | knowledge_graph/core/engine_tasks.py, agent dispatch |
Per-host files mean a second host cannot safely participate in those support
planes: delivery claims can double-fire, sessions are invisible across hosts,
and the gateway remains stateful. Durable execution itself is not in this
state-store layer. Its authoritative lifecycle and checkpoint_id live on the
engine-native WorkItem.
One flag: STATE_DB_URI¶
AgentConfig.state_db_uri (alias STATE_DB_URI) selects the backend for the
operational support stores at once:
- Unset (default) — zero-infra per-host SQLite support stores. Tests and development environments need no external state service. Each queue connect recreates its XDG parent directory and schema, so cleanup or lifecycle rotation of the local runtime directory cannot strand the queue on a stale path.
postgresql://…— the support stores move onto one shared Postgres through a singlepsycopg_pool.ConnectionPool(sized bySTATE_DB_POOL_SIZE, default 8). Schema is managed by lightweight idempotentCREATE TABLE IF NOT EXISTSmigrations on first connect.
The seam is agent_utilities/core/state_store.py:
open_state_connection(store, sqlite_path, postgres_ddl)— DB-API-ish connection that translates?placeholders to%sand yields rows addressable by index and name, so the existing SQLite SQL runs unchanged on both backends.state_claim_guard(name)— cross-host critical section (Postgres session advisory lock; no-op under SQLite).ensure_state_schema(store, ddl)— once-per-process idempotent migrations.
What changes by plane¶
- Durable execution — unchanged by
STATE_DB_URI. The native WorkItem owns claim, lease, fencing,checkpoint_id, idempotency, retry, and terminal result. Restart or redelivery resumes from that same engine record; no checkpoint sidecar exists. - Sessions / turns — the selected support backend stores conversational session and turn records plus fleet visibility. Goal definitions and UX projections may be cached here, but their writable lifecycle remains the native WorkItem.
- Queue delivery —
PostgresTaskQueue(AU-KG.ingest.cross-host-safe-kg) claims withUPDATE … WHERE id = (SELECT … FOR UPDATE SKIP LOCKED) RETURNING …, so N hosts drain one delivery queue without double-claims. A claimed-but-unacked item becomes claimable again after the visibility timeout (600 s) — the same at-least-once recovery the SQLite head-until-ack behavior provided. The envelope references the sole WorkItem; queue state never becomes a second work lifecycle.
Daemon leadership (AU-OS.state.cross-host-daemon-leadership)¶
With N hosts, each host's flock only de-duplicates daemons per host.
core/leadership.py adds fleet-wide election: DaemonLeadership(role) holds
a Postgres session advisory lock (pg_try_advisory_lock, stable per-role
key). A crashed leader's lock is released server-side with its connection;
followers re-try on every poll, so fail-over happens within one tick.
Under the SQLite default is_leader() is always true — single-host behavior
unchanged.
Tick classification¶
- Leader-only — the scheduler tick that materializes due recurring work. N copies will enqueue duplicate work.
- Per-host (capacity scaling) — ingestion workers. Native WorkItem claims, leases, fencing, dependency release, and expired-lease recovery are atomic across hosts (AU-KG.ingest.cross-host-safe-kg).
Queue-driven agent dispatch (ORCH-1.45)¶
State externalization made sessions and fleet metadata visible on every host; queue-driven
dispatch makes them executable on every host. An agent turn (goal run /
orchestrator job)
rides the session-keyed agent_turns queue and any host's
agent-dispatch-worker claims it, rehydrates from this shared state store,
runs the existing goal/agent bodies, and writes back — sessions are no
longer pinned to their birth host. The workers' liveness registry is one more
table in this store (dispatch_workers, surfaced by /api/fleet/topology),
and per-session mutual exclusion reuses state_claim_guard
(agent-session:<id> advisory locks). Full design:
Queue-Driven Agent Dispatch.
Fleet supervisory plane at scale (AU-OS.state.fleet-supervisory-plane-at)¶
gateway/fleet.py no longer scans every session row in Python:
/api/fleet/healthaggregates withCOUNT/GROUP BY(status and JSON-derived domain) in SQL on both backends./api/fleet/topologyis paginated (limit/offset) and filterable (status), with totals from SQL aggregates.- pause/kill are desired-state writes: sessions whose goal loop runs in
the local process are cancelled in-process and finalized (fast path);
under externalized state, remote sessions get
pause_requested/kill_requested, which the owning host's goal loop reconciles on its next tick (core.sessions._desired_session_action) intopaused/cancelled.
Truthful fleet supervisory evidence (fleet.health.v1)¶
orchestration/fleet_health.py is the single typed evidence contract shared by
the REST /api/fleet/health and /api/fleet/topology handlers, the
graph_sessions(action="health"|"topology") MCP/REST twin, readiness, the
reconciler, and the autoscaler. Each collection reads the existing live
authorities once: goal rehydration, session/control-store SQL aggregates, and
the dispatch-worker registry. It keeps no health cache and never replaces a
failed read with an empty mapping or zero count.
flowchart LR
REST[REST fleet health/topology] --> C["FleetHealthEvidence<br/>fleet.health.v1"]
MCP[MCP graph_sessions health/topology] --> C
C --> G[goal rehydration authority]
C --> S[session/control-store SQL]
C --> W[dispatch-worker registry]
G --> E[typed status + freshness + bounded diagnostics]
S --> E
W --> E
E --> R[readiness]
E --> A[autoscaling gate]
E --> K[convergence gate]
healthy is the only state that permits readiness, autoscaling, or desired
state convergence. partial means some authoritative reads succeeded while
another failed; degraded carries usable but impaired evidence; and
unavailable means no usable dependency evidence exists. Every dependency
records checked_at, last_success_at, and derived freshness. Diagnostics
are bounded stable labels and exception types only—never exception text,
paths, URLs, credentials, or row values. Partial, degraded, and unavailable
evidence therefore return an explicit not-ready signal; they cannot be
interpreted as zero load, zero workers, or convergence.
Testing¶
No test requires a live Postgres. Unit suites exercise the Postgres logic
against in-memory emulations of exactly the SQL each support backend issues
(tests/unit/test_state_store.py, tests/unit/test_goal_durability.py,
tests/unit/test_fleet_supervisory.py).
A live end-to-end pass (tests/integration/test_state_postgres_live.py) runs
only when STATE_DB_URI is set and reachable. WorkItem checkpoint behavior is
covered against the engine-native transaction surface, independently of the
support-state backend.