Skip to content

Services

This page gives the ports, routes, health checks, and scaling rules of each service. For what each service does, see the overview.

Port assignments

Each service uses the same port in every topology. In all mode the services bind these ports inside one container, and only Caddy is published.

Process Port Protocol Notes
Caddy 8080 HTTP The public port
Caddy admin API 2019 HTTP Bound to localhost only
imbi-api 8000 HTTP Also serves /metrics
imbi-mcp 8001 HTTP (streamable HTTP)
imbi-assistant 8002 HTTP
imbi-gateway 8003 HTTP Also serves /metrics
imbi-slackbot 8004 HTTP Health check only
imbi-scheduler 8005 HTTP Also serves /metrics
imbi-workflows 8006 HTTP Health check only
Apache Iggy 8090 TCP The services connect here (IGGY_URL)
Apache Iggy 3000 HTTP The Iggy HTTP API
iggy-connect 8081 HTTP The connectors runtime API and its /metrics

Iggy and iggy-connect are separate containers. The Iggy ports are the ones that compose.yaml sets.

URL routing

Caddy sends each request to a service by its path. It checks the routes in this order, so a more specific route comes before a general one:

Path Goes to Prefix
/api/* imbi-api kept
/status imbi-api kept
/assistant/* imbi-assistant kept
/docs, /docs/* imbi-api (API reference page) kept
/gateway/metrics* none: 404
/scheduler/metrics* none: 404
/gateway/* imbi-gateway removed
/scheduler/* imbi-scheduler removed
/mcp/.well-known/oauth-authorization-server* imbi-api /mcp removed
/mcp, /mcp/* imbi-mcp kept
/.well-known/oauth-protected-resource* imbi-mcp kept
/.well-known/oauth-authorization-server* imbi-api kept
/openapi.json imbi-api kept
/uploads/* imbi-api kept
*.map UI source maps, only when SERVE_SOURCE_MAPS is true. Otherwise 404
everything else UI static files, with index.html as the fallback

Caddy does not cache /index.html and /version.json. It fills in index.html as a template at serve time, which is how VITE_API_URL gets to the UI.

The prefix rule of each service must match how the service mounts its routes:

  • Kept for imbi-api, imbi-assistant, and imbi-mcp. imbi-api and imbi-assistant mount their routes under the path of their public URL (IMBI_API_URL, IMBI_ASSISTANT_URL), so they expect the prefix. The OAuth and MCP discovery documents need their full paths for the same reason.
  • Removed for imbi-gateway, which mounts at the root, and for imbi-scheduler, which mounts under its own IMBI_SCHEDULER_API_PREFIX (default /api). Thus a caller gets to the scheduler API at /scheduler/api/tasks.

MCP clients look for the authorization server metadata under the MCP resource URL, at /mcp/.well-known/oauth-authorization-server. imbi-api serves that document at the root, so Caddy removes /mcp for this one path.

/status has its own route because imbi-api serves its status route under the path of IMBI_API_URL. When IMBI_API_URL has no path (the chart default), the route is /status, which /api/* does not match. Without the /status route, the request goes to the UI, which returns 200 from index.html when imbi-api is down. The liveness and readiness probes then pass for a dead API.

imbi-slackbot and imbi-workflows have no route. imbi-slackbot connects out to Slack in socket mode. imbi-workflows takes runs from Postgres and calls imbi-api and imbi-scheduler. The port of each exists only for its health check.

Caddy trusts X-Forwarded-* headers from private address ranges. Thus a load balancer in front of Caddy can pass the original scheme and host to the services. imbi-api uses them to make the URLs in its OAuth and MCP discovery documents.

Health checks

Each service serves GET /status:

Service Path on the service Through Caddy
imbi-api /status under the path of IMBI_API_URL, for example /api/status /api/status, or /status when IMBI_API_URL has no path
imbi-assistant /status (no prefix) /assistant/status
imbi-gateway /status /gateway/status
imbi-mcp /status /mcp/status
imbi-scheduler /status (no prefix) /scheduler/status
imbi-slackbot /status No route
imbi-workflows /status No route
iggy-connect none No route. Use a TCP check on port 8081

The Helm chart uses these probes for each service.mode:

service.mode Probe
all, api, ui HTTP GET on the imbi-api status path, through the container port
scheduler, slackbot, workflows HTTP GET /status
assistant, gateway, mcp TCP connect on the container port

In ui mode the probe goes through Caddy to imbi-api. Thus a ui pod is not ready when imbi-api is down.

Scaling

In all mode, every service runs as a process in one container. For production, set IMBI_SERVICE to run one service in each container. Then scale each one by itself, behind a Caddy container in ui mode.

Service How it scales
imbi-api Horizontal. The background workers read Valkey streams in consumer groups, so replicas share the work. The daily scoring tick takes a Valkey lock. The runner lease sweep uses row locks, so two replicas can run it at the same time
imbi-assistant Horizontal. Conversations are in the graph, not in the process
imbi-gateway Horizontal. Each delivery is one request
imbi-mcp Not verified. FastMCP keeps streamable HTTP sessions in process memory unless it runs in stateless mode, and imbi-mcp does not set stateless mode. Use one replica, or session affinity, until this is verified
imbi-scheduler Horizontal. Firings are claimed with FOR UPDATE SKIP LOCKED, so replicas take different tasks, and only one replica claims an occurrence
imbi-slackbot Horizontal. Socket mode gives each replica its own connection, and Slack sends an event to one of them
imbi-workflows Horizontal. Runs are claimed with FOR UPDATE SKIP LOCKED and a lease, so replicas take different runs
Caddy (ui) Horizontal. It holds no state
iggy-connect One replica (the chart default). Each topic has one partition, and in a consumer group one member reads a partition. It reads its configuration only at startup. See Events and analytics

imbi-harness

imbi-harness is the agent runner. It is not in the all-in-one image, and it has no IMBI_SERVICE mode. It has its own image (ghcr.io/aweber-imbi/imbi-harness) and its own Helm chart (helm/imbi-harness). One chart release is one runner pool. The runners connect out to imbi-api to claim agent tasks, so imbi-api needs no route to them.

The chart has two pool modes (pool.mode):

  • deployment: a Deployment with a fixed number of pods (pool.replicas). Each pod runs one chain of tasks, then deletes itself. The ReplicaSet then starts a clean pod.
  • scaledJob: KEDA, which you install separately, starts one Job for each chain that the pool can claim. Each Job runs one chain and stops.

See Agent Runners.