Component map
What this page is
The architecture-index post-build plugin at
docusaurus/ratiba/plugins/architecture-index.js walks every markdown
file under docusaurus/ratiba/docs/, parses the components:,
adrs:, and runbook_steps: frontmatter, and emits a structured
inventory at docs/architecture-index.json. That JSON is the machine
view of the codebase — a coverage table, a dependency adjacency list,
a stale-pointer warning queue. The plugin contract is fully described
in ADR-0011.
This page is the human view of the same territory. It sits one rung
higher than the JSON: a folder-by-folder tour of backend/app/, the
module-level import topology drawn as a graph, and a short prose
explanation of what the plugin actually checks. Read it when you need
to know where a behaviour lives before going to the per-feature
explainer at How it works or
the cross-cutting deep page at Architecture / System
overview.
Two recent additions worth flagging before the full tour:
-
Phase 0 knowledge layer (
app/services/knowledge.py+app/services/inquiry.py+app/services/answer_shaper.py): the "no-RAG RAG" design that injects per-tenant knowledge snippets into theanswer_shaperuser template without touching the system prompt. Behavioural deep-dive at Knowledge answers; architectural decision at ADR-0013. -
Voice full-duplex primitives (
app/voice/config.py,barge_in.py,backchannel.py,hard_interrupt.py,listening_ack.py,voice_speed.py,brain_stream.py): the M7 voice channel upgraded to support natural turn-taking (barge-in, adaptive speed, bilingual hard-interrupt, agentic streaming seam). Behavioural deep-dive at Voice conversation.
The companion C4 view lives at Architecture overview; the data flows live at Architecture / Data flow; the schema shape lives at Architecture / Schema evolution. This page is the "where is the code" reference.
app/ folder tour
Every top-level package in backend/app/ has a single, narrow
purpose. The table below names that purpose and lists the modules a
new contributor would open first. Verify against on-disk state with
ls backend/app/<folder>/ — drift between this table and reality is
itself a stale-pointer warning the plugin will eventually catch
(last_verified vs. component file mtimes, per ADR-0011 D6).
| Folder | Purpose | Key modules |
|---|---|---|
app/api/ | FastAPI routers — webhooks (Meta + payments), admin endpoints, channel WebSockets | webhooks/whatsapp.py, webhooks/instagram.py, webhooks/messenger.py, webhooks/daraja.py, webhooks/pesapal.py, admin/chat_ws.py, admin/tenants.py, admin/catalog.py, admin/personality.py, channels/web.py, dependencies/admin_auth.py, dependencies/tenant.py, health.py |
app/channels/ | Channel substrate + 5 adapters + 1 SMS sink | _base/protocol.py (Channel + ChannelKind + Tier), _base/registry.py, _meta_messaging/, whatsapp/_channel.py + sender.py + models.py, voice/, web/, instagram/, messenger/ |
app/orchestrator/ | Conversation FSMs (LangGraph) | booking_graph.py, cancel_graph.py, reschedule_graph.py, state.py (FSM_GRAPH_VERSION + BookingState), dispatcher.py, threads.py, handoff.py |
app/services/ | Domain services — pure business logic, no I/O glue. Includes Phase 0 knowledge layer (knowledge.py + inquiry.py + answer_shaper.py) — see Knowledge answers. | availability.py, cross_sell.py, reservations.py, personality_config.py, catalog_importer.py, csv_extractor.py, vision_extractor.py, relation_inferrer.py, answer_shaper.py, inquiry.py, knowledge.py, channel_switch.py |
app/llm/ | LLM router + provider adapters + cost ledger | router.py, cost.py, _response.py, adapters/anthropic.py, adapters/openai.py, role_assignments.yaml |
app/payments/ | M-Pesa STK push + PesaPal cards + cancellation reversal | daraja.py, initiate_daraja.py, poll_daraja.py, pesapal.py, pesapal_flow.py, callbacks.py, cancel_reversal.py, credentials.py, state.py |
app/agents/ | Identity-resolution + agent abstractions | identity_resolver.py |
app/admin/ | Admin orchestrator (4-state shallow FSM, dual-channel rail) | orchestrator.py, commands.py, nl_router.py, message_router.py, stats.py, briefing.py, fanout.py |
app/persistence/ | DB pools + DAOs + LangGraph saver shim | session.py, pool.py, customers.py, personality_config.py, catalog_imports.py, tenant_scoped_saver.py, redis.py, encryption.py, onboarding_psycopg.py |
app/tenancy/ | Multi-tenant infrastructure + onboarding lifecycle | context.py (current_tenant ContextVar), models.py (Tenant ORM), schemas.py, onboarding.py, lifecycle.py, keycloak_admin.py, handoff_defaults.py |
app/prompts/ | Prompt YAML files + loader | loader.py (Prompt model + render), intent_classifier.yaml, answer_shaper.yaml, booking_*.yaml, cancel_request.yaml, reschedule_request.yaml, cross_sell_offer_en.yaml, cross_sell_offer_sw.yaml, personality_defaults.yaml, handoff_summarizer.yaml, vision_catalog_extract.yaml, relation_inference.yaml, catalog_gap_fill.yaml, admin_nl_routing.yaml |
app/eval/ | Calibration runner + scenario loader + metrics | scenario.py, runner.py, admin_runner.py, admin_scenario.py, metrics.py, langfuse_sink.py, __main__.py |
app/voice/ | Voice-channel internals (LiveKit + Deepgram + ElevenLabs glue) + full-duplex turn-taking primitives. See Voice conversation. | agent.py (LiveKit AgentSession plugin), deepgram.py (Nova-3 STT), elevenlabs.py (Multilingual v2 TTS), eot.py (end-of-turn detector), filler_clock.py (filler-word pacing), handoff.py, payment_listener.py, config.py (VoiceConfig dials: barge-in threshold, WPM bounds, backchannel phrases), barge_in.py (customer speech interrupts bot TTS), backchannel.py (listening acknowledgements — "mm-hm", "ndiyo"), hard_interrupt.py (bilingual hard-stop: "stop"/"simama"), brain_stream.py (VoiceStreamEvent agentic streaming seam for future agentic backend), listening_ack.py (visual/audio cue while awaiting FSM), voice_speed.py (WPM-adaptive TTS speed control) |
app/notifications/ | Outbound NotificationSinks (SMS + reminders) | sms.py (Africa's Talking), reminders.py |
app/workers/ | Background jobs — APScheduler in-process | payments_reaper.py |
Top-level files (app/main.py, app/config.py, app/worker.py)
carry process bootstrap — FastAPI app construction, settings loading
via Pydantic, and APScheduler setup respectively.
Module-level dependency graph
The graph below shows the import topology between top-level packages
inside backend/app/. Edges are directed A -> B whenever a module
in A imports any symbol from B. The shape is acyclic by
design — domain services never import from the orchestrator, the
orchestrator never imports from API handlers, and persistence sits at
the bottom of the stack with tenancy as its only downstream
dependency.
Three properties of the graph worth calling out:
- No upstream/downstream violations.
app/services/never importsapp/orchestrator/— services are pure domain logic, the FSM orchestrates them. This holds for the Phase 0 knowledge layer too:knowledge.pyandinquiry.pyare called by the FSM node (viaorchestrator --> services), not the other way around.app/persistence/never importsapp/services/or anything above it — the bottom of the stack only knows about tenancy.app/prompts/is a leaf (onlyloader+ YAML files) and depends on nothing insideapp/. app/tenancy/is the universal substrate. Every package that touches the database eventually readscurrent_tenant.get()fromapp/tenancy/context.py. The contextvar propagation is what makes schema-per-tenant isolation work without explicit tenant arguments on every function (per ADR-0002). The per-tenantknowledge_snippetstable is scoped identically —knowledge.pyinherits the tenant search path via the same contextvar without any extra argument threading.- The API layer is a thin shell.
app/api/only ever delegates downward — to channels (for webhook → adapter routing), to admin (for/admin/*endpoints), to orchestrator (for the rare direct FSM entry from REST). It owns no business logic.
architecture-index.json humanised
The plugin at docusaurus/ratiba/plugins/architecture-index.js runs
as a Docusaurus postBuild step. On every npm run build it walks
every .md / .mdx file under docusaurus/ratiba/docs/, parses the
YAML frontmatter, and writes structured output to
docs/architecture-index.json (gitignored at runtime, regenerated by
every build). The five behaviours that matter for this page:
components:scan +component_mapinversion. Every page declares whichapp/<...>files it documents. The plugin inverts the map — for every component, which pages mention it. Multi-page components (e.g.app/orchestrator/booking_graph.pyshows up in Architecture overview, How it works / Conversation FSM, How it works / Cross-sell, How it works / Payments, Quick start / First booking) are valuable signal: they're load-bearing modules that deserve careful change control.- Coverage threshold ≥ 80% per ADR-0011 D6.
The plugin enumerates every Python file under
backend/app/services/andbackend/app/agents/and reports the percentage referenced by at least onecomponents:list. The current threshold is 80% — pages added during M12 W2 + W3 lift the count from the M11 baseline; below 80% the build emits a warning. The latest build reports 64% — the remaining gap closes as Wave 3 lands the Architecture deep pages. - Stale
last_verifiedflagging. Every page carries alast_verified: YYYY-MM-DDfield. The plugin checks the most-recentgit logtimestamp of every file in the page'scomponents:list; if any component has been modified more recently than 30 days beforelast_verified, the page is flagged instale_pointer_warnings. Per ADR-0011 D6, git-log results are cached within the build pass — a component referenced by multiple pages reuses the cached lookup. - ADR cross-validation. Every page's
adrs: [N, ...]list is validated againstdocs/adr/ADR-NNNN-*.mdexistence on disk. Fat-fingered numbers or renumbered ADRs fail the build with a clear error. runbook_stepsaggregation. Every "How it works" leaf carries a 3-steprunbook_steps:list (the "Try this on local dev" block). The plugin collects these intorunbook_steps_by_pagefor future consolidated-runbook tooling. M12 doesn't ship that view; the data is captured for M13+.
The plugin output lives at docs/architecture-index.json and is
gitignored — every build regenerates it deterministically. The
frontmatter validator at
docusaurus/ratiba/scripts/validate-frontmatter.js runs on npm run validate-frontmatter (and as prebuild) and enforces that every
page in architecture/, how-it-works/, quickstart/,
methodology/, and runbook/ has the three required fields
(components:, adrs:, runbook_steps:) — empty lists are valid,
missing fields fail.
Cross-links
Architecture peers:
- Architecture overview — C4 levels 1+2 (Context + Container).
- System overview — C4 level 3 (the seven internal layers, transport per edge).
- Data flow — per-feature flow walkthroughs.
- Schema evolution — per-tenant DDL + Alembic migration order.
How it works leaves (each leaf names the modules in this map):
- Channel substrate
- Conversation FSM
- Identity and tenancy
- Payments
- Cross-sell
- Personality dials
- Catalog onboarding
- Admin orchestrator
- Knowledge answers —
app/services/knowledge.py+inquiry.py+answer_shaper.py; Phase 0 snippet injection - Voice conversation —
app/voice/*; LiveKit full-duplex turn-taking
Decisions:
- ADR-0001 — Tech stack — the Python 3.13 / FastAPI / Next.js / Postgres / Keycloak / Redis pin that anchors every package in this map.
- ADR-0011 — Docs IA and tooling — the post-build plugin contract this page humanises.