Skip to content

Enterprise Parity, Supervisory Plane & Durable Execution

Architecture decisions for running the ecosystem as an SI-first enterprise at scale: a single source of truth across the MCP and REST surfaces, a native supervisory plane, crash-safe durable execution, and cross-agent trace correlation. These records exist so the rationale survives as the surface grows.

flowchart TB
    subgraph clients["Consumers"]
        agent["Agents (MCP tools)"]
        http["HTTP / automation clients"]
        ui["agent-webui Fleet view"]
    end

    subgraph gw["Gateway (single process)"]
        mcp["graph-os MCP<br/>collapsed action tools"]
        rest["REST: collapsed twins<br/>(ACTION_TOOL_ROUTES)"]
        gran["REST: granular typed<br/>ontology/object GETs<br/>(ontology_api.py → OpenAPI)"]
        fleet["/api/fleet/* supervisory<br/>health · topology · pause/kill<br/>approvals · trace · touched"]
        dispatch["goal loop + dispatch worker"]
    end

    exec["_execute_tool()<br/><b>single source of truth</b>"]
    engine[("epistemic-graph engine<br/>the one authority")]
    corr["correlation_id stamped on<br/>FleetEvent + WorkItem records"]

    agent --> mcp
    http --> rest
    http --> gran
    ui --> fleet
    mcp --> exec
    rest --> exec
    gran --> exec
    fleet --> exec
    exec --> engine
    dispatch -->|"native WorkItem<br/>checkpoint · idempotency · fence"| engine
    dispatch --> exec
    fleet -.->|"trace?correlation_id<br/>touched?resource"| engine
    engine --- corr

1. Gateway ⇄ MCP parity over a shared dispatch (CONCEPT:AU-ECO.messaging.native-backend-abstraction)

Context. The GraphOS MCP tools and the gateway's REST routes both dispatch through the same in-process _execute_tool() → IntelligenceGraphEngine singleton (agent_utilities/mcp/kg_server.py). _execute_tool is therefore the de-facto service layer — neither "the MCP" nor "the REST app" owns the logic. Despite a docstring claiming the two never drift, ~17 MCP tools (the ontology_*, object_*, graph_context/feedback/hydrate/sessions/goals, document_process, source_connector surface) had no REST route.

Decision. Keep _execute_tool as the single source of truth. Maintain a canonical ACTION_TOOL_ROUTES map (tool → collapsed REST path) and serve every tool's REST twin from one factory (_make_tool_endpoint). MCP stays collapsed to action-routed tools (context-window friendly); REST exposes the full surface.

Granular typed surface (shipped). Per-entity REST verbs are no longer a follow-on — gateway/ontology_api.py mounts a typed FastAPI APIRouter of resource-style reads over the ontology/object layer (GET /api/ontology/value-types/{name}, /ontology/interfaces/{name}, /ontology/functions/{name}, /objects/{id}, /objects/{id}/history, /objects/{id}/as-of). Because it's an APIRouter, every route appears in /openapi.json with documented params and a response envelope — the schema enforcement the raw-Starlette collapsed routes lacked. Each handler is pure sugar: it builds the action + params and dispatches through the same _execute_tool, so there is no parallel implementation. Collapsed routes remain for agents.

Enforcement. tests/unit/test_gateway_mcp_parity.py asserts bidirectional parity (every MCP tool has a mounted REST twin; no phantom routes), so the collapsed surfaces can never silently drift; the granular router is additive and leaves that contract untouched.

2. Native swarm supervisory plane (CONCEPT:AU-OS.safety.ontological-guardrail)

Context. Supervisory data already exists — MASS swarm-health P1–P4 (graph/social_system.py), per-agent circuit breakers, the durable session registry, and request/grant approvals — but was scattered and unsurfaced. A separate supervisor service will add operational complexity.

