-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Engineering docs for Gradient — a single-user, local-first, multi-domain
study engine (questions → tagging → grounded atomic facts → Notion, plus Anki
linking). SPEC.md at the repo root is the forward source of truth; this wiki
holds longer-form design notes that don't belong in the spec.
Stack: Python 3.12+ · FastAPI · SQLAlchemy (async) · Postgres 16 (asyncpg) + pgvector · OpenAI SDK · APScheduler · Alembic. Backend-only (V-D1): every feature is a JSON API under /api/v1/*, the root /media store, an MCP tutor seam, or a scheduled job — there is no in-repo UI.
flowchart LR
subgraph Clients
EXT["Chrome extension<br/>(Gradient Capture)"]
TUT["MCP tutor host"]
ADM["Admin / curl"]
end
subgraph App["FastAPI app (app/main.py)"]
API["/api/v1/* routers"]
MED["/media/{path}"]
SCHED["APScheduler jobs<br/>(lifespan)"]
SVC["services/<br/>ingest · outline · kb · llm · anki · tutor"]
end
DB[("Postgres 16<br/>+ pgvector")]
subgraph External
OAI["OpenAI API"]
ANKI["AnkiConnect"]
NOTION["Notion API"]
end
EXT -->|POST /captures, /pdf/ingest| API
TUT -->|tutor reads + persist| API
ADM -->|admin, outline, kb reads| API
API --> SVC
MED --> SVC
SVC --> DB
SCHED --> SVC
SVC -->|embed · transcribe · tag| OAI
SVC -->|deck sync · allowlisted writes| ANKI
SVC -->|one-way mirror| NOTION
-
Getting Started — prerequisites, environment
variables,
uv sync, Postgres + Alembic, running the server, seeding, theX-Coach-Tokenheader, tests, and regenerating the OpenAPI contract.
- Architecture — layered breakdown, request lifecycle, async session model, and the scheduler / background-job model.
- Data Models — every ORM table (core, Anki, KB substrate) and the request/response Pydantic schemas.
- API Reference — the full endpoint catalog grouped by feature, with auth, request shapes, and response models.
- Function Index — the most-central functions and a by-module index across the service layer.
- Glossary — domain and code vocabulary (atomic fact, node_id, discriminator, calibration, source adapter, …).
-
Knowledge Base — PDF ingest → embed → grounded
tag → Notion mirror, plus the
kb_readsread endpoints. -
Outline & Mastery — schema-as-data outline tree,
>>-path resolution, subtree-as-set mastery rollups. -
Question Capture —
POST /captures, the source-adapter dispatch, normalization, and course scoping. - Anki — deck sync, tag parsing, assignment unlock, and the append-only review log.
- Tutor & MCP — the data-exposure tutor seam, cognitive-safety contract, and discriminator persistence.
-
Platform & Admin — the scheduler roster,
/adminroutes, the status probe, manual tag overrides, and the media store.
- Intelligence Layer — the AI chain: PDF ingest → embed → recall → grounded tagging → calibration. Its LLM4Tag lineage, the three recall meta-paths, model selection (V-L5), and the recall-completeness fixes (V-L6).
-
Core Library — the
gradient-coreboundary: the domain core that can be lifted out of the FastAPI app into an embeddable library, what stays transport/runtime, the runtime/config contract, and the phased migration plan. - Deployment & storage — target architecture for the redesign: self-hosted home server (Postgres + API), Apple clients over Tailscale, API-only access, filesystem media, paired backup.
- Features — single-page catalog of what Gradient does, grouped by area, with backing code and governing SPEC invariants.
- The spec (
SPEC.md) governs; wiki pages reference its sections (§G, §C, §I, §V, §T) rather than restating them. - This wiki is a git submodule of the main repo at
docs/wiki/. Edit pages there and push the submodule to publish.
Staleness and ambiguities surfaced while documenting against live code. Where docs conflict with code, code wins. These are tracked here so a later pass can reconcile them.
-
docs/BACKEND_CORE.mdpredates the current job set and routers. It still documents arun_categorizerjob (everyCATEGORIZER_INTERVAL_MINUTES) and arun_anki_topic_resolverjob (everyANKI_TOPIC_RESOLVER_INTERVAL_MINUTES), and points categorization atapp/services/categorizer/(outline_lookup.py,tag_question(...),categorizer/cache.py). None of these exist. The live job roster isrun_anki_sync/run_anki_assignment_unlock/run_anki_assignment_complete/run_anki_review/run_pdf_ingest/run_notion_sync/run_embed/run_grounded_tag, and categorization isapp/services/llm/grounded.py+app/services/kb/persist_tags.py. It also does not mention thekb_readsrouter. Needs a refresh. -
docs/wiki/Features.mdpredates thekb_readsrouter. Its KB row did not list the read endpoints (/concept-edges,/atomic-facts,/notion/pages,/pdf-sources); added in this pass. -
docs/openapi.jsonadmin coverage. The task brief claimed it omits the adminrecategorizeroute. Verified false at the source:app/api/v1/admin.pyhas no recategorize route (only/questions/{id}/tags,/tags/{id},/jobs,/jobs/{name}/trigger,/status), andBACKEND_CORE.mdis the only place that still references one. Soopenapi.jsonis not missing a recategorize path — butBACKEND_CORE.mdis stale on this point. -
FastAPI app title is still
MCAT Coachinapp/main.pydespite the project being renamed to Gradient.
-
admin.pycreate_manual_tagroute is broken. It passestopic_id/content_category_id/skillkwargs toadmin_tags.create_manual_tag, whose live signature is(*, node_id, rationale=None)only — the route raisesTypeErrorif hit. TheDELETE /tags/{tag_id}route bypasses the service and re-implements source-aware delete inline.
-
Admin mutation routes are not enforced.
create_manual_tag(POST /admin/questions/{id}/tags) anddelete_tag(DELETE /admin/tags/{id}) have noverify_coach_tokendependency and no programmatic localhost check; the module docstring calls them "localhost-only" but that is a deployment assumption, not enforced in code. -
Trigger allowlist is partial.
/admin/jobs/{name}/trigger's_VALID_JOBScovers only the 4 Anki jobs; the 4 KB/LLM jobs (pdf_ingest,notion_sync,embed,grounded_tag) appear in/admin/jobsand/admin/statusbut cannot be hand-triggered over HTTP.
-
Analyzer / recommendations stack was deleted (T53), not merely unmounted.
No analyzer or recommendations router is mounted;
recommender.pyandrun_feature_extraction_jobare gone. Onlyapp/schemas/analyzer.py(fenced) and therecommender_readyhealth flag remain.QuestionFeaturesis reader-live / writer-fenced (V-RB1). -
Anki fenced routes.
GET /anki/cards?topic_id=NandGET /anki/performanceare commented out (FENCED, T18 / V-RB2) — not mounted.state.pyandretention.pyservices return empties with no live route. -
Anki assignment SQL targets dropped tables.
assignment.py's_CANDIDATE_SQL_TOPIC/_CANDIDATE_SQL_CCstill join the legacytopics/content_categoriestables and referenceanki_note_tags.topic_id/content_category_id/parsed_kind IN ('aamc_topic','aamc_cc'), none of which exist in the livenode_id-only schema (V-T1 / §B4), soPOST /anki/assignmentsfails at the SQL layer until the resolver is ported.AnkiCardTagOut.topic_idis vestigial and always serializes null.
-
pgvector deferred.
ContentEmbedding.embeddingis a JSONB placeholder; migration 0003 does not runCREATE EXTENSION vector/ALTER ... vector(N)(pending T25), anddocker-compose.ymlruns stockpostgres:16, not a pgvector image. -
LlmBatchRunnaming. Itsanthropic_batch_idcolumn + docstring still reference the Anthropic Message Batches API, but the project pivoted to OpenAI; the model is not exercised by the live KB jobs or read endpoints. -
Planned §I renames not yet in code.
Question.qid → external_id(withUQ(source, external_id)) andAttempt.uworld_test_id → session_ref. -
.env.exampledocuments non-Settings knobs.CATEGORIZER_MODEL,FEATURE_EXTRACTOR_MODEL,ANKI_TOPIC_RESOLVER_MODELandgpt-4.1-minidefaults are notSettingsfields and are silently dropped (extra='ignore'); live defaults aregpt-5.4-nano/gpt-5.4-mini.POSTGRES_USER/PASSWORD/DBare consumed only bydocker-compose/mise run db:drop-env, notSettings. -
TaskRun.cost_usdNUMERIC(10,4)exists but is never written by any job (always NULL); token counts (incl.cached_tokens, V-L1) surface only on the synchronousPdfIngestResponse. -
Outline import payload confusion. Only
aamc_outline.schema.json({course, nodes}) is a validoutline:importbody; the siblingaamc_outline.json({version, sections}) is raw upstream source.outline:importtakes a rawdict[str, Any]validated byvalidate_outline_schema(not a Pydantic body), so a malformed schema returns a domain422 {errors:[...]}.
-
attempts/flaggedreturnstopics: []by design (T14 partial port). -
Tutor question-payload tags surface a raw
node_id, not a resolved>>path (unlikecaptures/recentand the session summary, which are label-resolved). -
Discriminator
notion_block_idis always NULL today — the T32 Notion write-back is not wired, so there is no live Notion back-link despite the column and V-N1 / V-N2 intent. -
No tutor-read route declares a
response_model; shapes are runtime-builtdict/list[dict], not Pydantic contracts. The same holds for the outline routes and thekb_readsread routes. -
Glossary substrate caveat.
atomic_fact_tags/notion_page_tagsare P2 target tables that may not be fully materialized; the Notion/PDF inbox poller jobs are conditional no-ops without their credentials.
Reference
Features
Design Notes