Architecture
ai-agent-subsystem is built as a single dub monorepo: reusable code lives in a shared library and
the executables are thin layers on top, all under packages/. It produces three binaries and
one shared library.
flowchart TB
subgraph MONO["D monorepo (dub)"]
direction TB
CORE[["agentcore<br/>shared library"]]
CTRL["controller<br/>binary"]
INIT["initializer<br/>binary"]
SUP["supervisor<br/>binary"]
CORE --> CTRL
CORE --> INIT
CORE --> SUP
end
CTRL <-->|watch / create / patch| K8S[("Kubernetes API")]
CTRL <-->|"batch: submit / poll / cancel / delete"| VENDOR[("Vendor APIs<br/>Anthropic, Gemini, OpenAI")]
CTRL -->|"model catalog: list models"| VENDOR
CALLER(["caller"]) -->|"realtime prompts: HTTP :8082"| CTRL
CTRL -->|"forwards to the supervisor: :8090"| POD
INIT -->|init container, prepares| POD["Agent Pod"]
SUP -->|runs as PID 1 inside| POD
STATIC["statically linked with LDC, no runtime deps"] -.-> CTRL
STATIC -.-> INIT
STATIC -.-> SUP
Components
Section titled “Components”agentcore: shared library
Section titled “agentcore: shared library”The pure core, with no process of its own:
- CRD type definitions (
AgentDefinition,Station,Agent,ModelCatalog,AgentSpawner). - AgentSpawner scheduling (
agentcore/spawner): five-field cron parsing and next/previous fire times, when a spawner is due, and the Agent it creates; for GitHub triggers, webhook signature checks, event matching and the Agent a delivery creates. - A Kubernetes REST + watch client.
- The pure reconcile state machine (I/O injected, so it is unit-testable).
- Prompt templating.
- The Job builder.
- Batch providers (
agentcore/batch): the Anthropic, Gemini and OpenAI batch adapters and the translation of a batch result into the event lines of the vendor’s CLI, with HTTP injected so tests run on fixtures. - The model catalog refresh (
agentcore/models): the vendors’ list-models calls and the build of theModelCatalogstatus from them. - The shared vendor routing (
core/vendor.d) that both CLI selection and batch selection use. - Realtime session support: the per-vendor session argv (
vendors/realtime.d, with Claude’s stdin session invendors/claude/agent.d), the session-id parsing (output/session.d) and the per-run prompt token (core/token.d).
controller: binary 1
Section titled “controller: binary 1”The operator. It watches Agent resources, resolves each one’s Station and AgentDefinition,
builds and creates a Job, polls the Job’s outcome, and patches the Agent’s status. It also
prunes old runs beyond the Station’s history limits, and exposes /healthz (liveness), /readyz
(readiness), plus a Prometheus /metrics endpoint (reconcile counts and latency, agents by phase,
watch reconnects, Kubernetes API latency, and leader status).
For an AgentDefinition with execution: batch (opt-in, enabled by the components/batch kustomize
component) it creates no Job. It submits the prompt itself to the batch API of the model’s vendor
(Anthropic, Gemini or OpenAI), polls the batch at most every BATCH_POLL_SECONDS (default 60),
records the result in status.output, and on batch.timeoutMinutes either fails the Agent or falls
back to an ordinary Job. An agents.re-cinq.com/batch-cleanup finalizer makes sure a deleted Agent’s
batch is cancelled and deleted at the provider. See batch runs and the
controller lifecycle.
With the opt-in components/model-catalog component the leader replica also keeps the default
ModelCatalog current (catalogloop.d): right after it wins the
Lease, then every MODEL_CATALOG_REFRESH_SECONDS (default 3600), it lists the models each vendor’s
API reports for the keys the AgentDefinitions map and writes them to the catalog’s status.
The leader also runs AgentSpawners (spawnerloop.d): every 15
seconds it lists them, creates an Agent for each one whose schedule came due, and records the slot in
the spawner’s status, so a new leader carries on from there.
With the opt-in components/realtime component every replica, leader or not, also serves the
prompt API on port 8082 (promptapi.d). It forwards each prompt or close
for a running realtime session to the run pod’s supervisor on port
8090, signed with that run’s own token, and records a newly revealed status.sessionId.
With the opt-in components/webhooks component every replica also serves the GitHub webhook
receiver on port 8083 (webhook.d). It verifies each delivery against the HMAC key its AgentSpawner
names and creates an Agent named after the delivery, so a redelivery, or the same delivery reaching
two replicas, starts one run. See Trigger runs from GitHub.
It combines a low-latency watch with a ~15s poll over one shared in-memory cache, so reconcile work is O(changed) and a dropped watch event is still caught by the next poll. Two replicas run with Lease-based leader election, so only the leader reconciles. See Watch + poll + cache and leader election for the mechanics.
initializer: binary 2
Section titled “initializer: binary 2”Runs as the Pod’s init container, before the supervisor. It provisions the agent’s environment
from what the recipe declares: cloning the resources.repos into the workspace and installing the
agent CLI (e.g. Claude via the official installer), self-bootstrapping any missing prerequisites
(git, bash, curl, sha256sum, base64) through the distro’s package manager first, and reporting its lifecycle to the
same output sinks as the agent. New provisioning tools and distros are added behind the Tool and
PackageManager interfaces. See Agent runtime for the full model.
supervisor: binary 3
Section titled “supervisor: binary 3”Runs inside the Job Pod as the entrypoint. It launches the agent process, streams its stream-json
output line by line to the configured sinks, forwards termination signals for graceful shutdown,
and exits with the agent’s exit code. For a realtime session it instead serves prompts on port 8090,
runs them one turn at a time (on one Claude process’s stdin, or one process per turn for the other
vendors) and ends the session when it is closed, idles out or is signalled. See
Agent runtime.
Static linking
Section titled “Static linking”All three binaries are compiled with LDC with the D runtime linked statically, so they ship as self-contained executables with no language runtime to install. The initializer and supervisor can be injected into any glibc-based Station image, and run unchanged across the common Kubernetes base distros (Debian, Ubuntu, RHEL-family, Amazon Linux, Alpine). See Building for the dub configuration and link flags.
Kubernetes as the control plane
Section titled “Kubernetes as the control plane”There is no external database. The controller’s entire state is the set of Agent resources and
their status. This keeps the system observable with plain kubectl and recoverable after a
restart: on startup the controller simply lists Agents and reconciles whatever it finds.