Skip to content

Latest commit

 

History

History
81 lines (56 loc) · 5.76 KB

ARCHITECTURE.md

File metadata and controls

81 lines (56 loc) · 5.76 KB

Architecture

System Shape

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 with ON DELETE CASCADE keep 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 state
  • apps/edge: this agent's public channel ingress, webhook handling, attachment fetches, proactive delivery, per-agent operator console
  • worker: sandbox boundary for shell, read, write, and edit (separate task, co-scheduled with agent, 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_EMAIL becomes admin.
  • Agents are namespaced per-user: (ownerSlug, name) is unique, not name globally — so alice/support and bob/support coexist.
  • 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 primitives
  • packages/internal-api: signed request/response contracts between runtimes
  • packages/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.

Dependency Rules

  • apps/* may depend on packages/*.
  • packages/* must not depend on apps/*.
  • Public ingress stays in apps/edge; stateful conversation execution stays in apps/agent; fleet orchestration stays in apps/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.

Request Flow

  1. A public surface sends an event to https://host/{ownerSlug}/{name}/webhooks/....
  2. Traefik routes the request to the edge task in the agent-{shortId} Nomad job (via Consul service discovery), stripping the /{ownerSlug}/{name} prefix so the edge sees the bare path.
  3. apps/edge resolves the target session and signs an internal request to the co-located apps/agent.
  4. apps/agent executes the turn, calling upstream model providers with that agent's credentials and worker for any shell/file work.
  5. apps/edge delivers 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).

Agent Definition Ownership

  • Each per-agent stack owns one logical agent definition end-to-end. The bySurface indirection from the previous single-tenant model is gone.
  • apps/agent still owns local worker-agent definitions (planner, explorer, architect) and a local main fallback for TUI sessions.
  • Signed turn requests carry the resolved top-level agent definition from edge to agent; local worker-agent definitions are not fetched over that boundary.

Storage Boundaries

  • Nomad alloc directory holds two per-agent roots: AGENT_CONFIG_DIR (editable defs — skills/workflows/crons/brain, synced bidirectionally with S3) and AGENT_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/credentials and secret/data/shared/setup — infra (S3, Langfuse, internal HMAC secret)
    • secret/data/agents/{ownerSlug}/{name}/credentials and secret/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.sqlite under the controller's state dir.

Documentation Rules

  • 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/.

Architecture Detail

Use the focused architecture docs in docs/architecture/ for the durable system seams: