Supported Deployment Configurations¶
Scale evidence boundary (AU-SCALE): The ladder describes AU source capabilities and deployment inputs.
IMPLEMENTED/UNIT-PROVENdoes not mean a rendered release is deployable or live;LAB-PROVEN,LIVE, and1M-CERTIFIEDrequire the corresponding external evidence. The optional Kubernetes actuator and the staged HPA manifests are documented separately from the safedryrundefault. See the AU scale claim register.
This is the flagship configuration ladder for agent-utilities: five complete, copy-paste-ready configurations, from a laptop with no separately managed graph infrastructure to an autonomous multi-host fleet. Each rung builds on the previous one and states its delta explicitly.
Every flag name, default, and behavior on this page is verified against
agent_utilities/core/config.py (AgentConfig), the shipped
docker/*.compose.yml files, and the module that reads the flag. The
authoritative flag inventory (with per-flag verdicts) is
Configuration Reference & Flag Audit.
Configurations the CI pipeline cannot stand up (live Kafka, live Postgres,
multi-shard engines, live Docker/Kubernetes actuators) are explicitly marked
not exercised in CI below, with a pointer to the unit suites that cover
their logic against injected fakes.
Summary matrix¶
| Rung | Durable state | WorkItem transport | Agent dispatch | Auth | Engines | Autonomy |
|---|---|---|---|---|---|---|
| (a) Zero-infra dev | per-host SQLite | sqlite (auto) |
inline (default) |
neutral one-time in-memory proof for exact tiny local stdio; external identity for every other boundary; engine HMAC | 1 supervised local engine | off; ActionPolicy = approval-required default |
| (b) Secured single node | per-host SQLite | sqlite (auto) |
inline |
process JWT + per-request Bearer JWT + engine HMAC + brain enforcement | 1 local engine, HMAC | off |
| (c) Durable single node | shared Postgres (STATE_DB_URI) |
postgres (auto) |
inline |
as (b) | 1 local engine | off |
| (d) Scaled multi-host | shared Postgres | kafka (explicit) |
queue + worker fleet |
as (b) | 3-member cell, catalog-routed MultiRaft groups | off |
| (e) Autonomous operations | shared Postgres | kafka |
queue |
as (b) | production cell | golden loop, failure-driven evolution, fleet reconciler, autoscaler, event webhook |
How configuration is loaded¶
Three layers, in precedence order:
- Explicit process environment — every flag is a typed field on
AgentConfig(agent_utilities/core/config.py); the env alias is the flag's canonical name. The CI gatescripts/check_no_env_sprawl.pykeeps new flags onAgentConfiginstead of scatteredos.environreads. AgentConfig never searches a repository or launch directory for a dotenv file. - XDG
config.json— discovered under$XDG_CONFIG_HOME/agent-utilities/config.json(override the directory withAGENT_UTILITIES_CONFIG_DIR; resolution lives inagent_utilities/core/paths.py). Each key is upper-cased to its env name and applied only if that env var is not already set — so the environment always wins. Lists/dicts are JSON-encoded (this is howchat_models/embedding_modelsregistries are normally configured). The optional fixed siblingruntime-secrets.jsonprojects only keys targeted by exactenv://references; it never overrides an explicit process value. A templateconfig.jsonis written on first run if none exists.GRAPH_SERVICE_ENDPOINTSand other list flags accept either a JSON list (natural inconfig.json) or a comma-separated explicit process value. - Defaults — the values baked into
AgentConfig. The zero-infra rung uses defaults for graph topology and local persistence. Only tiny packaged-local GraphOS over stdio may derive its neutral process authority in memory; every other boundary requires explicit external identity.
Data lives under XDG paths: config $XDG_CONFIG_HOME/agent-utilities/, data
$XDG_DATA_HOME/agent-utilities/ (AGENT_UTILITIES_DATA_DIR override), and
cache $XDG_CACHE_HOME/agent-utilities/. The runtime supplies platform defaults
when an XDG variable is unset.
The relevant processes (console scripts from pyproject.toml
[project.scripts]):
| Command | Role |
|---|---|
graph-os |
MCP tool surface (stdio default; --transport streamable-http --port <p> for HTTP; compose: docker/mcp.compose.yml, port 8004) |
graph-os-daemon |
Standalone KG host daemon: holds the flock host lock, drains the durable task queue, runs all maintenance/autonomy ticks. --status and --drain-queue flags. It serves no HTTP. |
python -m agent_utilities |
The REST gateway (FastAPI agent server): /health, /api/graph/*, /api/sessions, /api/goals, /api/fleet/*, /api/dashboard/*, /metrics. Binds HOST:PORT (defaults 0.0.0.0:9000). GATEWAY_WORKERS pre-forks it. When it runs, it hosts the KG daemon itself (flock-elected) — you do not also need graph-os-daemon. |
kg-ingest-worker |
Decoupled ingest consumer (kg-ingest group), engine client only (AU-KG.ingest.decoupled-kg-ingest-consumer) |
agent-dispatch-worker |
Stateless agent-turn consumer (agent-dispatch group), engine client only (ORCH-1.45) |
Rung (a): Zero-infra dev¶
What you get: the full platform on one machine with no separately managed
graph database or engine service. The knowledge graph runs on the default
epistemic_graph backend — the
epistemic-graph Rust engine is the one database (the authority), providing
durable persistence, in-memory cache, graph compute, and ontology reasoning in a
packaged, supervised out-of-process engine with no mirrors — all platform state in per-host
SQLite files under the XDG data dir, agent turns queued to a local dispatch
worker, a verified neutral process session for the exact local stdio boundary,
and authenticated engine traffic. GraphOS signs and validates a short-lived JWT
with an in-memory key as a one-time proof, destroys both, and returns a
process-lifetime session. It is not anonymous and contains no personal, host,
endpoint, filesystem, credential, or proof identity. What you don't get: durability
beyond this host, more than one host, or autonomous operations (all autonomy flags default off;
the shipped ActionPolicy marks every mutating operational action
approval_required).
AgentConfig projection¶
Generate the XDG config.json with the explicit tiny deployment profile. The
zero-infrastructure exception is only graph-os --transport stdio with no
engine endpoints and no external process-identity source. The
KEY=value notation below is documentation shorthand for AgentConfig aliases,
not a file to create in the repository. Put non-secret policy values and secret
references in AgentConfig; concrete token and client-secret material belongs only
in the runtime secret system:
DEPLOYMENT_PROFILE=tiny
# Keep GRAPH_SERVICE_ENDPOINTS unset so GraphOS owns the packaged engine.
# Keep KG_AUTH_TOKEN_REF and KG_IDENTITY_OAUTH2 unset so exact stdio startup
# uses the neutral in-memory authority. This exception applies to no other entrypoint.
# With GRAPH_SERVICE_ENDPOINTS unset, graph-os owns the packaged local Rust
# engine lifecycle. The `epistemic-graph-server` binary ships with the wheel;
# there is no in-process fallback.
# Model provider — only needed for LLM-backed features (agents, enrichment).
# Graph operations run without it.
CHAT_MODELS=[{"id":"chat-model","provider":"openai","api_key_ref":"vault://platform/llm#api_key"}]
# Other providers use the same per-model api_key_ref shape. Raw provider keys
# are explicit process-only inputs and cannot be durable AgentConfig values.
# -- Graph/runtime defaults, listed explicitly (do not need to be set) --
# The engine IS the fixed database authority; no selector is required.
#KG_DAEMON_ROLE=auto # flock host-lock elects ONE host per machine
#STATE_DB_URI= # unset = per-host SQLite state files
#TASK_QUEUE_BACKEND= # unset = auto: sqlite (no STATE_DB_URI)
# Agent dispatch is queue-only; TASK_QUEUE_BACKEND selects its transport.
# Tenant, ACL, session, and single-client enforcement are baked in.
#GATEWAY_WORKERS=1
#GATEWAY_METRICS=true # /metrics on the gateway
#KG_DEV_MODE=false # false = all maintenance daemons on
#FLEET_RECONCILER=false
#FLEET_AUTOSCALER=false
#KG_LOOP=false
#KG_FAILURE_EVOLUTION=false
#ACTION_POLICY_PATH= # empty = shipped conservative default policy
Before starting a daemon, library graph surface, REST gateway, network MCP
transport, non-tiny profile, or any GraphOS connected to explicit engine
endpoints, configure exactly one KG_AUTH_TOKEN_REF or KG_IDENTITY_OAUTH2
plus AUTH_JWT_JWKS_URI, audience, issuer, and KG_POLICY_VERSION. An explicit
source that cannot be acquired or validated never falls back to the local
authority.
config.json¶
Generate the XDG file with setup-config; do not add an external process identity
for the exact tiny packaged-local stdio path. A model registry
(chat_models / embedding_models) is optional for graph-only operations; see
docs/examples/config.json. Run
agent-utilities-doctor --only graph_identity auth before launch. Add secrets
to that doctor selection when validating an external identity source.
Process and engine auth on this rung¶
For the exact tiny packaged-local stdio boundary, GraphOS generates an asymmetric
key in memory, signs a short-lived JWT with fixed neutral service claims as a
one-time proof, validates it through the normal decoder, destroys the private key
and token, and returns the immutable process-lifetime graph session. That session
is least-privilege: it carries kg:read/kg:write, never
kg:admin — it is the identity every stdio tool call and background/system write
runs under, so an ambient admin grant would let any local process perform
admin:*/security:*/*:control engine actions with zero configuration. No
setting widens it. The one admin-scoped engine action this rung performs —
materializing a fresh packaged local engine's own tenant graph on first start —
uses a separate, one-shot proof (mint_local_process_bootstrap_authority) that
carries exactly the engine's graph:admin lifecycle scope, is minted only for
that provisioning call, and is never installed as ambient identity. Any other
administration needs a configured external identity (KG_AUTH_TOKEN_REF or
KG_IDENTITY_OAUTH2), which takes the deployment off this rung. Every other
boundary resolves exactly one runtime process identity and validates it against
the configured JWKS/issuer/audience. Missing, ambiguous, unresolved, or invalid
external identity aborts startup without local fallback.
Separately, engine traffic is authenticated: when
GRAPH_SERVICE_AUTH_SECRET is unset, a per-install HMAC secret is minted once
and persisted as engine_secret in the discovered XDG data directory (mode 0600,
knowledge_graph/core/graph_compute.py), and every local process — including
any engine this install spawns — agrees on it (CONCEPT:AU-OS.identity.authenticated-identity-enforcement).
The native transport has no unauthenticated development mode.
Verify¶
# 1. Prove the neutral local authority boundary before launch.
agent-utilities-doctor --only graph_identity auth
# 2. MCP surface over stdio (register the already-installed command in your IDE).
# { "mcpServers": { "graph-os": { "command": "graph-os", "args": ["--transport", "stdio"] } } }
# Daemon, library, REST, network MCP, and explicit-endpoint verification belongs
# to rung (b): configure its external process identity first.
Recipe form: Tiny. MCP consumption patterns: mcp-consumption.
Rung (b): Secured single node¶
What you get: everything from (a) plus a network-serving identity perimeter. The process JWT remains mandatory, and every REST or HTTP MCP graph request must present its own validated Bearer JWT; the server never substitutes process authority for a network caller. This rung also enables brain enforcement and fail-closed node-level permissions. What you don't get: durability beyond this host, scale-out, or autonomy.
The pieces (all CONCEPT:AU-OS.identity.authenticated-identity-enforcement, validated in
agent_utilities/security/auth.py and
agent_utilities/security/request_identity.py):
- Engine HMAC — automatic per-install secret as in rung (a); set
GRAPH_SERVICE_AUTH_SECRETexplicitly only when multiple installs/hosts must share one engine. - Verified GraphSession identity —
ActorIdentityMiddlewarevalidatesAuthorization: Bearer <JWT>againstAUTH_JWT_JWKS_URI(JWKS cached 5 minutes), checks the requiredAUTH_JWT_AUDIENCEplus the pinned issuer, and mints the server-sideActorContextfrom the claims (sub/client_id/azp→ actor,roles/realm_access.roles/scope→ roles,tenant_id/tenant/org_id/tid→ tenant). Missing or invalid credentials get 401; only health paths (/health,/health/ready,/healthz,/api/health,/api/healthz) stay open withCache-Control: no-store. Liveness and readiness share the same non-secret runtime report; readiness maps engine reachability onto HTTP 200/503 while liveness remains 200. Identity and raw topology detail are never included. Caller-supplied_actor/_roles/_tenantkwargs are rejected.KG_POLICY_VERSIONis required and stamped into the session. Only an explicit or identity-mappedkg:admincapability grants graph administration; a genericadminapplication role does not. KG_AUTH_TOKEN_REForKG_IDENTITY_OAUTH2— exactly one stdio process identity source. The first resolves a provisioned JWT from the secret manager; the second mints a short-lived JWT with client credentials whose secret is itself a runtime secret reference. Both are validated against the same JWKS. The validated expiry is shared with all process workers in an in-memory lease that contains no token or identity. Renewal accepts only the same subject, actor type, capabilities, tenant, authentication state, and groups. Drift is rejected; failed renewal retries without extending the lease, and all graph work fails closed at expiry.- Mandatory graph authority — every operation inherits the server-verified
actor, tenant, scopes, audience, and policy revision from its ambient
GraphSession. Tenant/ACL enforcement is always active, missing ACLs deny, and authorization failures never return unfiltered data. These are compiled contracts, not deployment flags.
Declaring APP_PROFILE=production also makes transport and execution bypasses
fatal at startup. Certificate verification must remain enabled, remote engine
TCP must be TLS protected, and unauthenticated/wildcard-host/developer escape
hatches must remain disabled. Private PKI is supported through a named TLS
profile or secret-backed CA bundle; production does not require public roots and
must not use a hardcoded verify=false exception.
APP_PROFILE is only that runtime safety posture; it does not identify a
deployment topology. DEPLOYMENT_PROFILE must name exactly tiny,
single-node-prod, or enterprise, and every generated configuration persists
it. Because both production topologies use APP_PROFILE=production, the config
doctor refuses a production posture with no explicit deployment identity instead
of guessing that it is enterprise.
AgentConfig projection¶
# ---- Everything from rung (a) ----
CHAT_MODELS=[{"id":"chat-model","provider":"openai","api_key_ref":"vault://platform/llm#api_key"}]
# ---- plus: identity & enforcement (OS-5.14) ----
# Required server-validated JWT identity on the KG REST/MCP surface
AUTH_JWT_JWKS_URI=https://identity.example.test/realms/agents/protocol/openid-connect/certs
AUTH_JWT_ISSUER=https://identity.example.test/realms/agents
AUTH_JWT_AUDIENCE=agent-utilities
KG_POLICY_VERSION=policy-v1
# Identity for stdio MCP processes (no Authorization header on stdio).
# Configure exactly one source; this example resolves a provisioned JWT.
KG_AUTH_TOKEN_REF=secret://graph-os/stdio-token
# Alternatively configure KG_IDENTITY_OAUTH2 as a JSON client-credentials block
# whose client_secret is a runtime secret reference.
# Engine HMAC is automatic (per-install secret at data_dir()/engine_secret).
# Set explicitly only to share one engine across installs:
#GRAPH_SERVICE_AUTH_SECRET=${GRAPH_SERVICE_AUTH_SECRET_FROM_SUPERVISOR}
Verify¶
python -m agent_utilities &
# Health stays open
curl -s localhost:9000/health
# Without a token: 401
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:9000/api/graph/query \
-H 'content-type: application/json' -d '{"query":"MATCH (n) RETURN n LIMIT 1"}'
# -> 401
# With a valid JWT from your issuer: 200
TOKEN=$(curl -s -X POST "$AUTH_JWT_ISSUER/protocol/openid-connect/token" \
-d grant_type=client_credentials -d client_id=agent-utilities \
-d client_secret=REDACTED | jq -r .access_token)
curl -s -X POST localhost:9000/api/graph/query \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"query":"MATCH (n) RETURN count(n) AS n"}'
# Engine secret was minted automatically
ls -l "$XDG_DATA_HOME/agent-utilities/engine_secret" # mode 0600
JWT validation (JWKS fetch/cache, claim mapping, 401 paths) is unit-tested; a live identity provider is not exercised in CI — validate the issuer wiring against your IdP once per environment. Worked example: identity-jwt. Background: Secrets & auth.
Rung (c): Durable single node¶
What you get: everything from (b) plus durability that survives the host
process — and the schema/locking groundwork for multi-host. One flag,
STATE_DB_URI, externalizes ALL durable platform state (durable-execution
checkpoints, sessions/turns/goals, the KG task + staging queue) onto one
shared Postgres through a single connection pool (CONCEPT:AU-OS.state.unified-durable-state-externalization–5.18,
state externalization). The engine
remains the graph authority; declaring GRAPH_MIRROR_TARGETS with a
GRAPH_DB_CONNECTION_PROFILE_REF adds an asynchronous Postgres/pg-age projection for interop and DR
(never on the read path).
What you don't get: horizontal scale-out of ingest or agent execution
(still one host doing the work), autonomy.
What turns on, with no further flags:
TASK_QUEUE_BACKENDauto-resolution — unset means auto:postgreswhenSTATE_DB_URIis set, elsesqlite(knowledge_graph/core/queue_backend.py: create_task_queue). The Postgres queue claims withFOR UPDATE SKIP LOCKED(at-least-once, 600 s visibility timeout). SettingTASK_QUEUE_BACKEND=postgresexplicitly makes an unreachable database a fail-loud startup error instead of a logged SQLite fallback (CONCEPT:AU-KG.backend.selectable-queue-backend).- Daemon leadership —
DaemonLeadership(core/leadership.py) holds a Postgres session advisory lock per role; maintenance ticks (analysis, golden loop, failure ingest, fuseki publish, reconciler, autoscaler) become leader-only fleet-wide. A crashed leader's lock releases server-side and a follower takes over within one tick (CONCEPT:AU-OS.state.cross-host-daemon-leadership). Under SQLite this is a no-op (single host).
Compose¶
docker/pg-age.compose.yml provisions a graph-enabled Postgres (ParadeDB)
on host port 5433, database agent_kg, user/password agent/agent,
with init scripts from docker/pg-age-init/:
docker compose -f docker/pg-age.compose.yml up -d
# A separate logical DB for platform state keeps it apart from the graph mirror:
docker exec agent-pg-age psql -U agent -d agent_kg -c 'CREATE DATABASE agent_state'
Any Postgres you already run works the same way; the compose file is the worked single-host example.
AgentConfig projection¶
# ---- Everything from rung (b) ----
CHAT_MODELS=[{"id":"chat-model","provider":"openai","api_key_ref":"vault://platform/llm#api_key"}]
AUTH_JWT_JWKS_URI=https://identity.example.test/realms/agents/protocol/openid-connect/certs
AUTH_JWT_ISSUER=https://identity.example.test/realms/agents
AUTH_JWT_AUDIENCE=agent-utilities
KG_POLICY_VERSION=policy-v1
# ---- plus: durable state (AU-OS.state.unified-durable-state-externalization) + async graph mirror ----
# ONE flag moves session/turn/fleet metadata + queue delivery onto shared
# Postgres (unset = per-host SQLite support stores). WorkItem checkpoints stay
# in epistemic-graph.
STATE_DB_URI=vault://platform/state#profile
#STATE_DB_POOL_SIZE=8 # default: max connections in the ONE shared pool
# Async Postgres/pg-age MIRROR of the engine graph (interop/BI/DR; off the read path)
GRAPH_MIRROR_TARGETS=postgresql
GRAPH_DB_CONNECTION_PROFILE_REF=vault://platform/graph#profile
# WorkItem notification transport: leave unset — auto resolves to postgres because STATE_DB_URI is
# set. Set explicitly to make a missing database fail loud at startup:
#TASK_QUEUE_BACKEND=postgres
Verify¶
graph-os-daemon --status # or run the gateway; queue backend should be postgres
# State survives a restart: create a goal, restart, list goals
curl -s -X POST localhost:9000/api/goals -H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' -d '{"description":"durability probe"}'
# restart the gateway process, then:
curl -s localhost:9000/api/goals -H "authorization: Bearer $TOKEN"
# The live-Postgres integration pass (skipped without STATE_DB_URI):
STATE_DB_URI="$STATE_DB_URI" \
python -m pytest tests/integration/test_state_postgres_live.py
Not exercised in CI: the live-Postgres paths. CI runs the unit suites
(tests/unit/test_state_store.py,
tests/unit/knowledge_graph/test_queue_backend.py) against fake
pools/connections; the live suite tests/integration/test_state_postgres_live.py
exercises the real SKIP LOCKED claims, advisory leadership, and schema, but
only runs when STATE_DB_URI points at a reachable Postgres — run it once
against your database.
Recipe form: Single-node prod.
Rung (d): Scaled multi-host¶
What you get: everything from (c) plus horizontal scale-out of all three
work planes — ingest (Kafka-partitioned task queue + kg-ingest-worker
fleet), agent execution (session-keyed agent_turns queue +
agent-dispatch-worker fleet), and the graph engine itself (a replicated cell
whose catalog assigns tenant graphs to fenced MultiRaft groups) — with N gateway workers/replicas behind a load
balancer and Prometheus scraping every tier. What you don't get:
autonomy (rung e). Placement movement is governed by the engine catalog and
online reshard workflow; clients never reassign data from an endpoint-list hash.
Not exercised in CI. No live Kafka broker, multi-member engine cell,
or cross-host worker fleet runs in CI. The selection/routing/partitioning/
delivery contracts are covered by unit suites with injected transport fakes:
tests/unit/knowledge_graph/test_kafka_ingest_scaleout.py,
tests/unit/knowledge_graph/test_engine_sharding.py,
tests/unit/test_agent_dispatch.py. Validate this rung end-to-end in a
staging environment before relying on it.
Production cell¶
# Kafka (KRaft, single broker).
# Minimal — topics are created by the app's idempotent ensure-topic:
docker compose -f docker/docker-compose.kafka.yml up -d
# ...or fully provisioned (kg.* event topics, retention policies, tunable
# partitions via KG_TASKS_PARTITIONS):
docker compose -f docker/kafka-kraft.compose.yml up -d
# Render only an exact, signed, compatibility-checked release manifest.
check-graphos-compatibility --manifest RELEASE_MANIFEST
python scripts/release/render_production_cell.py \
--manifest RELEASE_MANIFEST --output RENDERED_DIRECTORY
python scripts/deployment/check_production_assets.py \
--directory RENDERED_DIRECTORY --rendered
kubectl apply -k RENDERED_DIRECTORY
docker/engine-shards.compose.yml remains a compatibility fixture for independent
development engines. It does not provide Raft replication, authoritative movement,
or production availability and must not be used as this rung's data plane.
AgentConfig projection¶
# ---- Everything from rung (c) ----
CHAT_MODELS=[{"id":"chat-model","provider":"openai","api_key_ref":"vault://platform/llm#api_key"}]
AUTH_JWT_JWKS_URI=https://identity.example.test/realms/agents/protocol/openid-connect/certs
AUTH_JWT_ISSUER=https://identity.example.test/realms/agents
AUTH_JWT_AUDIENCE=agent-utilities
KG_POLICY_VERSION=policy-v1
STATE_DB_URI=vault://platform/state#profile
GRAPH_MIRROR_TARGETS=postgresql
GRAPH_DB_CONNECTION_PROFILE_REF=vault://platform/graph#profile
# ---- plus: Kafka task queue (KG-2.55/2.56) ----
TASK_QUEUE_BACKEND=kafka # explicit = fail-loud if the broker is down
KAFKA_BOOTSTRAP_SERVERS=kafka.example.test:9092
#KG_TASKS_PARTITIONS=6 # default; ensured at startup, grow-only;
# bounds kg-ingest consumer parallelism
# ---- plus: queue-driven agent dispatch (ORCH-1.45) ----
# Turns always return a job handle; deploy agent-dispatch-worker processes.
#AGENT_TURNS_PARTITIONS=6 # default; bounds concurrent-session parallelism
#AGENT_DISPATCH_MAX_DEPTH=100000 # default; fail-closed durable admission bound
#AGENT_DISPATCH_CLAIM_TTL_S=120 # default; capped at the 300s recovery objective
#AGENT_DISPATCH_RENEW_INTERVAL_S=30 # default; periodic fenced renewal
# ---- plus: engine placement ----
# All clients use one stable coordinator. Placement group, endpoint, epoch, and
# fencing token come from the engine catalog and stale routes refresh once.
GRAPH_SERVICE_ENDPOINTS=tls://kg-coordinator.example.test:9100
ENGINE_TLS_PROFILE_REF=vault://platform/engine/tls-profile
# Legacy compatibility input only; live placed-group routing uses the authenticated
# ClusterMembers snapshot returned by the coordinator. It is never endpoint authority.
#GRAPH_RAFT_GROUP_ENDPOINTS={"0":"tls://kg-group-0.example.test:9100"}
# Optional deployment pin and bounded last-good discovery/termination behavior.
#GRAPH_CLUSTER_ID=sha256:<verified-cluster-digest>
#GRAPH_CLUSTER_DISCOVERY_MAX_AGE_S=30
#GRAPH_CLUSTER_DISCOVERY_CLOCK_SKEW_S=5
#GRAPH_DRAIN_TIMEOUT_S=15
# A concrete value is injected by the deployment supervisor at process start.
GRAPH_SERVICE_AUTH_SECRET=${GRAPH_SERVICE_AUTH_SECRET_FROM_SUPERVISOR}
#KG_DEFAULT_GRAPH=__bus__ # default; policy-mapped before catalog lookup
# ---- plus: gateway scale + observability (AU-OS.observability.no-op-without-metrics) ----
GATEWAY_WORKERS=4 # pre-forked workers on ONE listen socket;
# the flock elects ONE KG host among them
#GATEWAY_METRICS=true # default: /metrics on the gateway
#GATEWAY_RATE_LIMIT=0 # per-tenant req/s; 0 = off (buckets per process)
#GATEWAY_RATE_BURST=0 # 0 = 2x rate
#ENGINE_BREAKER_THRESHOLD=5 # default; 0 = off
#ENGINE_BREAKER_COOLDOWN=15 # default, seconds
The same settings in config.json form:
{
"task_queue_backend": "kafka",
"kafka_bootstrap_servers": "kafka.example.test:9092",
"agent_dispatch_backend": "queue",
"graph_service_endpoints": [
"tls://kg-coordinator.example.test:9100"
],
"engine_tls_profile_ref": "vault://platform/engine/tls-profile",
"graph_service_auth_secret": "<the one shared secret>"
}
Worker fleets¶
Run on any host that can reach Kafka, Postgres, and the cell coordinator. Both force
KG_DAEMON_ROLE=client (never contend for the KG host flock) and fail loud at
startup if they cannot reach the engine with the shared HMAC secret:
# Ingest workers (consumer group "kg-ingest"); worker count autosized from
# CPU/memory when --workers is omitted:
kg-ingest-worker --workers 4 --bootstrap-servers kafka.example.test:9092
# Agent dispatch workers (consumer group "agent-dispatch"); default 1 thread —
# turns are LLM-bound:
agent-dispatch-worker --workers 2
Load balancer (Caddy)¶
N gateway replicas (each GATEWAY_WORKERS=1) or one multi-worker gateway —
both are supported; see gateway scaling
for the per-process state table (metrics registries, rate-limit buckets).
Prometheus scrape¶
/metrics registries are per-process — scrape each gateway replica directly
(not through the load balancer), plus every engine member's native metrics listener:
scrape_configs:
- job_name: agent-utilities-gateway
static_configs:
- targets: ["gw-1:9000", "gw-2:9000", "gw-3:9000"]
- job_name: epistemic-graph-members
static_configs:
- targets: ["kg-member-0.example.test:9110",
"kg-member-1.example.test:9110",
"kg-member-2.example.test:9110"]
Key series: agent_utilities_gateway_requests_total, native
epistemic_graph_* Raft/WAL/checkpoint health,
agent_utilities_dispatch_queue_depth,
agent_utilities_dispatch_turns_total{outcome},
agent_utilities_dispatch_workers. Full walkthrough:
observability.
Verify¶
# Kafka topics exist with the right partition counts (created/grown at startup)
docker exec kafka kafka-topics --bootstrap-server localhost:9092 --describe \
--topic kg_tasks # docker-compose.kafka.yml image; the kafka-kraft.compose.yml
# image uses /opt/kafka/bin/kafka-topics.sh
# Client endpoint topology: coordinator reachability + breaker state. This legacy
# view is not the Raft member inventory; use native engine placement/Raft health.
curl -s localhost:9000/api/dashboard/daemon/shards -H "authorization: Bearer $TOKEN"
# Dispatch fleet is visible (worker heartbeats in the topology)
curl -s localhost:9000/api/fleet/topology -H "authorization: Bearer $TOKEN"
# Metrics are flowing
curl -s gw-1:9000/metrics | grep agent_utilities_engine_shard_up
curl -s https://kg-member-0.example.test:9110/metrics | grep epistemic_graph_ | head
Worked examples: sharding-walkthrough, queue-dispatch-walkthrough. Deep dives: engine sharding, agent dispatch, event backbone, capacity model.
Rung (e): Autonomous operations¶
What you get: everything from (d) plus the platform operating on itself:
the golden-loop research/remediation cycle, failure-driven evolution from
Langfuse telemetry, the desired-state fleet reconciler with a policy-gated
Docker/Kubernetes/injected actuator,
the reactive replica autoscaler, and webhook ingress for monitoring events.
Every mutating action still flows through the ONE ActionPolicy gate — the
shipped default policy queues all of it for human approval, so "autonomous"
is opt-in per action kind. What you don't get: unattended mutation out of
the box (you must relax the policy rule-by-rule), and auto-merge of evolution
proposals unless you explicitly enable KG_GOLDEN_AUTO_MERGE.
All ticks below run in the KG host daemon and are leader-only under
STATE_DB_URI (rung c) — exactly one host in the fleet runs them.
Not exercised in CI. The control logic (policy gate, reconciler diff,
autoscaler bounds, golden-loop stages) is unit-tested, but CI never runs a
live Langfuse, a real Docker or Kubernetes actuator, or a live Prometheus
signal source.
The shipped defaults are deliberately inert (FLEET_ACTUATOR=dryrun,
approval-required policy); treat every relaxation as a production change.
AgentConfig projection¶
# ---- Everything from rung (d), plus: ----
# -- Golden loop: propose-only self-evolution (intake -> acquire -> distill) --
KG_LOOP=true
#KG_LOOP_INTERVAL=3600 # default, seconds
#KG_LOOP_TOPICS=5 # default: hot topics per tick
#KG_LOOP_DISTILL=false # default; opt-in distillation stage
#KG_LOOP_BREADTH=false # default; opt-in breadth scan
#KG_LOOP_STANDARDIZE=false # default; opt-in standardization stage
KG_GOLDEN_AUTO_MERGE=false # default; keep merges human-gated
#EVOLUTION_WORKTREE_ROOT= # default: data_dir()/evolution_worktrees
# -- Failure-driven evolution (AU-AHE.harness.failure-evolution): Langfuse failures -> remediation --
# Auto-enabled by the two Langfuse credentials below; set false to opt out.
#KG_FAILURE_EVOLUTION=true
#KG_FAILURE_EVOLUTION_INTERVAL=3600 # default, seconds
#KG_FAILURE_EVOLUTION_WINDOW=86400 # default: telemetry look-back, seconds
#KG_FAILURE_REGRESSION_DATASET=false # default; dataset-based regression path
LANGFUSE_HOST=https://langfuse.example.test
LANGFUSE_PUBLIC_KEY_REF=vault://platform/langfuse#public_key
LANGFUSE_SECRET_KEY_REF=vault://platform/langfuse#secret_key
# -- Fleet reconciler (AU-OS.config.desired-state-fleet-reconciler): desired state vs observed, policy-gated --
FLEET_RECONCILER=true
#FLEET_RECONCILER_INTERVAL=120 # default, seconds
#FLEET_RECONCILER_MAX_ACTIONS=5 # default: storm guard per tick
#FLEET_REGISTRY_PATH= # empty = shipped deploy/mcp-fleet.registry.yml
#FLEET_DESIRED_STATE_PATH= # optional per-service replicas/version overlay
FLEET_ACTUATOR=docker # default "dryrun" records intent, mutates NOTHING;
# "docker" = reference CLI actuator;
# "k8s"/"kubernetes" = optional kubectl Deployment actuator.
# Portainer/Swarm: set_fleet_actuator() seam (below)
#DEPLOY_WATCH_WINDOW=300 # default: post-deploy health watch, seconds
#DEPLOY_WATCH_POLL=15 # default: probe interval inside the watch
# -- Autoscaler (OS-5.29): load signal -> registry min/max -> policy gate --
FLEET_AUTOSCALER=true
#FLEET_AUTOSCALER_INTERVAL=60 # default, seconds
SCALING_PROMETHEUS_URL=https://prometheus.example.test:9090
# unset = zero-infra in-process gauges
# -- ActionPolicy (OS-5.24): the single autonomy decision point --
ACTION_POLICY_PATH=$XDG_CONFIG_HOME/agent-utilities/action-policy.yml
# Empty (default) = the shipped conservative policy
# (deploy/action-policy.default.yml): every mutating kind approval_required,
# only diagnose/observe/notify/record_dry_run auto. KG governance_rule
# overrides (scope: action_policy) win over file rules either way.
# -- Monitoring webhook ingress (AU-OS.config.fleet-event-ingress) --
FLEET_EVENTS_TOKEN_REF=<secret-provider-reference>
# Shared secret for POST /api/fleet/events (header X-Fleet-Events-Token,
# constant-time compare, re-read per request so rotation needs no restart).
# Default unset = unauthenticated callers are rejected.
Custom actuator (Portainer/Swarm or another deployment)¶
FLEET_ACTUATOR selects between dryrun, the reference docker CLI actuator,
and the optional k8s/kubernetes kubectl Deployment actuator. A deployment
can still register a Portainer, Swarm, or other actuator through the seam:
from agent_utilities.orchestration.fleet_actuation import set_fleet_actuator
set_fleet_actuator(MyPortainerActuator()) # real actuation behind the policy gate
Verify¶
# Reconciler/autoscaler proposals land in the approval queue (default policy
# queues every mutating action):
curl -s localhost:9000/api/fleet/approvals -H "authorization: Bearer $TOKEN"
# Webhook ingress: rejected without the token...
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:9000/api/fleet/events \
-H 'content-type: application/json' -d '{"alerts":[]}'
# -> 401
# ...accepted with it:
curl -s -X POST 'localhost:9000/api/fleet/events?source=alertmanager' \
-H "x-fleet-events-token: <runtime-resolved-token>" \
-H 'content-type: application/json' \
-d '{"alerts":[{"status":"firing","labels":{"alertname":"probe"}}]}'
# Daemon ticks are registered (leader host):
graph-os-daemon --status
Worked examples: action-policy-postures (relaxing the default posture rule-by-rule), fleet-events-wiring (Alertmanager / Uptime Kuma / Portainer payloads), autoscaling-signals, evolution-publication (how promoted proposals become reviewable local branches). Deep dives: fleet autonomy, failure-driven evolution, autonomous evolution guide.
Recipe form: Enterprise.
Where to go next¶
- Flag-by-flag inventory and the configuration-discipline rule: Configuration Reference & Flag Audit
- Worked end-to-end examples: ontology-to-workflow, identity-jwt, observability, sharding-walkthrough, queue-dispatch-walkthrough, fleet-events-wiring, action-policy-postures, autoscaling-signals, evolution-publication, mcp-consumption
- Architecture deep dives: state externalization, engine sharding, gateway scaling, agent dispatch, fleet autonomy, graph service layer
- Sizing: capacity model