Reviewed-on: MoA/agent#1 Co-authored-by: highperfocused <highperfocused@pm.me> Co-committed-by: highperfocused <highperfocused@pm.me>
5.5 KiB
Gateway: how it works
This document explains how the HTTP gateway in this repository works.
Overview
The gateway is a thin HTTP layer around @mariozechner/pi-coding-agent sessions.
Main goals:
- expose chat over HTTP (
/v1/chat,/v1/chat/stream) - keep long-lived conversation state per
conversationId - support adapter-friendly IDs (Slack/Matrix/etc.)
- optionally expose a built-in browser UI at
/
Key source files:
src/index.tssrc/gateway/server.tssrc/conversation-manager.tssrc/agent-session-factory.tssrc/gateway/events.ts
Startup flow
src/index.tsloads env config vialoadConfig().- If
RUN_MODE=single, one-shot mode is executed and exits. - Otherwise (
RUN_MODE=gateway), it:- creates
ConversationManager - initializes persisted conversation metadata (if enabled)
- starts
GatewayHttpServer
- creates
- On
SIGINT/SIGTERM, it stops the HTTP server and disposes sessions.
Core components
1) GatewayHttpServer (src/gateway/server.ts)
Responsible for:
- request routing
- auth and CORS handling
- request validation
- SSE streaming responses
- JSON/HTML responses
2) ConversationManager (src/conversation-manager.ts)
Responsible for:
- creating and tracking conversation records
- loading/opening/creating agent sessions
- serializing prompts per conversation (queue)
- persisting conversation index + session metadata
- aborting/deleting sessions
3) AgentSessionFactory (src/agent-session-factory.ts)
Responsible for constructing agent sessions with:
- model/provider selection (including Ollama support)
- tool selection (
all,readonly,none, or subset) - optional system prompt override/append
- auth storage and model registry wiring
Conversation model
A conversation is identified by conversationId.
- If client provides no ID, a UUID is generated.
- Each conversation maps to one
AgentSession. - Multiple requests for the same conversation are queued and processed in order.
- Metadata is exposed via
/v1/conversationsendpoints.
Validation rules:
conversationIdmax length: 200conversationIdmust not contain\n/\rmessagemust be a non-empty stringimagesmust be an array when providedstreamingBehaviormust be"steer"or"followUp"when provided
Persistence behavior
Controlled by SESSION_PERSIST.
SESSION_PERSIST=true
Data is stored under:
<CWD>/.gateway/conversations.json(conversation index)<CWD>/.gateway/sessions/...(session files)
At startup, the index is loaded and conversations are restored as unloaded records.
The actual AgentSession is lazily opened when that conversation is used.
SESSION_PERSIST=false
Everything is in memory and lost on process exit.
API routes
Health/UI
GET /health→{ "ok": true }GET /→ built-in Web UI HTML (ifGATEWAY_ENABLE_WEB_UI=true)
Conversation management
GET /v1/conversationsPOST /v1/conversationsGET /v1/conversations/:idDELETE /v1/conversations/:idPOST /v1/conversations/:id/abort
Chat
POST /v1/chat(JSON response)POST /v1/chat/stream(SSE response)
Adapter endpoints
POST /v1/adapters/chatPOST /v1/adapters/chat/stream
Adapter request fields (source, workspaceId, channelId, threadId, userId) are normalized into:
conversationId = source:workspaceId:channelId:threadIdadapterKey = source:workspaceId:channelId:threadId:userId
channelId is required. : is not allowed inside segment values.
For practical setup patterns per transport (Web UI, Slack, Matrix, custom), see docs/channels.md.
Streaming (SSE) behavior
For /v1/chat/stream and /v1/adapters/chat/stream:
- Response starts with SSE headers.
- A
readyevent is emitted. - Agent session events are mapped to gateway events (
src/gateway/events.ts). - A final
doneevent is emitted with summary payload. - On failure, an
errorevent is emitted and stream ends.
Common emitted event types:
assistant_text_deltaassistant_thinking_deltaassistant_message_updatetool_start,tool_update,tool_endagent_start,agent_endretry_start,retry_endcompaction_start,compaction_enddoneerror
done includes:
conversationIdsessionIdsessionFileassistantText- plus
adapterKeyon adapter streaming routes
Disconnect behavior:
- if client disconnects mid-stream and the request had a
conversationId, the server attempts to abort that conversation.
Auth and CORS
Bearer auth
If GATEWAY_AUTH_TOKEN is set, requests must include:
Authorization: Bearer <token>
Otherwise server returns 401.
Note: auth is checked before route handling, so this applies to all routes (including GET / and GET /health).
CORS
If GATEWAY_CORS_ORIGIN is set, server adds:
Access-Control-Allow-OriginAccess-Control-Allow-Headers: Content-Type, AuthorizationAccess-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
OPTIONS preflight returns 204.
Request limits and errors
- JSON body max size: 1 MiB (413 if exceeded)
- invalid JSON: 400
- invalid payload field types: 400
- unknown route: 404
- unexpected errors: 500
Environment variables (gateway-relevant)
RUN_MODE(gateway|single)GATEWAY_HOSTGATEWAY_PORTGATEWAY_CORS_ORIGINGATEWAY_AUTH_TOKENGATEWAY_ENABLE_WEB_UISESSION_PERSISTVERBOSE_TOOLSCWD
See .env.example for complete defaults and comments.