Architecture Map
Source-grounded architecture document

Current State Architecture Map — Psychology Book Assistant / Partner API

Implemented backend state, Book DB decision, LLM/retrieval wiring, coach profile model, frontend prototype status, and next implementation plan.

docs/architecture/current-state-map.md 2026-06-04 ~14 min read

Generated: 2026-06-04
Scope: /home/legion/Projects/psychology-book-assistant backend plus /home/legion/Projects/soulmates React prototype.
Rule: this document separates implemented state from gaps/recommendations and cites source files with line anchors.

Executive summary

The backend is a Python/FastAPI partner API wrapped around a source-grounded psychology book assistant. It has three separate persistence concerns:

  1. Book/corpus DB: data/assistant.sqlite — canonical book index and search source.
  2. Partner API state DB: data/partner_api.sqlite — couples, consents, conversations, messages, reviews, webhooks, idempotency.
  3. Local assistant session DB: data/sessions.sqlite — Telegram/local chat memory only.

Use data/assistant.sqlite as the Book DB. Do not merge corpus tables into partner_api.sqlite; partner state should reference answer outputs and audit metadata, not own the book corpus.

High-level architecture

flowchart LR
  Partner[Partner client] -->|Bearer token + scope| API[FastAPI Partner API /api/v1]
  API --> Auth[security.py token + scope checks]
  API --> Store[(data/partner_api.sqlite)]
  API -->|safe_answer| Agent[answer_with_tools]
  Agent --> Search[hybrid_search]
  Search --> Corpus[(data/assistant.sqlite FTS)]
  Search -. optional .-> Pinecone[Pinecone semantic sidecar]
  Agent --> LLM[OpenRouter or OmniRoute chat completions]
  API --> Messages[Persist user + assistant messages]
  Prototype[soulmates React prototype] -. not connected .-> API

Source anchors: FastAPI defaults and app setup are in src/psych_assistant/api/app.py:44-67; partner request headers/logging are in app.py:69-107; LLM provider constants are in src/psych_assistant/llm.py:11-12; hybrid search fallback is in src/psych_assistant/hybrid_search.py:15-47.

Implemented backend modules

Area Implemented now Source
Package/runtime Python >=3.11, FastAPI, Pinecone, Uvicorn, script entrypoints psych-assistant and psych-partner-api. pyproject.toml:1-17
API app FastAPI app title/version, OpenAPI at /api/v1/openapi.json, store defaulting to data/partner_api.sqlite, answerer defaulting to default_answerer. src/psych_assistant/api/app.py:52-67
Request envelope X-Request-Id, X-API-Version, X-Build-Id, no-store cache headers, placeholder rate-limit headers, structured request logs. src/psych_assistant/api/app.py:69-107
Auth Bearer-token auth from PARTNER_API_TOKENS, default token dev-partner-token, scopes from PARTNER_API_SCOPES. src/psych_assistant/api/security.py:39-74
Error contract Problem Details responses with sanitized validation errors and safe 500s. src/psych_assistant/api/errors.py:36-95
Partner store SQLite tables for couples, consents, intakes, conversations, messages, exercises, feedback, quality reviews, webhooks, idempotency. src/psych_assistant/api/store.py:27-112
Book index SQLite books, chunks, chunks_fts with FTS5 tokenizer; rebuild reads manifest EPUBs and chunks text. src/psych_assistant/index.py:24-87
Lexical search FTS query + author/role filters + fallback LIKE search. src/psych_assistant/index.py:90-207
Hybrid search SQLite FTS first, optional Pinecone semantic search, reciprocal rank fusion, FTS fallback on vector error. src/psych_assistant/hybrid_search.py:15-78
LLM Provider switch via LLM_PROVIDER; OpenRouter default; OmniRoute supported with its own URL, model, and secret name. src/psych_assistant/llm.py:86-140, llm.py:186-269
Agent Tool-calling path with deterministic direct-retrieval fallback when tool routing is off or provider fails. src/psych_assistant/agent.py:146-179, agent.py:242-383

Partner API endpoint map

