Architecture at a glance
The service map for engineers extending Swarm, covering what each component does, where it lives in the repo, and which ADR explains why.
After reading this page you'll know which component owns each stage of a bee's run and where to find it in the repo. This page is a map, not the territory. Every load-bearing decision has an ADR in docs/adrs/, and that directory, not this page, is the record of why things are the way they are.
The service map
Every run moves left to right through the same pipeline:
triggers → orchestrator (router + Inngest queue) → runner → agent container → GitHub PR| Stage | What it does | Where it lives |
|---|---|---|
| Triggers | Always-on services that accept external events: Slack mentions, Jira webhooks, GitHub comments, Semgrep tickets, generic HMAC webhooks. Each validates the event (HMAC signature, required fields), writes it to the durable trigger_inbox for dedup, then dispatches to the router. | triggers/slack/, triggers/jira/, triggers/github/, triggers/semgrep-jira/, triggers/generic-webhook/; shared code in triggers/lib/ |
| Orchestrator | One Express service hosting two things: the router (classifies the request: an explicit payload.bee in the catalog takes that recipe's shortcut, anything else falls through to the swarm agent) and the Inngest serve endpoint at /api/inngest, where durable workflow functions like run-bee-job and launch-swarm-agent execute. | orchestrator/; router in orchestrator/router/, functions in orchestrator/functions/ |
| Runner | Launches the agent workload in an isolated container. Pluggable via AGENT_RUNNER: docker run locally, ecs:RunTask on Fargate in production. | triggers/lib/agent-runner.js |
| Agent runtime | The code that runs inside the container: bee-runtime/ is the in-container harness for recipe bees; swarm-agent/ is the default general agent, built on the Claude Agent SDK. Recipes are YAML in recipes/. | bee-runtime/, swarm-agent/, recipes/ |
| GitHub | The output surface. The run ends in a pull request (or an explicit verdict/failure; see how Swarm thinks). No human writes code. A human always reviews. | n/a |
Two components sit alongside the pipeline rather than in it: the dashboard (dashboard/, Next.js + Postgres/SQLite) receives job lifecycle events from triggers/lib/job-tracker.js and fails silently if it's down, and the Terraform for all of it lives in infra/.
The runner abstraction
ADR 0001. Both runner implementations expose the same async run(...) interface, and callers never branch on runner type. docker is the default so a fresh clone works with docker compose up and no AWS config; production sets AGENT_RUNNER=ecs. The split exists for security as much as portability: production services never touch a Docker socket, and the agent's own task role deliberately lacks ecs:RunTask, so an escaped agent cannot spawn more agents. Adding a new backend means a new implementation behind the same interface, not changes to callers.
GitHub auth
ADR 0002. Per run, the trigger layer signs a GitHub App JWT, exchanges it for an installation access token (1-hour lifetime, scoped to the installation, contents:write + pull_requests:write + metadata:read), and injects it into the agent container as GITHUB_TOKEN at launch; it is never baked into the image or task definition. PRs are authored by the App's bot identity, so agent PRs are auditable at a glance. A PAT fallback (GITHUB_TOKEN set directly, App vars unset) is kept for local dev. Implementation: triggers/lib/github-auth.js.
AWS topology in one paragraph
ADR 0038. Everything is Terraform (infra/environments/ per environment, infra/modules/ shared), running in eu-west-2: an ECS cluster with the always-on trigger and orchestrator services, on-demand Fargate tasks for agent runs, an ALB + WAF fronting the webhook ingress (Slack uses socket mode, so no ingress), Secrets Manager for credentials, and per-task IAM roles. Inngest is self-hosted on ECS with RDS Postgres, a residency requirement, so no workflow state leaves the VPC. Staging is deliberately single-AZ to keep the pre-prod loop cheap and fast.
Where the decisions live
If you're about to change how any of this fits together, read the relevant ADR first and write a new one if you're changing a decision. The index is docs/adrs/README.md. The repo's CLAUDE.md defines what counts as architecturally significant.