Serving — main.py, query_engine, mcp_server
App Runner container from Dockerfile.api: copies main.py, reviewer modules,
mcp_server.py, bedrock_client.py, query_engine/, and staged scorer_onnx/.
CMD: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 (single worker so
file-based usage counters stay coherent; filesystem is ephemeral anyway).
main.py
FastAPI monolith (thousands of lines). Major surfaces:
| Path | Role |
|---|---|
/papers/* | Search over CORPUS_DIR parsed JSON; empty in the container by design |
/graph/<template> | Thin REST for templates T1–T17 (query_engine.templates) |
/graph/ask | NL pipeline: router → gates → templates → assemble → refine → generate; freeform fallback via relevance_gate → shape_classifier → text2cypher → graph_explorer_fallback |
/graph/draft, /graph/commit | Interactive query-drafting agent (stateless; logs proposals) |
/reviewer-bot/* | Review generation (Bedrock) + grounding + PDF + S3 archive |
/mcp | Mounted MCP ASGI app when MCP env vars are present |
Worker → App Runner auth: shared secret header X-Syntology-Internal-Key.
_verify_internal_key is fail-open when SYNTOLOGY_INTERNAL_KEY is unset
(see Security, F-06).
First request path historically called linker.ensure_indexes() (CREATE FULLTEXT INDEX … IF NOT EXISTS) with the API credential — migration on the
request path; reader may not be allowed to create indexes (F-06). Candidate
kernel deliberately does not create indexes on the request path.
query_engine/ (SynQ)
| Module | Role |
|---|---|
router.py | Regex slot-match; zero-LLM; ACTIVE_TEMPLATES = decline menu |
linker.py | Entity resolution; ensure_indexes() migration helper |
gates.py | Pre-spend coverage (TEMPLATE_RELTYPE_REQUIREMENTS) |
coverage.py | Live-count-derived coverage (no stored manifest) |
schema.py / syntology_schema.py | Locked names + Text2Cypher schema snapshot (qc_t2c_schema.py freshness gate on fresher trees) |
templates.py | T1–T17 executors + preview Cypher builders |
assemble.py / refine.py / generate.py / narrate.py | Evidence → claims → optional narration |
text2cypher.py | k-sample self-consistency + allowlist validator |
relevance_gate.py, shape_classifier.py | Freeform path shaping |
graph_explorer_fallback.py | Bounded tool-calling explorer |
logging.py | request_log.jsonl / proposal_log.jsonl on container FS (lost on deploy — F-06) |
bench/ | Gold sets, readiness, results |
mcp_server.py
MCP Streamable HTTP for agents. Auth is trial bearer tokens in Cloudflare
KV (not the internal key) — agents are meant to hit App Runner directly for
/mcp, while the Worker still origin-shields the raw URL.
- Verifies tokens via Cloudflare KV REST (
CLOUDFLARE_KV_READ_TOKEN, account + namespace ids). - Budget decrement / agents-served counters use PUT with that token; failures swallowed — F-17: if the token is truly read-only, MCP budgets may never decrement.
- Origin shield middleware + header injection from
mcp-proxy.js(SECURITY_INCIDENT_2026-08-21.md). - Tools:
graph_askplus typed tools (get_paper, code/citation/review/ institution helpers,get_reference_implementation,list_reference_implementations, …) — seepublic/llms.txt.
Paying-tier balance check uses BILLING KV when
CLOUDFLARE_BILLING_NAMESPACE_ID is set; otherwise paying tokens fail closed
on that path while free tokens still work.
Honest gaps
- Container does not ship the corpus;
/papers/*is hollow in production. - Rate-limit JSON files reset every deploy.
- Request/proposal logs are not durable off-box unless separately shipped.
- Modular
syntology.servingstrangler is additive — production still points atmain:appunless promoted (see Modular candidate).