| AuthHeaderMiddleware | Validates the UCAN delegation (signature, expiry, audience, capability). Resolves the user’s DID and attaches RuntimeUserContext. Failure → 401, agent never runs. |
| SubscriptionMiddleware | Only when the credits plugin is loaded. Checks user balance. Empty + DISABLE_CREDITS !== 'true' → 402. |
| ThrottlerGuard | Per-user rate limit, registered as a global Nest guard (APP_GUARD), not an HTTP middleware. Nest runs middleware before guards, so it still gates after auth and subscription. Breach → 429. |
| MessagesController | Reads the inbound message, looks up the session (creates on first contact), hands off to the agent builder. Returns SSE. |
| createMainAgent | Builds the per-request RuntimeContext, reads cached registries, runs getRequestTools / getRequestSubAgents, composes the prompt, returns a LangChain agent compiled against the per-user checkpointer. |
| Checkpointer | Loads { messages, loadedPlugins, userContext, ... } for this thread_id (= session id) from per-user SQLite (synced to Matrix in the background). |
| LLM invocation | Composed prompt + bound tools + state messages go to the model resolved for the main role (provider from LLM_PROVIDER, or a hooks.resolveModel override). Plugin middleware fires (beforeModel, wrapModelCall, afterModel). |
| Tool calls | The agent loop dispatches plugin tools, sub-agent tools, meta-tools, and browser tools. Each emits a typed event so the client can render “Agent called X”. |
| Save state + stream | The final state is checkpointed; the assistant message streams over SSE. WS events for intermediate steps have already shipped. |