Method Path Scope Implemented behavior Source
GET /api/v1/health Public Health, version, timestamp, build metadata. app.py:109-111
GET /api/v1/version Public Build metadata from env/defaults. app.py:113-115, app.py:768-776
GET /api/v1/capabilities Authenticated Languages, frameworks, safety rules, limits, partner id. app.py:117-129
GET /api/v1/coaches coaches:read Static coach catalog: balanced, soft, analytic. app.py:131-155
POST /api/v1/couples couples:write Creates partner-scoped couple; idempotency required; strips private fields. app.py:157-189
GET /api/v1/couples/{couple_id} couples:read Fetches only if object belongs to partner. app.py:191-198, app.py:625-630
PATCH /api/v1/couples/{couple_id} couples:write Updates public couple fields and summary. app.py:200-216
POST /api/v1/couples/{couple_id}/consents consents:write Records consent, updates couple consent status, idempotent. app.py:218-257
POST /api/v1/couples/{couple_id}/intakes intakes:write Stores intake summary/themes for couple memory. app.py:259-291
POST /api/v1/conversations conversations:write Creates active conversation for a couple and optional coach id. app.py:293-330
GET /api/v1/conversations/{conversation_id}/messages messages:read Lists paginated messages; requires active consent. app.py:332-349, app.py:673-679
POST /api/v1/conversations/{conversation_id}/messages messages:write Saves user message, safety-checks, calls answerer, saves assistant message. app.py:351-416
POST /api/v1/safety/checks safety:write Returns keyword-based risk flags/escalation policy. app.py:418-438, app.py:681-700
PATCH /api/v1/exercises/{exercise_id} exercises:write Upserts exercise status/notes/reflection. app.py:440-450
POST /api/v1/messages/{message_id}/feedback feedback:write Stores feedback against an existing partner-owned message. app.py:452-469
GET /api/v1/quality/cases quality:read Lists partner-safe redacted quality cases. app.py:471-483, app.py:641-655
POST /api/v1/quality/reviews quality:write Stores review verdict/scores/learning status. app.py:485-494
GET /api/v1/webhook-endpoints webhooks:read Lists partner webhook endpoints. app.py:496-502
POST /api/v1/webhook-endpoints webhooks:write Creates endpoint; idempotency required. app.py:504-535

Notes:

Which DB should be used for Book?

Use data/assistant.sqlite.

DB Current role Current tables/counts Decision
data/assistant.sqlite Canonical corpus/search DB. books=40, chunks=17964, chunks_fts=17964. Schema is books, chunks, chunks_fts. Book DB. Keep as source of truth.
data/partner_api.sqlite Partner API operational state. couples, consents, conversations, messages, feedback, quality_reviews, webhook_endpoints, idempotency. Do not store book chunks here. Store conversation/output/audit state only.
data/sessions.sqlite Local assistant/Telegram memory. sessions, messages, answer_feedback. Do not use for Partner API or Book corpus.
Pinecone index Optional semantic sidecar. Index psychology-book-assistant, namespace books-v1, source ids book_id:chunk_index. Not a source of truth; rebuild from assistant.sqlite when needed.

Why: README explicitly states SQLite FTS is the local search index and canonical source of truth, while Pinecone is only an optional semantic sidecar (README.md:7-12, README.md:61-63). The physical Book DB schema in index.py contains only books and chunks (src/psych_assistant/index.py:24-50).

LLM and retrieval integration

Provider selection

Retrieval path

  1. Partner message hits POST /api/v1/conversations/{conversation_id}/messages.
  2. API requires active consent, saves the user message, runs assess_safety, then calls safe_answer (app.py:351-383).
  3. safe_answer blocks crisis/diagnosis cases, otherwise calls the app answerer (app.py:703-711).
  4. Default answerer calls answer_with_tools(question, DEFAULT_DATABASE, DEFAULT_PROFILES, DEFAULT_ENV, mode=PARTNER_API_DEFAULT_MODE or soft, conversation_context=...) (app.py:714-722).
  5. Agent uses direct retrieval unless AGENT_TOOL_ROUTER_ENABLED=1; direct retrieval uses hybrid search, source chunk fetch, response planning, and final answer generation with fallback behavior (agent.py:146-179, agent.py:242-383).
  6. Hybrid search uses SQLite FTS first, calls Pinecone only when HYBRID_SEARCH_ENABLED is true, then fuses results; vector errors fall back to FTS (hybrid_search.py:15-47, hybrid_search.py:76-78).
  7. Vector config requires VECTOR_SEARCH_ENABLED, VECTOR_PROVIDER=pinecone, a Pinecone key, index/name/namespace/model settings; semantic matches are mapped back to SQLite source chunks (vector_search.py:46-69, vector_search.py:103-124).

Active configuration from existing docs

The current ops map says the active setup is:

Source: skills/psychology-book-assistant-ops/references/system-map.md.

Psychologist/coach profile model

There are two different profile concepts today.

1. Author profiles for book-grounded answer style

data/author_profiles.json stores author-oriented guidance: voice, core_themes, best_for, avoid_for, and weight. The loader builds immutable AuthorProfile records and resolves author names by exact match, substring match, or surname (src/psych_assistant/profiles.py:8-52). Example profiles include Freud, Jung, Fromm, Winnicott, Klein, Berne, Le Bon, Kohut, Csikszentmihalyi, and Cialdini (data/author_profiles.json:1-75).

These profiles are not partner-facing coach identities. They are style/relevance metadata for grounded answers from source authors.

2. Partner API coaches

GET /api/v1/coaches currently returns a static catalog with three coach ids:

All three use persona_policy = source_grounded_style_not_person_impersonation (src/psych_assistant/api/app.py:131-155). This means the API intentionally exposes coach modes, not real psychologist impersonations.

Recommendation

Keep this split:

React prototype status: /home/legion/Projects/soulmates

The soulmates repo is a Vite + React + TypeScript prototype (package.json:1-21). It is not wired to the Partner API.

Implemented UI pieces:

Network status: a grep for fetch, axios, XMLHttpRequest, WebSocket, EventSource, /api/, http://, and https:// across source/docs only found the local Vite preview URL in docs/VERIFICATION_CHECKLIST.md. So the prototype is product/UI only.

Current test coverage

Implemented tests cover:

Gaps and risks

Gap/risk Why it matters Evidence
Partner API uses dev token defaults. Production needs real token provisioning/rotation, per-partner scopes, rate limits, audit controls. security.py:64-74
Rate-limit headers are placeholders. Headers say limit/remaining/reset but no enforcement exists in middleware. app.py:92-96
Safety check is keyword-based. Crisis/violence/diagnosis detection is simple string matching, not robust clinical safety. app.py:681-700
Grounding score in Partner API response is hardcoded by safety level. Message response does not expose actual retrieval source ids yet. app.py:384-391
Coaches are static modes, not persisted profiles. Product may expect configurable psychologist/coach profiles, specialties, languages, schedules. app.py:131-155
Webhooks are stored but no dispatcher is implemented. Partners can register endpoints, but events are not sent. app.py:496-535, store.py:92-99
partner_api.sqlite is SQLite. Fine for prototype; production needs migrations, backups, connection strategy, and possibly Postgres. app.py:48, store.py:27-112
React prototype is disconnected. UX cannot exercise real consent/conversation/message flows yet. soulmates/src/App.tsx:299-315, grep result described above
Book corpus paths point to local downloads. Rebuild portability depends on local files staying present. data/books_manifest.json:5-120
Pinecone secret naming drift. Existing code defaults to PINECODE; ops notes say working secret is PINECIDE API. vector_search.py:52-59, ops map
  1. Make Partner API message grounding real in responses. Return selected source_ids, titles, authors, and groundedness/coverage from answer_with_tools instead of hardcoded sources: [] and score 1.0.
  2. Add production auth/rate-limit layer. Replace dev token defaults with partner-token records or external auth; enforce the rate limit that headers currently advertise.
  3. Create a persisted coach catalog. Keep balanced/soft/analytic as modes, but store partner-facing coach metadata separately from author profiles.
  4. Wire soulmates to the Partner API. Start with: create couple → record consent → create conversation → send message → render assistant answer and audit/source metadata.
  5. Add webhook dispatch/outbox. Store outbound events in a durable outbox table and deliver to registered endpoints with retries/signatures.
  6. Normalize environment and secret names. Set PINECONE_API_KEY_SECRET_NAME=PINECIDE API explicitly; document OmniRoute/OpenRouter switch in deployment runbook.
  7. Plan DB migration path. Keep assistant.sqlite for Book corpus, but move partner operational state to Postgres when concurrency/multi-instance deployment starts.
  8. Add integration tests for real retrieval payloads. Mock LLM/provider but assert the API carries actual source metadata and consent/safety behavior.

Source index