Syntology Docs

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:

PathRole
/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/askNL pipeline: router → gates → templates → assemble → refine → generate; freeform fallback via relevance_gate → shape_classifier → text2cypher → graph_explorer_fallback
/graph/draft, /graph/commitInteractive query-drafting agent (stateless; logs proposals)
/reviewer-bot/*Review generation (Bedrock) + grounding + PDF + S3 archive
/mcpMounted 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)

ModuleRole
router.pyRegex slot-match; zero-LLM; ACTIVE_TEMPLATES = decline menu
linker.pyEntity resolution; ensure_indexes() migration helper
gates.pyPre-spend coverage (TEMPLATE_RELTYPE_REQUIREMENTS)
coverage.pyLive-count-derived coverage (no stored manifest)
schema.py / syntology_schema.pyLocked names + Text2Cypher schema snapshot (qc_t2c_schema.py freshness gate on fresher trees)
templates.pyT1–T17 executors + preview Cypher builders
assemble.py / refine.py / generate.py / narrate.pyEvidence → claims → optional narration
text2cypher.pyk-sample self-consistency + allowlist validator
relevance_gate.py, shape_classifier.pyFreeform path shaping
graph_explorer_fallback.pyBounded tool-calling explorer
logging.pyrequest_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_ask plus typed tools (get_paper, code/citation/review/ institution helpers, get_reference_implementation, list_reference_implementations, …) — see public/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.serving strangler is additive — production still points at main:app unless promoted (see Modular candidate).