Deployment Topology

How Hoziron deploys in production — the unified hoziron-server binary with pluggable surfaces, the standalone registry option, container structure, networking, storage, and scaling. This document is the conceptual/architectural view; step-by-step deployment instructions live in docs/guides/deployment/.

Two Deployment Shapes

Hoziron ships one binary, hoziron-server, that can run as either shape:

  • Unified (the default): one hoziron-server process serves every enabled surface — API, package registry, MCP, operations console/dashboard — selected via --surfaces or the [surfaces] block in config.toml. This is the common case: single host/container, single data volume, simplest operational model.
  • Split: the package registry runs as its own deployment — same hoziron-server binary, invoked with --surfaces registry — with its own scaling, its own storage (local disk or S3-compatible object storage backend), and its own auth/key store. This is the shape for a shared catalog that must scale independently of any single carrier's agent runtime, or for a DMZ/CDN-origin placement that should not co-locate with the API surface. hoziron-registry's standalone-auth capability (bootstrap its own admin key, manage its own keys.db) is exactly what makes this split possible without a co-located API daemon (ADR-024).

Every surface — including a split-out registry — is served by exactly the same hoziron-server binary and the same underlying hoziron-core/kernel stack; splitting is a deployment-topology choice, not a different build.

Single-Node Process Architecture

Whichever shape is chosen, each hoziron-server instance is single-process, single-node:

The hoziron-cli CLI is not part of this diagram at all — it is a separate, stateless binary (bin/hoziron-cli) that speaks plain HTTP to whichever hoziron-server instance(s) an operator points it at; it carries no server or kernel dependency and deploys independently (typically just distributed to operator workstations, not containerized alongside the server).

Container Deployment

Container Details

PropertyValue
Base imagegcr.io/distroless/cc-debian12:nonroot
UserUID 65532 (nonroot)
Entrypointhoziron-server (default CMD: --surfaces api,registry)
Port4200 (API + metrics on same port)
Volume/data (all persistent state)
Read-only filesystemYes (except /data)
Health probeGET /health

Environment Variables

VariablePurposeContainer Default
HOZIRON_HOMEData directory/data
HOZIRON_LOGLog filterinfo
HOZIRON_LOG_FORMATLog format (json or text)text (recommend json for production)
ANTHROPIC_API_KEYProvider key— (inject from secrets)

There is no HOZIRON_LISTEN environment variable — the bind address is set per surface via --api-listen/--registry-listen/--mcp-listen/--dashboard-listen or [surfaces.<name>].listen in config.toml (see TLS and networking).

Network Topology

Container / Docker

Bare Metal / VM

For bare metal, enable native TLS if no reverse proxy (set per surface, on whichever surface owns the listener — see TLS and networking):

[surfaces.api.tls]
enabled = true
cert_path = "/opt/hoziron/tls/cert.pem"
key_path = "/opt/hoziron/tls/key.pem"

Storage Layout

/data/                          (HOZIRON_HOME in containers)
├── config.toml                 Platform configuration
├── .env                        API keys (local dev only)
├── hoziron.db                  Main SQLite database
├── packages.lock               Dependency lockfile
├── data/
│   ├── memory/                 Per-agent KV stores
│   └── sessions/               Conversation histories
├── auth.db                     API key store (mode 0600)
├── audit.db                    Audit trail (WAL mode)
├── packages/                   Installed catalog packages
│   ├── claims-intake/
│   ├── document-ocr/
│   └── installed.json          Installation metadata
├── keys/                       Signing keypair
│   └── default.key             Ed25519 private key (mode 0600)
├── vault/                      Encrypted credentials
└── agents/                     Auto-load manifest directory
    ├── claims-processor.toml
    └── monitor-agent.toml

Storage Sizing

ComponentGrowth PatternTypical Size
hoziron.dbPer-agent, per-session10 MB – 1 GB
data/memory/Per-agent, per-message1 MB per 1000 messages
audit.dbPer-API-request~100 bytes/entry, 100K entries max
packages/Per-install10 KB – 5 MB per package
auth.dbPer-key< 1 MB

Scaling Considerations

Hoziron is single-node by design. Scale by increasing resources:

DimensionScale Factor
More agentsMore memory (10 MB base per agent)
More concurrent requestsMore CPU cores (tokio async)
Longer historyMore disk (session storage)
More integrationsMore file descriptors (MCP processes)

Horizontal Isolation

For multi-team environments, run separate instances:

Each instance has:

  • Its own config, agents, competencies, and data
  • Shared registry access (install same packages)
  • Shared IdP (same SSO, different role mappings)
  • Shared LLM providers (separate API keys if needed)

High Availability

Hoziron does not natively support active-active HA (single-process, SQLite). For high availability:

ApproachHowTrade-offs
Process supervisor restartsystemd/Docker restart: always + health probesSeconds of downtime on crash
Warm standbySecond instance with shared storage (active-passive)Manual failover
Stateless agentsRecreate from manifests on new instanceLose in-flight sessions

For most enterprise deployments, automatic process restart with graceful shutdown (30s grace period) provides sufficient availability.

Graceful Shutdown

Give the process at least 30 seconds between SIGTERM and a hard kill (e.g. systemd's TimeoutStopSec, or Docker's --stop-timeout) to allow the flush to complete.

Split-Registry Network Topology

When the registry is split out, it is a second, independent deployment with its own address, its own storage (local disk or an S3-compatible bucket via config.s3.*), and its own auth store — it does not share hoziron.db, auth.db, or the audit chain with the API-serving hoziron-server instance(s):

Cross-registry auth is per-registry token: the catalog client reads auth_token_env per configured registry entry and sends it as a Bearer header — there is no shared identity between the API-serving instance and a split-out registry beyond that token.


Related: