Architecture Overview¶
Imbi is a set of Python services, a React UI, and a data plane. The
services and the UI are in one container image
(ghcr.io/aweber-imbi/imbi). The agent runner, imbi-harness, has its own
image and runs outside Imbi, in runner pools.
Platform diagram¶
flowchart TB
browser[Browser]
ext[GitHub, PagerDuty,<br/>other webhook senders]
mcpc[MCP clients]
slack[Slack]
subgraph edge[Edge]
caddy[Caddy :8080<br/>UI static files and reverse proxy]
end
subgraph services[Services]
api[imbi-api :8000]
assistant[imbi-assistant :8002]
gateway[imbi-gateway :8003]
mcp[imbi-mcp :8001]
scheduler[imbi-scheduler :8005]
workflows[imbi-workflows :8006]
slackbot[imbi-slackbot :8004]
end
subgraph data[Data plane]
pg[(PostgreSQL + Apache AGE)]
iggy[(Apache Iggy)]
conn[iggy-connect]
ch[(ClickHouse)]
s3[(S3-compatible storage)]
valkey[(Valkey)]
smtp[SMTP server]
end
subgraph runners[Runner pools, outside the image]
harness[imbi-harness runners]
end
browser --> caddy
ext --> caddy
mcpc --> caddy
caddy --> api & assistant & gateway & mcp & scheduler
slackbot <-- socket mode --> slack
assistant & gateway & mcp & scheduler & workflows & slackbot --> api
workflows --> scheduler
scheduler --> gateway
harness -- claim tasks --> api
harness -- Imbi tools --> mcp
api & assistant & gateway & scheduler & workflows & slackbot --> pg
api & gateway & scheduler & workflows -- publish --> iggy
iggy --> conn --> ch
conn -. sink configuration .-> api
api & gateway & scheduler -- query --> ch
api --> s3 & valkey & smtp
The diagram has three zones and the runners:
- Edge. Caddy is the only public port. It serves the UI files and sends each path prefix to one service. See URL routing.
- Services. Each service is a separate process. All of them use the
imbi-commonlibrary. imbi-slackbot and imbi-workflows have no public route. - Data plane. Postgres holds the catalog. Every ClickHouse write goes through Iggy and iggy-connect. See Events and analytics.
- Runner pools. imbi-harness runners connect out to imbi-api to claim agent tasks. imbi-api never connects to a runner. See Agent runtime.
The diagram does not show every call. imbi-api, imbi-assistant, imbi-slackbot, and imbi-workflows also call the Anthropic API, and the plugins call their remote systems (GitHub, PagerDuty, SonarQube, AWS, and others).
Services¶
| Service | What it does | More |
|---|---|---|
| imbi-api | The REST API: the catalog, delivery, documents, scoring, agents, workflows, secrets, authentication, and authorization. It also runs the background workers (see below) | API |
| imbi-assistant | The AI assistant in the UI. It streams Claude responses over Server-Sent Events. Its tools are the imbi-api operations (with the user's token), external MCP servers, and UI actions | Assistant |
| imbi-gateway | Receives webhooks at /gateway/notifications/{webhook_id}. It records each delivery, evaluates the webhook rules, and runs the plugin actions that match |
Gateway |
| imbi-mcp | An MCP server. It makes MCP tools from the imbi-api OpenAPI document and sends each call to imbi-api with the token of the caller | MCP |
| imbi-scheduler | Fires scheduled tasks (cron, interval, one-shot) as one HTTP call to imbi-api or imbi-gateway | Scheduler |
| imbi-workflows | The workflow engine. It runs the steps of workflow runs that imbi-api creates, and keeps the scheduler tasks and gateway rules of workflow triggers | Workflows |
| imbi-slackbot | A Slack bot with the same tools as the assistant. It acts as the Imbi user who wrote the message | Slackbot |
| imbi-harness | The agent runner. It is not in the image. One Helm release of helm/imbi-harness is one runner pool |
Agent runners |
Background workers in imbi-api¶
imbi-api runs these workers in its own process. Each one reads a Valkey stream as part of a consumer group, so the replicas of imbi-api share the work:
- Score recompute, and a daily scoring tick
- Commit and tag sync, and pull request sync
- Deployment sync, and the release promote watcher
- Maintenance operations
- Identity token refresh
- Document read session sweeper
If imbi-api cannot get a Valkey client at startup, it does not start these workers. The API itself continues to run.
imbi-api also runs a pool of processes that render prompts, and it sweeps the expired leases of runners. See Agent runtime.
imbi-common¶
The shared library. All the Python services use it. It contains the
graph client (Postgres and Apache AGE), the ClickHouse query client, the
Iggy publisher, the Valkey client, the domain models, the settings classes,
authentication helpers, the plugin registry and plugin contracts, the
prompt render pool, the LLM helpers, the workflow node registry, the
/metrics route, the access log, and Sentry setup. See the
library reference.
Data stores¶
PostgreSQL and Apache AGE¶
PostgreSQL with the Apache AGE extension is the primary data store. The graph holds the catalog: organizations, teams, users, roles and permissions, projects and project types, environments, releases, deployments, blockers, documents, integrations, webhooks and webhook rules, agents, and prompts.
These services also keep relational schemas in the same database:
| Schema | Owner | Holds |
|---|---|---|
agent_runtime |
imbi-api | Agent task state (ADR 0020) |
workflows |
imbi-api | Workflow definitions and trigger bindings (ADR 0021) |
workflow_runtime |
imbi-api | Workflow runs and their steps |
secrets |
imbi-api | Secrets broker providers, bindings, and resolutions. No secret material (ADR 0022) |
harness |
imbi-api | Runner pools and dispatch |
scheduler |
imbi-scheduler | Task definitions and trigger state. IMBI_SCHEDULER_SCHEMA sets the name |
ClickHouse¶
The analytics store: the operations log, events, commits, tags, pull requests, drift verdicts, score history, SBOM components, document versions and reads, scheduler runs, agent task events and usage, workflow runs, email audit, maintenance log, and secret audit events. The services read from it. Only iggy-connect writes to it. See Events and analytics.
Apache Iggy and iggy-connect¶
Iggy is a message stream server. Each ClickHouse table has one Iggy
stream. iggy-connect is the Iggy connectors runtime from the same image
(ghcr.io/aweber-imbi/iggy, with IGGY_MODE=connectors). It drains each
stream into its table. imbi-api, imbi-gateway, and imbi-scheduler do not
start without Iggy.
S3-compatible storage¶
Holds uploaded files. imbi-api creates the bucket at startup, and it does
not start if it cannot reach the storage. Caddy sends /uploads/* to
imbi-api.
Valkey¶
A Redis-compatible store. Only imbi-api uses it: for the work queues of
the background workers, the identity refresh sweeper, and document read
sessions. Set VALKEY_URL. The default is valkey://localhost:6379/0.
SMTP¶
imbi-api sends email (welcome and password reset messages, and workflow email steps) through an SMTP server. Email is on by default. For the settings, see API configuration.
Deployment topologies¶
The IMBI_SERVICE variable selects what one container runs:
| Topology | IMBI_SERVICE |
What runs |
|---|---|---|
| All in one | all (default) |
Every service and Caddy in one container. imbi-slackbot starts only when SLACK_APP_TOKEN, SLACK_BOT_TOKEN, and ANTHROPIC_API_KEY are set |
| Split | api, assistant, gateway, mcp, scheduler, workflows, or slackbot |
One service for each container, so that you can scale each one |
| Edge only | ui |
Caddy only. It serves the UI files and sends each route to the service named in IMBI_API_UPSTREAM, IMBI_ASSISTANT_UPSTREAM, IMBI_GATEWAY_UPSTREAM, IMBI_MCP_UPSTREAM, and IMBI_SCHEDULER_UPSTREAM (each a host:port) |
| Runner pool | none (separate image) | ghcr.io/aweber-imbi/imbi-harness, installed with the helm/imbi-harness chart, one release for each pool |
A split install uses the ui topology as its router. In all mode, the
upstreams default to the loopback, so you set none of them.
In all mode, the entrypoint makes the service account credentials of
imbi-scheduler, imbi-workflows, and imbi-gateway when you do not supply
them. In a split install, you must supply them. iggy-connect is always a
separate container.
For the Helm chart (service.mode) and the variables that each mode
needs, see Kubernetes and
Docker.
Observability¶
Prometheus metrics¶
imbi-api, imbi-gateway, and imbi-scheduler serve GET /metrics on the root
of their own port, outside the API prefix. The route has no
authentication. Caddy does not send it to the public:
/api/*keeps its prefix, and the imbi-api/metricsis not under it./gateway/metrics*and/scheduler/metrics*return 404 at Caddy.
Prometheus must scrape the pods directly. The Helm chart adds the
prometheus.io/* annotations. In all mode a pod can name one scrape
port, so the chart scrapes imbi-api on port 8000 only. iggy-connect serves
its metrics on port 8081, and the chart annotates that Deployment too.
imbi-assistant, imbi-mcp, imbi-workflows, and imbi-slackbot serve no metrics.
Sentry¶
imbi-api, imbi-assistant, imbi-mcp, and imbi-slackbot send errors to Sentry
when SENTRY_DSN is set. SENTRY_TRACES_SAMPLE_RATE turns on tracing (the
default is 0.0). In all and ui modes, the entrypoint uploads the UI
source maps when SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT
are set. Caddy serves the .map files only when SERVE_SOURCE_MAPS is
true.
OpenTelemetry¶
imbi-common and imbi-api have an otel extra. The imbi meta-package
depends on imbi-common[otel], so the container image installs the
OpenTelemetry packages. But no service starts the OpenTelemetry SDK, and
the entrypoint does not run the services under opentelemetry-instrument.
If you run a service with OpenTelemetry instrumentation, the access log
adds the trace ID of each request.
Access logs¶
imbi-api, imbi-gateway, imbi-scheduler, and imbi-mcp write an access log
line for each request, with the principal when it is known. They do not
log health check and /metrics requests.