Decision. No new service. Expose a /api/fleet/* plane from the existing gateway (gateway/fleet.py): per-domain health/error-rates, live topology, whole-domain pause/kill (blast-radius containment reusing core.sessions cancel mechanics), the mutation/risk approval queue (read/grant via the parity-covered graph_query/graph_orchestrate tools), and the correlation query endpoints (GET /api/fleet/trace?correlation_id=…, GET /api/fleet/touched?resource=…, see §4). The agent-webui Fleet Supervisor view is the single pane of glass over it. Multi-agent containment + recovery are covered end-to-end by tests/integration/test_fleet_chaos.py (domain pause contains only its domain; concurrent goal loops honor pause with zero side effects).

3. Durable execution on native WorkItems (CONCEPT:AU-ORCH.execution.retry-predicate-raised-treating)

Context. A separate checkpoint database creates a second lifecycle next to the engine's work authority. Its progress can diverge from the active lease, allow a stale worker to advance, or report completion before the authoritative terminal commit.

Decision. Keep lease, retry, idempotency, progress, and terminal outcome on the same engine-native WorkItem. A live claimant persists an opaque checkpoint_id only after renewing its engine-issued lease; the update is atomically fenced on tenant, owner, lease epoch, fencing token, and current status. Repeated matching terminal commits are idempotent. There is no SQLite or Postgres checkpoint sidecar, and STATE_DB_URI does not select execution checkpoint storage.

Wired into the live path (shipped). LoopController.run_loop resumes from the WorkItem's last fenced checkpoint:iteration:<n> reference after an expired lease is reclaimed. Agent dispatch similarly uses the turn's deterministic WorkItem and acknowledges queue delivery only after its fenced native commit. A crash before acknowledgement is therefore a safe redelivery; a stale worker cannot overwrite progress or outcome from the newer lease.

Covered by tests/unit/orchestration/test_work_item.py, tests/unit/knowledge_graph/test_loop_work_item_checkpoint.py, and tests/unit/test_agent_dispatch.py.

4. Cross-agent trace correlation (CONCEPT:AU-OS.observability.run-wide-correlation-id)

Context. @trace nests spans within one process via contextvars, but a multi-agent run had no shared key and side-effects carried no correlation — "which agents touched record X?" was unanswerable across boundaries.

Decision. observability/correlation.py adds a run-wide correlation id, W3C traceparent (de)serialization (current_carrier/bind_carrier) for cross-process agent spawns, and inject/extract for outbound side-effect headers (Kafka records, ServiceNow/connector calls). engine.run_graph establishes the id at every entry point; the Langfuse exporter stamps it on every trace so a run is one joinable story.

Queryable from the graph (shipped). Emission alone left "who touched X?" answerable only from Langfuse / ad-hoc Cypher. The correlation id (plus actor/tenant) is now persisted onto the durable effect nodes — FleetEvent (gateway/fleet_events.py persist_event) and executed Task nodes (the dispatch worker) — and read back through two supervisory routes: GET /api/fleet/trace? correlation_id=… returns every node stamped with a run's id, and GET /api/fleet/touched?resource=… returns the events + originating actors that touched a resource (blast-radius). Covered by tests/unit/test_fleet_correlation.py.

Scale note (100k–100M agents)

These decisions make the control surface complete and correct, but extreme scale needs a distributed execution substrate: multiple gateway workers, a durable queue (Kafka is deployed), shared durable graph state (pg-age), and lease/heartbeat work distribution. Durable execution (§3) is now wired into the live goal-loop and dispatch paths and, with state_db_uri, shares Postgres checkpoints across hosts — the concrete first step; broader horizontal scale-out is tracked separately.

Wire contract integrity (CONCEPT:EG-KG.query.wire-protocol)

The gateway/engine boundary is out-of-process MessagePack over UDS/TCP — there is no PyO3 / FFI — so the Python client mirrors the Rust Method enum by sending variant names as strings. Nothing generated or type-checked that, so a renamed/removed engine op or a client typo can drift silently. The epistemic-graph repo now ships a CI gate (tests/test_protocol_parity.py, run in rust-ci.yml): it parses the Method enum (165 variants) and the client's _send(...) calls, asserts no client method lacks a Rust variant, and ratchets the set of unbound variants against a committed baseline so a new engine op forces a conscious client binding.