Architecture
Conversation and announcement domains, provider adapters, webhooks, and the Foundry wake.
Binaries
| Binary | Path | Role |
|---|---|---|
| API | cmd/api | HTTP server + outbox publisher; flags -migrate, -openapi |
| conversationListener | cmd/conversationListener | Logs conversation events and retries a Foundry wake |
| announcementListener | cmd/announcementListener | Logs announcement events |
Layout
services/communications/
domains/conversation/events/
domains/announcement/events/
internal/
domains/
providers/ ← Twilio, Mailgun
catalog/ ← committed tool list
wake/ ← HTTP POST to foundry-api
handlers/
authz/
command/
publisher/
testdb/
messageBus/
migrations/
db/Tables use the domain slug, singular
(ADR 0015).
conversation_message.announcement_id joins the two domains, so there is one sqlc package
(ADR 0023).
Inbound path
Webhooks are chi handlers, not Huma, because Twilio and Mailgun post forms. They skip Cedar and
verify the provider signature. After commit, if conversation.agent_id is set, the API POSTs
/v1/agents/{id}/wake with conversation_id, message_id, and channel. Body text stays in
this database. Foundry fetches GET /v1/messages/{id}. A failed wake is retried from the
outbox, not rolled back on the inbound row.
Inbound email to ${slug}@ai.flow.life (prod) or ${slug}@dev-ai.flow.life (dev)
uses a Mailgun receiving route you create in the dashboard. The route forwards
every address on that domain to POST /v1/webhooks/mailgun. The webhook asks
Foundry GET /v1/agents/by-slug/{slug} and links the thread before the wake.
Linking is POST /v1/conversations/{id}/link. Foundry stores the same conversation_id on the
agent row and calls this path after create or edit.
Channels
sms | email | whatsapp | signal. Phase 1 adapters: Twilio SMS, Mailgun email. The other two
are reserved.
See ADR 0045.