Assemblio runs as a control plane plus N per-agent data planes, scheduled by Nomad.
Control plane (singleton):
apps/controller: identity (Better Auth), agent registry, lifecycle API, fleet management UI, per-agent console proxy; the only service that talks to Nomad- shared infrastructure: Nomad, Consul, Traefik, OpenBao, MinIO
- single SQLite file (
controller.sqlite) holds Better Auth tables (user, session, account, verification) plus controller-owned tables (agent, membership) — FK constraints withON DELETE CASCADEkeep ownership consistent
Per-agent data plane (one per agent, scheduled as a Nomad job named agent-{shortId}, where shortId is the first 8 hex chars of the agent's UUID):
apps/agent: stateful agent runtime, tool execution, session stateapps/edge: this agent's public channel ingress, webhook handling, attachment fetches, proactive delivery, per-agent operator consoleworker: sandbox boundary for shell, read, write, and edit (separate task, co-scheduled withagent, shares the alloc directory)
Multi-tenancy:
- Operators authenticate to the controller via Better Auth (email magic link, Google OAuth, GitHub OAuth). The first user pinned by
BOOTSTRAP_ADMIN_EMAILbecomes admin. - Agents are namespaced per-user:
(ownerSlug, name)is unique, notnameglobally — soalice/supportandbob/supportcoexist. - Public path scheme:
/{ownerSlug}/{name}/.... Console traffic (/{ownerSlug}/{name}/console/*) is intercepted by Traefik and routed through the controller for auth + membership checks before being forwarded to the per-agent edge over the shared Docker network. - Webhooks (
/{ownerSlug}/{name}/webhooks/*) hit the per-agent edge directly via Consul-registered routes, since the public surface they serve doesn't carry an operator session.
Shared packages:
packages/core: runtime config, storage, credentials, metrics, Composio primitivespackages/internal-api: signed request/response contracts between runtimespackages/console-ui: UI assets — fleet UI bundled into controller, per-agent UI bundled into edge
See docs/architecture/multi-agent-orchestration.md for the full topology, routing rules, secrets layout, and lifecycle flows.
apps/*may depend onpackages/*.packages/*must not depend onapps/*.- Public ingress stays in
apps/edge; stateful conversation execution stays inapps/agent; fleet orchestration stays inapps/controller. - Any cross-runtime payload, signer, or transport contract belongs in
packages/internal-api. - Shared business logic, config readers, storage helpers, and integration clients belong in
packages/core.
- A public surface sends an event to
https://host/{ownerSlug}/{name}/webhooks/.... - Traefik routes the request to the
edgetask in theagent-{shortId}Nomad job (via Consul service discovery), stripping the/{ownerSlug}/{name}prefix so the edge sees the bare path. apps/edgeresolves the target session and signs an internal request to the co-locatedapps/agent.apps/agentexecutes the turn, calling upstream model providers with that agent's credentials andworkerfor any shell/file work.apps/edgedelivers the reply back to the origin surface.
Console traffic (/{ownerSlug}/{name}/console/*) takes a different path: Traefik's higher-priority file rule routes it to the controller, which validates the operator's session + membership and then forwards directly to the per-agent edge over the shared Docker network (http://edge-{shortId}:3100).
- Each per-agent stack owns one logical agent definition end-to-end. The
bySurfaceindirection from the previous single-tenant model is gone. apps/agentstill owns local worker-agent definitions (planner,explorer,architect) and a localmainfallback for TUI sessions.- Signed turn requests carry the resolved top-level agent definition from
edgetoagent; local worker-agent definitions are not fetched over that boundary.
- Nomad alloc directory holds two per-agent roots:
AGENT_CONFIG_DIR(editable defs — skills/workflows/crons/brain, synced bidirectionally with S3) andAGENT_STATE_DIR(agent work files — sessions/plans/todos/logs/workflow-runs/auth, synced for durability). - OpenBao stores persisted runtime configuration, namespaced per-agent under the owner slug:
secret/data/shared/credentialsandsecret/data/shared/setup— infra (S3, Langfuse, internal HMAC secret)secret/data/agents/{ownerSlug}/{name}/credentialsandsecret/data/agents/{ownerSlug}/{name}/setup— per-agent (channel tokens, LLM keys, channel/integration config)
- S3-compatible storage (MinIO in dev) is shared. Per-agent data is namespaced by prefix:
agents/{ownerSlug}/{name}/.... - Controller-side identity, agent registry, and memberships live together in
controller.sqliteunder the controller's state dir.
- Repo-local markdown is the system of record for architecture, reliability, security, and execution plans.
- Hidden runtime state under
.agent/can support execution, but should not be the only place a durable decision lives. - Architectural changes should update this file and any linked docs in
docs/.
Use the focused architecture docs in docs/architecture/ for the durable system seams: