HTTP API¶
The FastAPI service (spec_v2 §4): the app factory, the wire contract, SSE
framing, dependencies, and the route modules. The HTTP-level contract
(paths, bodies, event protocol) is documented as-built in the OpenAPI
schema served at /openapi.json; these are the Python internals.
varagity.api
¶
The HTTP API (spec_v2 §4.1): FastAPI over the same flows the CLI runs.
The API lives inside the package so it imports the pipeline directly — no
network hop, and the "app is a client of every backing service" invariant
holds with a second front door. async at the edge, sync flows
underneath: endpoints run the synchronous pipeline in a threadpool while
the event loop streams SSE frames out.
Import :func:create_app lazily from :mod:varagity.api.main (uvicorn's
--factory target) — importing it pulls FastAPI and the whole pipeline.
varagity.api.main
¶
FastAPI app factory: CORS, lifespan, structured errors, routers.
create_app is served by a single uvicorn worker (plan decision #11):
uvicorn varagity.api.main:create_app --factory. The lifespan warms the
registries, runs the idempotent migration runner (so an existing pgdata
volume gains the v2 tables without down -v), and replays persisted
runtime setting overrides (spec_v2 §4.7); every error response carries the
{error: {code, message}} envelope the GUI banners on.
Importing this module imports :mod:varagity.pipeline (via the chat
route), which exports PREFECT_API_URL before prefect loads — the
same ordering invariant the CLI honors.
StructuredServerErrors
¶
Render unhandled route exceptions as the structured 500 envelope.
Starlette's stack is ServerErrorMiddleware → user middleware (CORS)
→ ExceptionMiddleware → routes: an exception no handler catches
propagates past the CORS middleware and becomes a bare text 500
without Access-Control-Allow-Origin — which a browser can only
report as TypeError: Failed to fetch, hiding the real error (the
exact failure mode of the first unwritable-./docs upload). This
pure-ASGI middleware is registered inside CORS, so its enveloped 500
flows through the CORS send path and stays readable cross-origin —
"errors are structured" (spec_v2 §4.1) holds for every failure.
A response whose headers already flushed (an SSE stream mid-flight)
can't change status; those re-raise, and the stream protocols carry
their own in-band error events instead.
Source code in varagity/api/main.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | |
__call__(scope, receive, send)
async
¶
Serve one connection, enveloping any unhandled exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
dict[str, Any]
|
The ASGI connection scope. |
required |
receive
|
Callable[..., Any]
|
The ASGI receive callable. |
required |
send
|
Callable[..., Any]
|
The ASGI send callable. |
required |
Source code in varagity/api/main.py
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | |
__init__(app)
¶
Wrap the downstream ASGI app.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Callable[..., Any]
|
The next ASGI callable in the stack. |
required |
Source code in varagity/api/main.py
278 279 280 281 282 283 284 | |
create_app()
¶
Build the configured FastAPI application.
Returns:
| Type | Description |
|---|---|
FastAPI
|
The app, ready for uvicorn ( |
Source code in varagity/api/main.py
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 | |
lifespan(app)
async
¶
Run startup work before the app serves: registries, then migrations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The FastAPI instance being started. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[None]
|
Nothing — control for the app's serving lifetime. |
Source code in varagity/api/main.py
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
varagity.api.schemas
¶
The HTTP wire contract — pydantic request/response models (spec_v2 §4.1).
Every request and response body is a model here; FastAPI renders them into
the OpenAPI schema the web app's TypeScript types are generated from
(openapi-typescript), so the contract cannot drift by hand-editing.
SSE event payloads (spec_v2 §4.3) are models too — one per event type —
because they cross the same wire.
BackupLogEvent
¶
Bases: BaseModel
Payload of the backup-status SSE log event.
Relayed varagity.backup log records (what was exported or restored,
a swept partial, an embedding-model drift warning, a graph workdir entry
the export had to skip, a graph restore undone because the restored
graph would not open), so the panel shows what the server log would.
Attributes:
| Name | Type | Description |
|---|---|---|
level |
str
|
The log level name ( |
message |
str
|
The formatted log message. |
Source code in varagity/api/schemas.py
1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 | |
BackupProgressEvent
¶
Bases: BaseModel
Payload of the backup-status SSE progress event.
A RAG export walks snapshot (open one consistent read of the corpus)
→ documents → chunks (rows written, with current/total) →
finalize (the vector matrix, the manifest, the atomic rename). A
graph export walks snapshot (take the graph's write lock, list its
workdir) → pack (the workdir's files copied in, counted in bytes)
→ finalize. A RAG restore walks validate (the bundle's verdict
again) → load (the replacing transaction's binary COPY, chunks
loaded so far) → index (the vector index rebuild and the commit —
the restore takes effect) → bm25 (the BM25 index refilled from the
restored rows, chunks so far). A graph restore walks validate →
extract (the bundle's files into a staging folder beside the
workdir, counted in bytes) → swap (the session closed, the
folders renamed) → open (the restored graph opened and checked).
Attributes:
| Name | Type | Description |
|---|---|---|
stage |
Literal['snapshot', 'documents', 'chunks', 'pack', 'finalize', 'validate', 'load', 'index', 'bm25', 'extract', 'swap', 'open']
|
|
current |
int | None
|
Rows ( |
total |
int | None
|
The stage's row ( |
Source code in varagity/api/schemas.py
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 | |
BackupRestoreRequest
¶
Bases: BaseModel
Body of POST /api/backups/{corpus}/restore (ADR-019).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The bundle to restore — a file name in the corpus's
|
Source code in varagity/api/schemas.py
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 | |
BackupRunOut
¶
Bases: BaseModel
One backup run's state (POST /api/backups/{corpus}/export or /restore).
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
str
|
The run handle. |
corpus |
Literal['rag', 'graph']
|
The corpus being backed up or restored. |
operation |
Literal['export', 'restore']
|
What the run does — |
state |
Literal['running', 'completed', 'failed']
|
|
bundle |
str | None
|
An export's: the file it wrote (set once it is published — an export names nothing until its bundle is whole). A restore's: the bundle it restores, from the start. |
saved_to |
str | None
|
Where an export's bundle landed, relative to the checkout
( |
started_at |
datetime
|
When the run started. |
finished_at |
datetime | None
|
When it reached a terminal state ( |
error |
str | None
|
The run-level failure ( |
summary |
BackupSummaryOut | None
|
The final counters ( |
Source code in varagity/api/schemas.py
1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 | |
BackupStatusEvent
¶
Bases: BaseModel
Payload of the backup-status SSE status event.
The stream's first frame (a snapshot on connect) and its last (the
terminal state). run=None means no backup run has run for this
corpus in this API process — the stream closes immediately after.
Attributes:
| Name | Type | Description |
|---|---|---|
run |
BackupRunOut | None
|
The current (or last) run, if any. |
Source code in varagity/api/schemas.py
1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 | |
BackupSummaryOut
¶
Bases: BaseModel
What a completed backup run did (terminal runs only).
Attributes:
| Name | Type | Description |
|---|---|---|
counts |
dict[str, int]
|
An export's: what the bundle holds — |
size_bytes |
int
|
The bundle's size on disk. |
wall_clock_s |
float
|
Seconds from the snapshot to the published bundle (an export), from validation to a searchable index (a RAG restore), or from validation to an open graph (a graph restore). |
stale |
bool | None
|
A restore's: whether it left the corpus flagged stale — for
RAG, the bundle was stale at export or its rows were made under
other ingest settings; for the graph, the bundle was stale at
export. |
Source code in varagity/api/schemas.py
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 | |
BundleManifestOut
¶
Bases: BundleManifest
A backup bundle's bundle.json, as the backups list reports it (ADR-019).
The file format's own model (:class:varagity.backup.bundle.BundleManifest)
on the wire unchanged — the panel shows what a bundle holds (counts)
and what it was made under (the embedding model and dimension, a RAG
bundle's ingest settings, a graph bundle's engine format, the stale
flag) straight from the manifest.
Source code in varagity/api/schemas.py
1243 1244 1245 1246 1247 1248 1249 1250 1251 | |
BundleOut
¶
Bases: BaseModel
One bundle file in GET /api/backups/{corpus} (ADR-019).
The verdict fields are the cheap check — the zip's directory and its manifest, never a hash (a restore hashes every member as it loads it, before anything takes effect). A bundle damaged inside an intact zip therefore lists as restorable until a restore fails on it; from then on, until its size or mtime changes, it lists as damaged (and a restore of it is refused up front).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The bundle's file name in the corpus's |
size_bytes |
int
|
Its size on disk. |
modified_at |
datetime
|
Its mtime (UTC). |
manifest |
BundleManifestOut | None
|
Its |
restorable |
bool
|
Whether nothing the cheap check sees refuses a restore. |
reason |
str | None
|
Why the bundle can't be restored ( |
drift |
list[str]
|
The reingest-affecting settings the bundle's rows were made under that differ from this stack's — restoring it flags the corpus stale. Empty when they match (or there is no manifest). |
Source code in varagity/api/schemas.py
1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 | |
ChatOverrides
¶
Bases: BaseModel
Per-request overrides of query-time settings (spec_v2 §4.2).
Unknown fields are rejected so a typo'd override fails loudly instead of silently running with defaults.
Attributes:
| Name | Type | Description |
|---|---|---|
retrieval_method |
str | None
|
Registry name of the retrieval method for this
question only ( |
top_k |
int | None
|
Number of chunks retrieved for this question only. |
chat_engine |
str | None
|
Registry name of the chat engine for this question
only ( |
Source code in varagity/api/schemas.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
ChatRequest
¶
Bases: BaseModel
Body of POST /api/chat (spec_v2 §4.2, spec_graphrag §4.2).
Attributes:
| Name | Type | Description |
|---|---|---|
query |
str
|
The user's question. |
conversation_id |
str | None
|
Existing conversation to append the turn to; a new conversation is created when omitted. |
overrides |
ChatOverrides | None
|
Optional per-request setting overrides. |
corpus |
Literal['rag', 'graph'] | None
|
Which corpus to answer from — |
Source code in varagity/api/schemas.py
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
ConfigResponse
¶
Bases: BaseModel
Response of GET /api/config — static capabilities (spec_v2 §4.2).
The GUI builds its settings controls from this, so a newly registered implementation (one file + one import) appears in the UI automatically.
Attributes:
| Name | Type | Description |
|---|---|---|
retrievers |
list[str]
|
Registered retrieval method names. |
chunkers |
list[str]
|
Registered chunking strategy names. |
ocr_engines |
list[str]
|
Available OCR engine names. |
model_types |
list[str]
|
Valid |
llm_model_types |
list[str]
|
The chat-capable subset of |
ranges |
dict[str, NumericRange]
|
Valid ranges for the numeric query-time knobs, keyed by setting name (lowercase). |
upload_max_mb |
int
|
Effective per-file upload cap ( |
allowed_extensions |
list[str]
|
Effective ingestable extensions
( |
preview_enabled |
bool
|
Whether the page-preview endpoints are on
( |
Source code in varagity/api/schemas.py
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 | |
ConversationCreateRequest
¶
Bases: BaseModel
Body of POST /api/conversations.
Attributes:
| Name | Type | Description |
|---|---|---|
title |
str | None
|
Optional explicit title; defaults to the placeholder the first chat turn auto-replaces. |
Source code in varagity/api/schemas.py
294 295 296 297 298 299 300 301 302 | |
ConversationDetailOut
¶
Bases: BaseModel
Response of GET /api/conversations/{id} — the full transcript.
Attributes:
| Name | Type | Description |
|---|---|---|
conversation_id |
str
|
The app-generated id. |
title |
str
|
Current title. |
created_at |
datetime
|
Creation timestamp. |
updated_at |
datetime
|
Last-turn timestamp. |
messages |
list[MessageOut]
|
All messages, oldest first, each with its sources. |
Source code in varagity/api/schemas.py
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 | |
ConversationSummaryOut
¶
Bases: BaseModel
One conversation in the GET /api/conversations list.
Attributes:
| Name | Type | Description |
|---|---|---|
conversation_id |
str
|
The app-generated id. |
title |
str
|
Current title. |
created_at |
datetime
|
Creation timestamp. |
updated_at |
datetime
|
Last-turn timestamp (list ordering key). |
message_count |
int
|
Number of persisted messages. |
group_id |
str | None
|
Sidebar group the conversation is filed under ( |
Source code in varagity/api/schemas.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
ConversationUpdateRequest
¶
Bases: BaseModel
Body of PATCH /api/conversations/{conversation_id}.
Unknown fields are rejected so a typo'd patch fails loudly instead of silently doing nothing.
Attributes:
| Name | Type | Description |
|---|---|---|
group_id |
str | None
|
Sidebar group to file the conversation under, or |
Source code in varagity/api/schemas.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
DeltaEvent
¶
Bases: BaseModel
Payload of the SSE reasoning and token events.
Attributes:
| Name | Type | Description |
|---|---|---|
delta |
str
|
The next text fragment, in stream order. |
Source code in varagity/api/schemas.py
207 208 209 210 211 212 213 214 | |
DocumentBulkDeleteRequest
¶
Bases: BaseModel
Body of POST /api/documents/delete (spec_v2 §4.2).
Attributes:
| Name | Type | Description |
|---|---|---|
doc_ids |
list[str]
|
The documents to remove, at least one. Duplicates collapse and unknown ids are reported rather than fatal, so the corpus table's selection can be posted as-is. |
Source code in varagity/api/schemas.py
734 735 736 737 738 739 740 741 742 743 744 745 | |
DocumentBulkDeleteResponse
¶
Bases: BaseModel
Response of POST /api/documents/delete (spec_v2 §4.2).
Attributes:
| Name | Type | Description |
|---|---|---|
deleted |
list[DocumentDeleteResponse]
|
Per-document outcomes, in requested order (duplicates collapsed) — the same shape a single delete returns. |
not_found |
list[str]
|
Requested ids that no longer had a |
Source code in varagity/api/schemas.py
748 749 750 751 752 753 754 755 756 757 758 759 760 | |
DocumentDeleteResponse
¶
Bases: BaseModel
Response of DELETE /api/documents/{doc_id} (spec_v2 §4.2).
Also one entry of a bulk delete's deleted list.
Attributes:
| Name | Type | Description |
|---|---|---|
doc_id |
str
|
The deleted document. |
chunks_deleted |
int
|
Chunks removed (the pgvector count; the
Elasticsearch |
file_removed |
bool
|
Whether the source file was also deleted from
|
Source code in varagity/api/schemas.py
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 | |
DocumentOut
¶
Bases: BaseModel
One corpus document in the GET /api/documents list (spec_v2 §4.2).
Attributes:
| Name | Type | Description |
|---|---|---|
doc_id |
str
|
The document's stable id. |
file_name |
str
|
Base name of the source file. |
source |
str
|
Absolute file path recorded at ingest time. |
relative_path |
str | None
|
The source's path relative to the corpus directory
( |
file_type |
str
|
File extension without the dot ( |
content_hash |
str
|
sha256 of the source file's bytes at ingest time. |
n_chunks |
int
|
Chunks ingested ( |
ingested_at |
datetime
|
When the document (last) landed in the stores. |
extraction_mix |
dict[str, int]
|
Chunk count per extraction method ( |
Source code in varagity/api/schemas.py
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 | |
DoneEvent
¶
Bases: BaseModel
Payload of the terminal SSE done event (spec_v2 §4.3).
Attributes:
| Name | Type | Description |
|---|---|---|
message_id |
str
|
Persisted assistant message id. |
conversation_id |
str
|
The conversation the turn was persisted into (the one just created, when the request named none). |
answer |
str
|
The full, |
usage |
UsageInfo
|
Token usage and per-stage latency. |
Source code in varagity/api/schemas.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | |
ErrorBody
¶
Bases: BaseModel
The machine-readable error inside the envelope (spec_v2 §4.1).
Attributes:
| Name | Type | Description |
|---|---|---|
code |
str
|
Stable, machine-readable code (e.g. |
message |
str
|
Human-readable detail. |
Source code in varagity/api/schemas.py
23 24 25 26 27 28 29 30 31 32 33 | |
ErrorEvent
¶
Bases: BaseModel
Payload of the in-band SSE error event.
Emitted when the pipeline fails after the stream already opened (the 200 headers are gone — status can't change; spec_v2 plan decision #7).
Attributes:
| Name | Type | Description |
|---|---|---|
code |
str
|
Stable, machine-readable code. |
message |
str
|
Human-readable detail. |
Source code in varagity/api/schemas.py
279 280 281 282 283 284 285 286 287 288 289 290 291 | |
ErrorResponse
¶
Bases: BaseModel
The structured error envelope every non-2xx response carries.
Attributes:
| Name | Type | Description |
|---|---|---|
error |
ErrorBody
|
The error body. |
Source code in varagity/api/schemas.py
36 37 38 39 40 41 42 43 | |
GraphBuildLogEvent
¶
Bases: BaseModel
Payload of the graph-build SSE log event.
Relayed varagity.graph log records (the parse summaries, the build
diff's new/changed/unchanged counts, per-document failures), so the
browser sees what the server log would show.
Attributes:
| Name | Type | Description |
|---|---|---|
level |
str
|
The log level name ( |
message |
str
|
The formatted log message. |
Source code in varagity/api/schemas.py
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 | |
GraphBuildProgressEvent
¶
Bases: BaseModel
Payload of the graph-build SSE progress event.
Two phases, because a graph build has two very different clocks:
scan/parse walk the corpus directory in seconds, then
index/process hand the merged messages to the engine and sample
its own document statuses for hours (the extraction pass — ADR-017's
7.17 s/message).
Attributes:
| Name | Type | Description |
|---|---|---|
stage |
str
|
|
file |
str | None
|
The file being parsed ( |
current |
int | None
|
Intra-stage progress (files parsed so far; messages kept by
|
total |
int | None
|
Stage denominator (files found, messages parsed). |
docs_done |
int | None
|
Documents the engine reports as processed ( |
docs_total |
int | None
|
Documents the engine holds in any status ( |
Source code in varagity/api/schemas.py
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 | |
GraphBuildRequest
¶
Bases: BaseModel
Body of POST /api/graph/build (spec_graphrag §5.2).
Attributes:
| Name | Type | Description |
|---|---|---|
reingest |
bool
|
Wipe the engine's working directory and index from scratch. The only thing that clears the graph-stale flag (stage-2 decision #16) — and the only reason to pay a full backfill twice. |
message_limit |
int | None
|
Index at most this many messages, newest first — the bounded escape hatch that makes a minutes-long spot check possible on an archive whose full build runs for days. A bounded build never prunes: its render is partial by construction, so documents it does not mention are kept, not deleted. |
since |
date | None
|
Ignore messages older than this date (the other half of the bound; same no-prune rule). |
Source code in varagity/api/schemas.py
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 | |
GraphBuildRunOut
¶
Bases: BaseModel
One graph build run's state (POST /api/graph/build + its stream).
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
str
|
The run handle. |
state |
str
|
|
reingest |
bool
|
Whether the run wiped the working directory first. |
bounded |
bool
|
Whether |
message_limit |
int | None
|
The requested message cap, if any. |
since |
date | None
|
The requested date floor, if any. |
started_at |
datetime
|
When the run started. |
finished_at |
datetime | None
|
When it reached a terminal state ( |
summary |
GraphBuildSummaryOut | None
|
The final counters (terminal states only; a run that died
before the engine returned carries |
error |
str | None
|
The run-level failure ( |
Source code in varagity/api/schemas.py
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 | |
GraphBuildStatusEvent
¶
Bases: BaseModel
Payload of the graph-build SSE status event.
The stream's first frame (a snapshot on connect) and its last (the
terminal state). run=None means no build has run in this API
process — the stream closes immediately after.
Attributes:
| Name | Type | Description |
|---|---|---|
run |
GraphBuildRunOut | None
|
The current (or last) run, if any. |
Source code in varagity/api/schemas.py
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 | |
GraphBuildSummaryOut
¶
Bases: BaseModel
The graph build's counters (terminal runs only).
Attributes:
| Name | Type | Description |
|---|---|---|
files_scanned |
int
|
Files found under |
files_parsed |
int
|
Of those, files a registered message source claimed. |
files_failed |
int
|
Files that raised while parsing (the run continued). |
messages_parsed |
int
|
Messages recovered from every parsed file. |
messages_indexed |
int
|
Messages handed to the engine after the bounds
were applied (equal to |
messages_seen |
int
|
What the engine reported after guid-merging the batches — the upsert grain. |
wall_clock_s |
float
|
Seconds the engine spent indexing. |
resumed |
int
|
Documents already in flight (or failed) when this run started — the work a killed build left behind and this one picked up. |
documents |
dict[str, int]
|
The engine's document counts by status when the run finished. |
failures |
list[str]
|
Per-step failures the build caught and continued past. |
Source code in varagity/api/schemas.py
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 | |
GraphDocumentDeleteResponse
¶
Bases: BaseModel
Response of DELETE /api/graph/documents/{name}.
Attributes:
| Name | Type | Description |
|---|---|---|
relative_path |
str
|
The removed file's path under |
graph_stale |
bool
|
Whether the graph is now flagged stale — it keeps the deleted file's messages until the next rebuild, and saying so is the honest alternative to silently diverging (stage-2 decision 16).
|
Source code in varagity/api/schemas.py
944 945 946 947 948 949 950 951 952 953 954 955 956 | |
GraphDocumentOut
¶
Bases: BaseModel
One file in the GET /api/graph/documents list (spec_graphrag §10.2).
Unlike the chunk-RAG corpus list — which reports the documents
table — this is a directory listing: the graph's own record of what it
indexed lives in the engine's workdir, and the tab's job is to show what
has been dropped in. Non-source files (a contacts list, the -wal /
-shm sidecars a chat.db needs) are listed too, with a None
doc_id: they are on disk and the user put them there.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Base name of the file. |
relative_path |
str
|
Its POSIX path relative to |
size_bytes |
int
|
Size on disk. |
modified_at |
datetime
|
The file's mtime. |
doc_id |
str | None
|
The stable id a build derives for it (path relative to
|
parse |
GraphParseSummary | None
|
What the last scan parsed out of it, or |
Source code in varagity/api/schemas.py
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 | |
GraphEntityDetailOut
¶
Bases: BaseModel
Response of GET /api/graph/entities/{name} — the inspect panel.
Attributes:
| Name | Type | Description |
|---|---|---|
entity |
GraphExportNodeOut
|
The entity itself, with the summary the engine merged from every mention. |
relations |
list[GraphExportEdgeOut]
|
Every edge touching it in a depth-1 slice, i.e. its immediate neighbourhood. |
sources |
list[GraphTranscriptRefOut]
|
The transcript days it was extracted from — the drill-down from a name in the graph back to the conversations behind it. Empty when the engine recorded no document provenance. |
Source code in varagity/api/schemas.py
1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 | |
GraphEntityOut
¶
Bases: BaseModel
One entity the graph retrieval surfaced (spec_graphrag §10.2).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The entity's canonical name, as the engine resolved it. |
type |
str | None
|
The engine's entity type/category ( |
summary |
str | None
|
The engine's description of the entity. |
Source code in varagity/api/schemas.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
GraphExportEdgeOut
¶
Bases: BaseModel
One relation in the drawable graph slice (spec_graphrag §4.4).
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable edge identity within the slice ( |
source |
str
|
Name of the edge's source entity. |
target |
str
|
Name of the edge's target entity. |
label |
str | None
|
Short relation label/keywords ( |
description |
str | None
|
The engine's longer description of the relation. |
Source code in varagity/api/schemas.py
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 | |
GraphExportNodeOut
¶
Bases: BaseModel
One entity in the drawable graph slice (spec_graphrag §4.4).
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
The entity's canonical name — also the node's identity in the view and the key the entity-detail route takes. |
entity_type |
str | None
|
The engine's type/category. This is what the view
colors and groups by: LightRAG builds no community tier, so
type is the honest clustering rather than a faked one (ADR-017).
|
description |
str | None
|
The engine's merged description of the entity. |
degree |
int
|
How many of this slice's edges touch the node — the size channel in the view. A subgraph degree, not a whole-graph one: the export is capped, so the number describes the picture drawn. |
Source code in varagity/api/schemas.py
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 | |
GraphExportOut
¶
Bases: BaseModel
Response of GET /api/graph/export — what the graph view draws.
Attributes:
| Name | Type | Description |
|---|---|---|
nodes |
list[GraphExportNodeOut]
|
The exported entities, engine order (degree-descending for a whole-graph export, so the cap keeps the most connected). |
edges |
list[GraphExportEdgeOut]
|
Every edge between exported nodes. |
truncated |
bool
|
Whether the cap hid nodes the graph actually holds. The view must surface this rather than implying it drew everything — an honest partial picture, per §4.4's scale ceiling. |
Source code in varagity/api/schemas.py
1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 | |
GraphParseSummary
¶
Bases: BaseModel
What the last build's scan found inside one graph source file.
Attributes:
| Name | Type | Description |
|---|---|---|
messages |
int
|
Recoverable messages parsed from the file. |
threads |
int
|
Distinct conversations they belong to. |
first |
datetime | None
|
Oldest message timestamp ( |
last |
datetime | None
|
Newest message timestamp ( |
Source code in varagity/api/schemas.py
884 885 886 887 888 889 890 891 892 893 894 895 896 897 | |
GraphRelationOut
¶
Bases: BaseModel
One relation the graph retrieval surfaced (spec_graphrag §10.2).
Attributes:
| Name | Type | Description |
|---|---|---|
source |
str
|
Name of the edge's source entity. |
target |
str
|
Name of the edge's target entity. |
label |
str | None
|
Short relation label/keywords ( |
description |
str | None
|
The engine's longer description — the fact the answer grounds on. |
Source code in varagity/api/schemas.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
GraphRetrievalPayload
¶
Bases: BaseModel
The graph half of the SSE retrieval event (spec_graphrag §4.3).
Attributes:
| Name | Type | Description |
|---|---|---|
mode |
str
|
The engine query mode the turn retrieved with
( |
entities |
list[GraphEntityOut]
|
Entities the retrieval surfaced. |
relations |
list[GraphRelationOut]
|
Relations the retrieval surfaced. |
transcripts |
list[GraphTranscriptOut]
|
The cited transcript days, best first — the cards a
citation chip resolves to, labelled |
Source code in varagity/api/schemas.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
GraphStatusOut
¶
Bases: BaseModel
Response of GET /api/graph/status — the graph tab's poll surface.
Every size field is optional because "nothing has been built here yet"
and "the engine would not say" are both real states, and reporting them
as 0 would read like an empty graph (the honesty rule
:class:~varagity.graph.records.GraphStats sets).
Attributes:
| Name | Type | Description |
|---|---|---|
enabled |
bool
|
The |
stale |
bool
|
Whether a source file was deleted since the last rebuild. |
building |
bool
|
Whether a build is running in this API process. |
documents |
dict[str, int]
|
The engine's document counts by status (its own
vocabulary: |
entities |
int | None
|
Entity count from the workdir's summary sidecar. |
relations |
int | None
|
Relation count from the same. |
message_guids |
int | None
|
Messages the workdir's manifest accounts for. |
workdir_files |
int
|
Files in the engine's working directory that a
backup would copy (ADR-019) — |
last_build |
GraphBuildRunOut | None
|
The current (or last) build run in this API process. |
Source code in varagity/api/schemas.py
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 | |
GraphTranscriptOut
¶
Bases: BaseModel
One cited transcript day of a graph answer (spec_graphrag §4.3).
Evidence is day-grain, not message-grain: that is what the engines can honestly attribute (ADR-017's priced regret), so this is the card the evidence panel draws and the label the answer cites.
Attributes:
| Name | Type | Description |
|---|---|---|
doc_key |
str
|
The transcript document key — the join back to the corpus. |
thread_name |
str
|
Human-facing thread label. |
span |
str
|
The document's day span ( |
excerpt |
str
|
The retrieved passage, capped for the wire. |
message_count |
int
|
Messages the graph's manifest accounts for in that
document ( |
Source code in varagity/api/schemas.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
GraphTranscriptRefOut
¶
Bases: BaseModel
One transcript day an entity was extracted from (the drill-down).
The evidence panel's :class:GraphTranscriptOut without the retrieved
text: this is a pointer into the corpus ("Bob was talked about on
these days"), not a retrieval hit, so there is no excerpt to show.
Attributes:
| Name | Type | Description |
|---|---|---|
doc_key |
str
|
The transcript document key — the join back to the corpus. |
thread_name |
str
|
Human-facing thread label, resolved through the workdir manifest (falling back to the key's own thread id). |
span |
str
|
The document's day span ( |
message_count |
int
|
Messages the manifest accounts for in that document
( |
Source code in varagity/api/schemas.py
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 | |
GraphUploadResponse
¶
Bases: BaseModel
Response of POST /api/graph/documents.
Attributes:
| Name | Type | Description |
|---|---|---|
files |
list[UploadedFileOut]
|
Per-file outcomes, in upload order (the corpus upload's
shape, plus its |
Source code in varagity/api/schemas.py
933 934 935 936 937 938 939 940 941 | |
GroupCreateRequest
¶
Bases: BaseModel
Body of POST /api/groups.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Display name of the new group. |
Source code in varagity/api/schemas.py
421 422 423 424 425 426 427 428 429 430 | |
GroupOut
¶
Bases: BaseModel
One sidebar conversation group (a user-created folder).
Attributes:
| Name | Type | Description |
|---|---|---|
group_id |
str
|
The app-generated id. |
name |
str
|
Display name. |
created_at |
datetime
|
Creation timestamp. |
Source code in varagity/api/schemas.py
433 434 435 436 437 438 439 440 441 442 443 444 | |
HealthResponse
¶
Bases: BaseModel
Response of GET /api/health.
The endpoint answers 200 whenever the API process itself is alive —
per-dependency state lives in the body, so a flapping backing service
doesn't mark the api container unhealthy.
Attributes:
| Name | Type | Description |
|---|---|---|
ok |
bool
|
|
services |
dict[str, ServiceHealth]
|
Probe result per service name ( |
Source code in varagity/api/schemas.py
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 | |
IngestLogEvent
¶
Bases: BaseModel
Payload of the ingest-status SSE log event.
Relayed varagity.ingest log records (the per-file outcome lines the
terminal shows: skipped-unchanged, no-text, failure tracebacks' heads),
so the browser progress view mirrors the CLI run.
Attributes:
| Name | Type | Description |
|---|---|---|
level |
str
|
The log level name ( |
message |
str
|
The formatted log message. |
Source code in varagity/api/schemas.py
868 869 870 871 872 873 874 875 876 877 878 879 880 881 | |
IngestProgressEvent
¶
Bases: BaseModel
Payload of the ingest-status SSE progress event.
One frame per pipeline stage transition, mirroring the CLI's rich
display: discover (with total files), then per file parse →
chunk → contextualize (with per-chunk current/total
ticks) → embed → store, and a file_done frame carrying the
file's outcome plus the run-level progress counters.
Attributes:
| Name | Type | Description |
|---|---|---|
stage |
str
|
|
file |
str | None
|
The file being processed ( |
outcome |
str | None
|
|
current |
int | None
|
Intra-stage progress (contextualized chunks so far). |
total |
int | None
|
Stage denominator (files for |
files_done |
int | None
|
Files finished so far ( |
files_total |
int | None
|
Files discovered ( |
Source code in varagity/api/schemas.py
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 | |
IngestRunOut
¶
Bases: BaseModel
One ingest run's state (POST /api/ingest + the status stream).
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
str
|
The run handle. |
state |
str
|
|
reingest |
bool
|
Whether the run re-processes unchanged files. |
started_at |
datetime
|
When the run started. |
finished_at |
datetime | None
|
When it reached a terminal state ( |
summary |
IngestSummaryOut | None
|
The final counters (terminal states only; |
error |
str | None
|
The flow-level failure ( |
Source code in varagity/api/schemas.py
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 | |
IngestStartRequest
¶
Bases: BaseModel
Body of POST /api/ingest.
Attributes:
| Name | Type | Description |
|---|---|---|
reingest |
bool
|
Delete each discovered document's previous ingest and re-process it (required after reingest-affecting setting changes — content hashes don't change, so unchanged files are otherwise skipped). |
Source code in varagity/api/schemas.py
763 764 765 766 767 768 769 770 771 772 773 | |
IngestStatusEvent
¶
Bases: BaseModel
Payload of the ingest-status SSE status event.
The stream's first frame (a snapshot on connect) and its last (the
terminal state). run=None means no ingest has run in this API
process — the stream closes immediately after.
Attributes:
| Name | Type | Description |
|---|---|---|
run |
IngestRunOut | None
|
The current (or last) run, if any. |
Source code in varagity/api/schemas.py
823 824 825 826 827 828 829 830 831 832 833 834 | |
IngestSummaryOut
¶
Bases: BaseModel
The ingest run counters (mirrors the loader's IngestSummary).
Attributes:
| Name | Type | Description |
|---|---|---|
discovered |
int
|
Files found in the corpus buckets. |
ingested |
int
|
Files parsed, chunked, embedded, and stored this run. |
skipped |
int
|
Unchanged files skipped via the idempotency check. |
no_text |
int
|
Files with no extractable text. |
unsupported |
int
|
Files whose bucket has no registered parser. |
failed |
int
|
Files that raised during ingestion (run continued). |
chunks |
int
|
Total chunks stored this run. |
Source code in varagity/api/schemas.py
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 | |
MessageOut
¶
Bases: BaseModel
One persisted message in a transcript.
Attributes:
| Name | Type | Description |
|---|---|---|
message_id |
str
|
The app-generated id. |
role |
str
|
|
content |
str
|
The question or the generated answer. |
created_at |
datetime
|
Persistence timestamp. |
retrieval_method |
str | None
|
Retrieval method of an assistant turn. |
latency_ms |
dict[str, Any] | None
|
Per-stage timings of an assistant turn. |
reasoning |
str | None
|
Captured |
condensed_query |
str | None
|
The standalone search query that drove an
assistant turn's retrieval (spec_v3 §8 — snapshot semantics,
like the sources: it explains a historical answer). |
chat_engine |
str | None
|
Registry name of the chat engine that produced an
assistant turn ( |
target_corpus |
str | None
|
The corpus the turn asked for ( |
graph_evidence |
dict[str, Any] | None
|
The graph retrieval's |
sources |
list[MessageSourceOut]
|
The turn's snapshotted evidence, rank order. |
Source code in varagity/api/schemas.py
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
MessageSourceOut
¶
Bases: BaseModel
One snapshotted evidence row of a persisted assistant turn.
Attributes:
| Name | Type | Description |
|---|---|---|
rank |
int
|
Final rank in the answer's evidence (1-based). |
chunk_id |
str
|
Soft reference to the producing chunk — or, on a graph
turn, the cited transcript day's |
trace |
dict[str, Any]
|
The spec_v2 §9.1 snapshot: score, content, context, source
provenance, and the serialized retrieval trace. A graph turn's
rows carry the other shape ( |
Source code in varagity/api/schemas.py
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
NumericRange
¶
Bases: BaseModel
Valid range of one numeric setting, for GUI control generation.
Attributes:
| Name | Type | Description |
|---|---|---|
min |
float | None
|
Inclusive lower bound ( |
max |
float | None
|
Inclusive upper bound ( |
Source code in varagity/api/schemas.py
476 477 478 479 480 481 482 483 484 485 | |
PreviewLocateRequest
¶
Bases: BaseModel
Body of POST /api/documents/{doc_id}/preview/locate (ADR-010).
Attributes:
| Name | Type | Description |
|---|---|---|
text |
str
|
The chunk content to locate — both wire shapes (the live
|
Source code in varagity/api/schemas.py
630 631 632 633 634 635 636 637 638 639 640 | |
PreviewLocateResponse
¶
Bases: BaseModel
Where a chunk's text lives in its source document (ADR-010).
available=False + reason covers every degradable condition
(preview_disabled | unsupported_type | file_missing |
file_changed | conversion_unavailable | conversion_failed |
no_match) — the GUI falls back to the full-text view on any of
them, never a dead panel.
Attributes:
| Name | Type | Description |
|---|---|---|
available |
bool
|
Whether a page was located (the fields below are set). |
reason |
str | None
|
The degradable condition when |
page |
int | None
|
Best-matching page, 1-based. |
page_count |
int | None
|
Total pages in the document (also set on |
rects |
list[PreviewRect]
|
Highlight rectangles for the chunk's text on that page. |
coverage |
float | None
|
The winning page's containment score in |
Source code in varagity/api/schemas.py
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 | |
PreviewRect
¶
Bases: BaseModel
One highlight rectangle, normalized to the page ([0, 1], top-left origin).
Y-flipped from PDF coordinates server-side, so the client positions overlay divs with bare percentages and no coordinate math.
Attributes:
| Name | Type | Description |
|---|---|---|
x0 |
float
|
Left edge. |
y0 |
float
|
Top edge. |
x1 |
float
|
Right edge ( |
y1 |
float
|
Bottom edge ( |
Source code in varagity/api/schemas.py
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 | |
RetrievalEvent
¶
Bases: BaseModel
Payload of the SSE retrieval event — the provenance panel's data.
Emitted once retrieval (+rerank) completes, before any answer token
(spec_v2 §4.3): the browser gets the evidence before the prose. One
event name covers both corpora (spec_graphrag §4.3): a graph turn fills
graph and leaves chunks empty, and a graph turn that degraded
to a chunk answer is visibly exactly that — corpus="graph" with
graph=null and chunks present (ADR-017's degrade semantics).
Attributes:
| Name | Type | Description |
|---|---|---|
chunks |
list[RetrievedChunk]
|
The retrieved chunks, best first, each carrying its full
metadata record and (when the method fills it) the
:class: |
method |
str
|
What actually retrieved, in the corpus's own vocabulary: the
retrieval-method registry name for a chunk turn, the engine
query mode for a graph one (also in |
top_k |
int
|
Chunks requested from the retriever — a chunk-retrieval knob, reported unchanged on a graph turn, which bounds its evidence engine-side instead. |
reranked_to |
int | None
|
|
condensed_query |
str | None
|
The standalone search query the chat engine
rewrote this turn into (spec_v3 §4.7) — retrieval metadata,
like |
corpus |
str
|
The corpus the turn asked for ( |
graph |
GraphRetrievalPayload | None
|
The graph evidence, present only on a graph turn that the engine actually answered. |
Source code in varagity/api/schemas.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
ServiceHealth
¶
Bases: BaseModel
Reachability of one backing service (spec_v2 §4.2).
Attributes:
| Name | Type | Description |
|---|---|---|
ok |
bool
|
Whether the service answered its probe. |
detail |
str | None
|
Failure detail when it didn't ( |
Source code in varagity/api/schemas.py
447 448 449 450 451 452 453 454 455 456 | |
SettingOut
¶
Bases: BaseModel
One effective setting in the GET /api/settings catalog (spec_v2 §4.7).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The |
value |
SettingValue
|
The effective value (env defaults merged with any override). |
group |
str
|
Drawer group — |
overridden |
bool
|
Whether a persisted runtime override is in effect (the drawer shows a reset affordance). |
reingest_affecting |
bool
|
Whether changing it marks the corpus stale (it doesn't change content hashes — the surfaced v1 footgun). |
choices |
list[str] | None
|
Valid values for enum-like settings (registry-derived, so a
new implementation appears automatically); |
Source code in varagity/api/schemas.py
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 | |
SettingsPatchRequest
¶
Bases: BaseModel
Body of PATCH /api/settings.
Attributes:
| Name | Type | Description |
|---|---|---|
overrides |
dict[str, SettingValue | None]
|
Setting name → new value, or |
Source code in varagity/api/schemas.py
564 565 566 567 568 569 570 571 572 573 574 575 576 | |
SettingsResponse
¶
Bases: BaseModel
Response of GET /api/settings and PATCH /api/settings.
Attributes:
| Name | Type | Description |
|---|---|---|
settings |
list[SettingOut]
|
The full overridable catalog with effective values. |
corpus_stale |
bool
|
Whether a reingest-affecting setting changed since the corpus was last (re)ingested — the "Re-ingest to apply" banner. |
graph_stale |
bool
|
Whether the graph corpus changed since the graph was last (re)built — the graph tab's "Re-build to apply" banner (stage-2 decision #16). |
Source code in varagity/api/schemas.py
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 | |
StatsEvent
¶
Bases: BaseModel
Payload of the SSE stats event — live decode throughput.
Emitted while generation runs, interleaved with the reasoning/
token deltas and throttled server-side (the model server reports
these counters on every chunk; a frame per token would be noise).
Purely additive to the protocol: the event only exists when the model
server reports its own timings — llama.cpp does, so the readout is
llama.cpp-only by construction rather than by configuration.
Attributes:
| Name | Type | Description |
|---|---|---|
tokens_per_second |
float
|
Decode throughput so far, averaged over the whole generation (cumulative, not instantaneous — it settles rather than jitters). |
completion_tokens |
int
|
Tokens decoded so far, the denominator behind that average. |
Source code in varagity/api/schemas.py
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | |
UploadResponse
¶
Bases: BaseModel
Response of POST /api/documents.
Attributes:
| Name | Type | Description |
|---|---|---|
files |
list[UploadedFileOut]
|
Per-file outcomes, in upload order. |
Source code in varagity/api/schemas.py
704 705 706 707 708 709 710 711 | |
UploadedFileOut
¶
Bases: BaseModel
Outcome for one file of a POST /api/documents upload.
Attributes:
| Name | Type | Description |
|---|---|---|
file_name |
str
|
The stored (sanitized) file name (rejections echo the client-supplied name/path they rejected). |
size_bytes |
int
|
Bytes written ( |
stored |
bool
|
Whether the file landed in |
replaced |
bool
|
Whether an existing file at the same target was overwritten (a re-upload; the next ingest re-processes it under a new hash). |
reason |
str | None
|
Rejection reason when |
relative_path |
str | None
|
The stored path relative to |
Source code in varagity/api/schemas.py
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 | |
UsageInfo
¶
Bases: BaseModel
Token usage and per-stage latency reported by the done event.
Attributes:
| Name | Type | Description |
|---|---|---|
prompt_tokens |
int | None
|
Server-reported prompt tokens ( |
completion_tokens |
int | None
|
Server-reported completion tokens ( |
latency_ms |
dict[str, int]
|
Wall-clock milliseconds per stage: |
tokens_per_second |
float | None
|
Final decode throughput as the model server
measured it, or |
Source code in varagity/api/schemas.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | |
varagity.api.streaming
¶
SSE event framing and the flow→stream bridge (spec_v2 §4.3).
Two concerns live here:
- Typed framing — one constructor per event in the chat protocol
(
retrieval,reasoning,token,stats,done,error), each pairing the event name with its :mod:varagity.api.schemaspayload, so routes can't emit a misnamed or mistyped frame. - The bridge — the query pipeline is synchronous and runs in a worker
thread (spec_v2 §4.1: async at the edge, sync flows underneath), while
the SSE response is an async generator on the event loop.
:class:
EventBridgecarries frames from the flow's callbacks to the generator (thread-safe, order-preserving) and carries the client's disconnect back to the flow as an abort flag polled between tokens.
EventBridge
¶
Order-preserving conduit between a worker-thread flow and an SSE stream.
Built on the running event loop; the flow's callbacks call :meth:emit
from their thread (marshalled with call_soon_threadsafe), the async
generator drains :meth:events, and the stream side signals the flow to
stop with :meth:abort (polled via :meth:should_abort between
tokens — how a closed tab frees the GPU, spec_v2 §4.3 cancellation).
Must be constructed on the event loop thread.
Source code in varagity/api/streaming.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
__init__()
¶
Capture the running loop and start with an empty queue.
Source code in varagity/api/streaming.py
120 121 122 123 124 | |
abort()
¶
Ask the producing flow to stop at its next between-tokens check.
Source code in varagity/api/streaming.py
147 148 149 | |
close()
¶
Signal end-of-stream; :meth:events finishes after the backlog.
Source code in varagity/api/streaming.py
143 144 145 | |
emit(event, payload)
¶
Enqueue one frame from any thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
str
|
The SSE event name. |
required |
payload
|
BaseModel
|
The event's typed payload. |
required |
Source code in varagity/api/streaming.py
126 127 128 129 130 131 132 133 | |
emit_frame(frame)
¶
Enqueue an already-framed event from any thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ServerSentEvent
|
The framed event. |
required |
Source code in varagity/api/streaming.py
135 136 137 138 139 140 141 | |
events()
async
¶
Drain frames in emission order until :meth:close.
Yields:
| Type | Description |
|---|---|
AsyncIterator[ServerSentEvent]
|
Each enqueued frame. |
Source code in varagity/api/streaming.py
159 160 161 162 163 164 165 166 | |
should_abort()
¶
Whether :meth:abort was called (the flow's poll seam).
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in varagity/api/streaming.py
151 152 153 154 155 156 157 | |
delta_event(kind, text)
¶
Frame one classified text fragment as reasoning or token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
The splitter's classification — |
required |
text
|
str
|
The fragment. |
required |
Returns:
| Type | Description |
|---|---|
ServerSentEvent
|
The framed event. |
Source code in varagity/api/streaming.py
56 57 58 59 60 61 62 63 64 65 66 67 68 | |
done_event(payload)
¶
Frame the terminal done event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
DoneEvent
|
Message/conversation ids, the full answer, and usage. |
required |
Returns:
| Type | Description |
|---|---|
ServerSentEvent
|
The framed event. |
Source code in varagity/api/streaming.py
83 84 85 86 87 88 89 90 91 92 | |
error_event(code, message)
¶
Frame an in-band error event (mid-stream failure).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
Stable, machine-readable code. |
required |
message
|
str
|
Human-readable detail. |
required |
Returns:
| Type | Description |
|---|---|
ServerSentEvent
|
The framed event. |
Source code in varagity/api/streaming.py
95 96 97 98 99 100 101 102 103 104 105 | |
retrieval_event(payload)
¶
Frame the provenance payload as the retrieval event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
RetrievalEvent
|
The retrieved chunks and method metadata. |
required |
Returns:
| Type | Description |
|---|---|
ServerSentEvent
|
The framed event. |
Source code in varagity/api/streaming.py
44 45 46 47 48 49 50 51 52 53 | |
stats_event(payload)
¶
Frame one live-throughput reading as the stats event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
StatsEvent
|
The decode throughput so far and its token count. |
required |
Returns:
| Type | Description |
|---|---|
ServerSentEvent
|
The framed event. |
Source code in varagity/api/streaming.py
71 72 73 74 75 76 77 78 79 80 | |
varagity.api.deps
¶
FastAPI dependencies: providers, health probes, and the chat preflight.
Providers are deliberately thin functions so tests swap them via
app.dependency_overrides — the same seam the flows expose with their
injectable retriever/llm parameters. The health probes back both
GET /api/health (report) and the chat preflight (clean 503 with a
machine-readable code before the SSE stream opens — once the 200 headers
flush, status can't change; spec_v2 plan decision #7).
It is also where a corpus's write gate turns into HTTP (ADR-019): the one
409 corpus_busy envelope every start and delete route answers with, and
the 503 corpus_restoring a reader gets while a backup restore replaces
the corpus it reads — :func:require_rag_readable is that refusal as a
dependency, for every route that queries documents/chunks.
check_services(names)
async
¶
Probe the named backing services concurrently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
tuple[str, ...]
|
Service names among |
required |
Returns:
| Type | Description |
|---|---|
dict[str, ServiceHealth]
|
Probe outcome per requested name. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a name is not a known service. |
Source code in varagity/api/deps.py
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |
corpus_busy_error(error)
¶
Build the structured 409 corpus_busy for a corpus another operation holds.
The one envelope every route uses — an ingest or build start, a backup start, a document or graph-source delete — so the code and the message (which names the holder) never drift between them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error
|
CorpusBusy
|
The gate's refusal. |
required |
Returns:
| Type | Description |
|---|---|
HTTPException
|
The |
Source code in varagity/api/deps.py
447 448 449 450 451 452 453 454 455 456 457 458 459 460 | |
get_app_settings_store()
¶
Provide a per-request app-settings store, closed after the response.
Yields:
| Type | Description |
|---|---|
AppSettingsStore
|
A connected store. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
get_backup_preflight()
¶
Provide the backup reachability preflight (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[str, str], Awaitable[None]]
|
func: |
Source code in varagity/api/deps.py
434 435 436 437 438 439 440 | |
get_bm25_store()
¶
Provide a per-request BM25 store, closed after the response.
The Elasticsearch client performs no I/O at construction, so
unreachability surfaces on the operation itself — routes map those
failures to the structured 503 es_unreachable.
Yields:
| Type | Description |
|---|---|
ElasticsearchBM25
|
A configured store. |
Source code in varagity/api/deps.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
get_chat_engine_resolver()
¶
Provide the chat-engine resolver (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[str], ChatEngine]
|
func: |
Source code in varagity/api/deps.py
83 84 85 86 87 88 89 | |
get_conversation_store()
¶
Provide a per-request conversation store, closed after the response.
Yields:
| Type | Description |
|---|---|
ConversationStore
|
A connected store. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
get_conversation_store_factory()
¶
Provide a store factory for routes that connect off the event loop.
The chat route persists from a worker thread after the stream ends — a per-request dependency handle would pin the connection open for the stream's whole lifetime instead of the write's.
Returns:
| Type | Description |
|---|---|
Callable[[], ConversationStore]
|
A zero-argument :class: |
Source code in varagity/api/deps.py
171 172 173 174 175 176 177 178 179 180 181 | |
get_graph_build_preflight()
¶
Provide the graph-build reachability preflight (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[], Awaitable[None]]
|
func: |
Source code in varagity/api/deps.py
384 385 386 387 388 389 390 | |
get_graph_chat_preflight()
¶
Provide the graph-chat reachability preflight (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[], Awaitable[None]]
|
func: |
Source code in varagity/api/deps.py
325 326 327 328 329 330 331 | |
get_ingest_preflight()
¶
Provide the ingest reachability preflight (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[], Awaitable[None]]
|
func: |
Source code in varagity/api/deps.py
355 356 357 358 359 360 361 | |
get_llm()
¶
Provide the chat LLM client (override seam for tests).
Returns:
| Type | Description |
|---|---|
LLMClient
|
The registry client for |
LLMClient
|
the config validator guarantees it's never |
LLMClient
|
so this is always an :class: |
Source code in varagity/api/deps.py
61 62 63 64 65 66 67 68 69 70 71 | |
get_retriever_resolver()
¶
Provide the retrieval-method resolver (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[str], Retriever]
|
func: |
Source code in varagity/api/deps.py
74 75 76 77 78 79 80 | |
get_services_preflight()
¶
Provide the chat reachability preflight (override seam for tests).
Returns:
| Type | Description |
|---|---|
Callable[[], Awaitable[None]]
|
func: |
Source code in varagity/api/deps.py
302 303 304 305 306 307 308 | |
get_vector_store()
¶
Provide a per-request vector store, closed after the response.
Backs the corpus routes (document list/count/delete — spec_v2 §4.2).
Yields:
| Type | Description |
|---|---|
ContextualVectorDB
|
A connected store. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
refuse_corpus_busy(gate, operations)
¶
Answer 409 corpus_busy while one of operations holds the corpus.
For routes that write outside any run (the deletes) and so conflict with only some of the gate's holders; checked before any store is touched, without claiming the corpus.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gate
|
CorpusGate
|
The corpus's write gate. |
required |
operations
|
Collection[Operation]
|
The holders that refuse the request. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 | |
refuse_corpus_restoring(gate)
¶
Answer 503 corpus_restoring while a backup restore holds the corpus.
A wait, not a fault: until the restore commits (RAG) or swaps the graph
in, the corpus is empty, half-loaded, or about to be replaced — and a
RAG restore's TRUNCATE holds both tables, so a query would hang for
the whole load instead of answering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gate
|
CorpusGate
|
The write gate of the corpus the request would read. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | |
require_backup_services(corpus, operation)
async
¶
Backup preflight: 503 if a store the operation needs is down.
Runs before POST /api/backups/{corpus}/… spawns the run, so an
outage is a clean structured error instead of an instantly failed run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The corpus being backed up ( |
required |
operation
|
str
|
The backup operation ( |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
KeyError
|
If the |
Source code in varagity/api/deps.py
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
require_chat_services()
async
¶
Chat preflight: 503 with a machine-readable code if a dependency is down.
Runs before POST /api/chat opens its stream, so an outage surfaces
as a clean structured error instead of a half-stream.
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |
require_graph_build_services()
async
¶
Graph-build preflight: 503 with a machine-readable code if a model service is down.
A graph build talks to the two GPU services and nothing else — the
engine self-stores in its working directory, so neither postgres nor
Elasticsearch is required (only the stale flag touches postgres, and
that failure is logged rather than raised). Runs before POST
/api/graph/build spawns a run that would otherwise fail hours in.
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 | |
require_graph_chat_services()
async
¶
Graph-chat preflight: 503 if a service the graph turn needs is down.
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
311 312 313 314 315 316 317 318 319 320 321 322 | |
require_ingest_services()
async
¶
Ingest preflight: 503 with a machine-readable code if a dependency is down.
Ingestion needs both stores and the embedder; the chat LLM joins only
when CONTEXTUALIZE is on (the identity path never calls it). Runs
before POST /api/ingest spawns the background run, so an outage is
a clean structured error instead of an instantly failed run.
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
require_rag_readable(gates)
¶
Dependency: refuse a documents/chunks read while a RAG restore runs.
Attached to every route that queries those tables (the document list, the page previews), so none of them parks a worker thread on the restore's table lock for the length of the load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gates
|
Annotated[dict[str, CorpusGate], Depends(get_corpus_gates)]
|
The process's per-corpus write gates. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/deps.py
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 | |
varagity.api.routes.chat
¶
POST /api/chat — the streaming question endpoint (spec_v2 §4.2, §4.3).
Reuses the pipeline, never reimplements it: the request runs
:func:~varagity.pipeline.query_flow.query_stream_flow (every stage a
tracked Prefect task, exactly like the CLI) in a worker thread, while this
module's async generator relays the flow's callback events to the client as
typed SSE frames — retrieval first (the evidence before the prose),
then reasoning/token deltas, then done after the turn persists.
corpus="graph" (spec_graphrag §4.2 — the field a router will later fill)
swaps the flow for :func:~varagity.pipeline.graph_flow.graph_query_stream_flow
and nothing else: same protocol, same event order, same persistence, with
the retrieval event carrying graph evidence instead of chunks. Whether
the graph can answer is settled in :func:prepare_chat, because ADR-017's
degrade (kill switch off, engine unavailable → answer from the chunk corpus,
requested corpus still recorded) has to be decided before any evidence is on
the wire.
Failure surfaces split by stream state: dependency outages and bad
references are caught by dependencies before the stream opens (clean
structured 503/404/422) — and so is a corpus mid-restore, which
answers 503 corpus_restoring rather than retrieve from tables that are
being replaced, or open a graph whose workdir is being swapped (ADR-019);
anything after the 200 flushed is an in-band error event. A client
disconnect cancels this generator, whose cleanup flips the bridge's abort
flag; the flow notices between tokens and closes the LLM stream, freeing
the GPU (spec_v2 §4.3 cancellation).
ChatPlan
dataclass
¶
A validated, ready-to-run chat request.
Assembled by :func:prepare_chat so every failure that can be
detected before streaming is raised while the response status can
still express it — including the graph degrade decision, which is
settled here rather than mid-stream (ADR-017's semantics say a degraded
turn answers from the chunk corpus, and by the time evidence has been
emitted it is too late to change corpus).
Attributes:
| Name | Type | Description |
|---|---|---|
payload |
ChatRequest
|
The validated request body. |
retriever |
Retriever
|
The resolved retrieval method. |
method |
str
|
Its registry name (recorded on the turn). |
top_k |
int
|
Chunks to retrieve (override or settings default). |
reranked_to |
int | None
|
|
engine |
ChatEngine
|
The resolved chat engine (override or
|
engine_name |
str
|
Its registry name (recorded on the turn, like
|
history |
list[Turn]
|
The conversation's recent turns, oldest first (empty for a fresh conversation) — the engine's condense input. |
llm |
LLMClient
|
The chat client. |
store_factory |
Callable[[], ConversationStore]
|
Conversation-store constructor (persistence runs in a worker thread with its own short-lived connection). |
corpus |
str
|
The corpus the request asked for ( |
graph_service |
GraphService | None
|
The opened graph service when a graph turn will
really run on the graph; |
graph_mode |
str
|
The engine query mode a graph turn retrieves with, resolved once here so a mid-turn settings PATCH cannot make the event and the persisted record disagree. |
Source code in varagity/api/routes/chat.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
retrieval_label
property
¶
What actually retrieved, in the corpus's own vocabulary.
Returns:
| Type | Description |
|---|---|
str
|
The graph query mode for a live graph turn, else the retrieval |
str
|
method's registry name — including for a degraded graph turn, |
str
|
which really did retrieve chunks (:class: |
str
|
|
RecordingEngine
¶
Per-request engine wrapper that records the prepare outcome.
The SSE retrieval event carries condensed_query (spec_v3
§4.7), but the flow's on_retrieved callback only delivers chunks
and the flow state only returns after the stream ends — too late. The
condense stage runs strictly before retrieval in the same worker
thread, so a delegating wrapper observes the
:class:~varagity.chat.base.PreparedQuery exactly when the retrieval
callback needs it, with no flow-signature change. Per-request by
construction: registry engines are shared singletons and must not
carry request state.
Source code in varagity/api/routes/chat.py
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
condensed_query
property
¶
The rewritten search query, or None when none was used.
Returns:
| Type | Description |
|---|---|
str | None
|
|
str | None
|
before the condense stage ran and whenever the search used the |
str | None
|
user's words verbatim. |
__init__(inner)
¶
Wrap one resolved engine for one request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inner
|
ChatEngine
|
The registry engine that does the actual preparing. |
required |
Source code in varagity/api/routes/chat.py
358 359 360 361 362 363 364 365 | |
prepare(query, *, history, llm, verbose)
¶
Delegate to the wrapped engine, keeping its outcome readable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
The user's question, verbatim. |
required |
history
|
Sequence[Turn]
|
Prior turns, oldest first. |
required |
llm
|
LLMClient | None
|
Chat client for engines that condense. |
required |
verbose
|
int
|
Validated console verbosity. |
required |
Returns:
| Type | Description |
|---|---|
PreparedQuery
|
The wrapped engine's split, unchanged. |
Source code in varagity/api/routes/chat.py
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | |
chat(plan)
async
¶
Answer one question as a typed SSE stream (spec_v2 §4.3).
Event order: retrieval (the provenance payload) → reasoning/
token deltas, interleaved with throttled stats frames while the
model server reports throughput → done (ids, full answer, usage +
per-stage latency). A failure after the stream opened emits an in-band
error event instead. Aborted turns (client disconnect) persist
nothing — the turn is persisted at done.
stats is optional in the protocol: it appears only when the model
server reports its own decode counters (llama.cpp does; see
:class:~varagity.models.llm.GenerationTimings), so clients must treat
zero stats frames as normal rather than as a stalled stream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plan
|
Annotated[ChatPlan, Depends(prepare_chat)]
|
The validated request plan. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[ServerSentEvent]
|
The framed SSE events, in protocol order. |
Source code in varagity/api/routes/chat.py
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 | |
graph_evidence(retrieval)
¶
Build the persisted messages.graph_evidence snapshot.
The entities and relations, plus the mode that found them — the same
shape the live event carries, minus the transcripts, which persist as
message_sources rows instead (stage-2 decision #12).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
retrieval
|
GraphRetrieval
|
What the graph returned. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The JSONB-ready snapshot. |
Source code in varagity/api/routes/chat.py
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | |
graph_retrieval_payload(retrieval)
¶
Map a graph retrieval onto the SSE retrieval event's graph half.
Two things are dropped on the way out, both deliberately:
:attr:~varagity.graph.records.GraphEvidence.raw (the engine-native
payload, kept for autopsies, not for browsers) and everything past
:data:_EXCERPT_MAX_CHARS of each passage — a transcript day can run to
thousands of characters and the panel shows a card, not a file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
retrieval
|
GraphRetrieval
|
What the graph returned. |
required |
Returns:
| Type | Description |
|---|---|
GraphRetrievalPayload
|
The wire payload. |
Source code in varagity/api/routes/chat.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | |
graph_snapshots(retrieval)
¶
Build the persisted evidence rows for the cited transcript days.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
retrieval
|
GraphRetrieval
|
What the graph returned. |
required |
Returns:
| Type | Description |
|---|---|
list[GraphSourceSnapshot]
|
One snapshot per cited transcript day, best first. |
Source code in varagity/api/routes/chat.py
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
prepare_chat(payload, llm, resolve_retriever, resolve_engine, store_factory, services_preflight, graph_service, graph_preflight, gates)
async
¶
Validate the request and resolve everything the stream will need.
Checks run cheapest-first: body shape (FastAPI, 422 — an unknown
corpus never gets past it) → retrieval-method resolution (422) →
chat-engine resolution (override or settings, 422) → a graph turn while
the graph is mid-restore (503, before the session is opened — a lazy
open could otherwise wait out the swap, or read a graph about to be
replaced) → the graph degrade decision, which picks which corpus
answers and so which dependencies the turn even needs → that corpus
mid-restore (503) → dependency reachability (503, before the stream
opens) → conversation existence (404), whose store round-trip doubles as
the history load for the chat engine (bounded by
CONDENSE_HISTORY_TURNS).
A graph turn resolves the chunk retriever too: it is cheap, and it is what answers the turn if the graph degrades.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
ChatRequest
|
The request body (FastAPI shares the parse with the route). |
required |
llm
|
Annotated[LLMClient, Depends(get_llm)]
|
The chat client provider. |
required |
resolve_retriever
|
Annotated[Callable[[str], Retriever], Depends(get_retriever_resolver)]
|
The retrieval-method resolver. |
required |
resolve_engine
|
Annotated[Callable[[str], ChatEngine], Depends(get_chat_engine_resolver)]
|
The chat-engine resolver. |
required |
store_factory
|
Annotated[Callable[[], ConversationStore], Depends(get_conversation_store_factory)]
|
The conversation-store factory. |
required |
services_preflight
|
Annotated[Callable[[], Awaitable[None]], Depends(get_services_preflight)]
|
The awaitable reachability check (raises the structured 503). |
required |
graph_service
|
Annotated[GraphService, Depends(get_graph_service)]
|
The process-wide graph service. |
required |
graph_preflight
|
Annotated[Callable[[], Awaitable[None]], Depends(get_graph_chat_preflight)]
|
The graph turn's reachability check (Elasticsearch is not on that path). |
required |
gates
|
Annotated[dict[str, CorpusGate], Depends(get_corpus_gates)]
|
The process's per-corpus write gates (a restore holds its corpus's gate for the whole run). |
required |
Returns:
| Type | Description |
|---|---|
ChatPlan
|
The assembled plan. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/chat.py
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 | |
varagity.api.routes.conversations
¶
Conversation CRUD routes (spec_v2 §4.2, §4.4).
Thin adapters over :class:~varagity.stores.conversation_store
.ConversationStore — the endpoints are synchronous on purpose (FastAPI
runs them in its threadpool), matching the sync psycopg store underneath.
create_conversation(payload, store)
¶
Start a conversation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
ConversationCreateRequest
|
Optional explicit title (the first chat turn auto-titles otherwise). |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
ConversationSummaryOut
|
The created conversation's summary. |
Source code in varagity/api/routes/conversations.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | |
delete_conversation(conversation_id, store)
¶
Delete a conversation; messages and sources cascade.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
str
|
The conversation to delete. |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
Response
|
An empty |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/conversations.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
get_conversation(conversation_id, store)
¶
Fetch a full transcript: messages plus each answer's stored sources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
str
|
The conversation to fetch. |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
ConversationDetailOut
|
The transcript, oldest message first. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/conversations.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
list_conversations(store)
¶
List every conversation, most recently updated first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
list[ConversationSummaryOut]
|
Summaries with message counts. |
Source code in varagity/api/routes/conversations.py
45 46 47 48 49 50 51 52 53 54 55 56 57 | |
update_conversation(conversation_id, payload, store)
¶
Move a conversation into a sidebar group, or out of one.
Deliberately not a re-order: the move never bumps updated_at, so
the recency-sorted list keeps its order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conversation_id
|
str
|
The conversation to move. |
required |
payload
|
ConversationUpdateRequest
|
The target group ( |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
Response
|
An empty |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/conversations.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
varagity.api.routes.groups
¶
Conversation-group CRUD routes.
Sidebar groups are user-created folders over the conversation list. The
endpoints mirror :mod:varagity.api.routes.conversations — synchronous
thin adapters (FastAPI runs them in its threadpool) over the same
per-request :class:~varagity.stores.conversation_store.ConversationStore.
There is no rename: like conversations, a group's identity is its id, and
the GUI offers create/delete only.
create_group(payload, store)
¶
Create a conversation group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
GroupCreateRequest
|
The new group's display name. |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
GroupOut
|
The created group. |
Source code in varagity/api/routes/groups.py
58 59 60 61 62 63 64 65 66 67 68 69 | |
delete_group(group_id, store)
¶
Delete a group; its conversations survive, ungrouped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_id
|
str
|
The group to delete. |
required |
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
Response
|
An empty |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/groups.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
group_not_found(group_id)
¶
Build the structured 404 for an unknown group.
Shared with the conversations router, whose move endpoint validates its target group against the same vocabulary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_id
|
str
|
The id that failed to resolve. |
required |
Returns:
| Type | Description |
|---|---|
HTTPException
|
The exception carrying the |
Source code in varagity/api/routes/groups.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
list_groups(store)
¶
List every conversation group, name order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
StoreDep
|
The per-request conversation store. |
required |
Returns:
| Type | Description |
|---|---|
list[GroupOut]
|
All groups, including empty ones. |
Source code in varagity/api/routes/groups.py
45 46 47 48 49 50 51 52 53 54 55 | |
varagity.api.routes.system
¶
System routes: liveness/dependency health and static capabilities.
GET /api/health reports per-service reachability; GET /api/config
exposes the registries and valid ranges the GUI builds its controls from
(spec_v2 §4.2) — a newly registered retriever/chunker/parser appears here,
and therefore in the UI, with zero API edits.
config()
¶
Expose the registered capabilities and valid ranges.
Mostly static registry contents; the two upload constraints
(upload_max_mb, allowed_extensions) are read from the effective
settings per request so the dropzone always validates against what the
server enforces (ALLOWED_EXTENSIONS is runtime-overridable).
Returns:
| Type | Description |
|---|---|
ConfigResponse
|
Registry contents (retrievers, chunkers, OCR engines, model types), |
ConfigResponse
|
the numeric knobs' valid ranges, and the upload constraints. |
Source code in varagity/api/routes/system.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
health()
async
¶
Report API liveness and per-dependency reachability.
Always 200 while the API process is alive — the compose healthcheck
gates on the process, and per-dependency state lives in the body so a
flapping backing service doesn't flap the api container.
Returns:
| Type | Description |
|---|---|
HealthResponse
|
The aggregate flag and each service's probe outcome. |
Source code in varagity/api/routes/system.py
44 45 46 47 48 49 50 51 52 53 54 55 56 | |
varagity.api.routes.metrics
¶
GET /metrics — the Prometheus scrape target (spec_v2 §6).
Serves the process-wide default registry (the spec_v2 §6.2 catalog in
:mod:varagity.observability.metrics) in the Prometheus text exposition
format. A plain route rather than a mounted make_asgi_app(): mounting
307-redirects the bare /metrics path (Starlette mount semantics), and
generate_latest over the default registry is exactly what that ASGI
app serves anyway — one process, one registry, single uvicorn worker
(plan decision #11, no multiprocess registry).
settings.METRICS_ENABLED gates inclusion of this router at app build
time (:func:varagity.api.main.create_app), so disabling metrics turns
the endpoint into a structured 404.
metrics()
¶
Expose the metric catalog for a Prometheus scrape.
Returns:
| Type | Description |
|---|---|
Response
|
The default registry rendered in the Prometheus text format |
Response
|
( |
Source code in varagity/api/routes/metrics.py
22 23 24 25 26 27 28 29 30 | |
varagity.api.routes.settings
¶
GET/PATCH /api/settings — the live settings surface (spec_v2 §4.7).
GET returns the full overridable catalog (effective values, groups,
override/reingest flags, registry-derived choices) plus both staleness
flags — the chunk corpus' and the graph's;
PATCH merges new overrides over the active set, validates the
whole through the config validators, persists to app_settings, clears
the settings cache (via :mod:varagity.api.runtime_settings), and flags
the corpus stale when a reingest-affecting knob actually changed on a
non-empty corpus. Query-time knobs take effect on the next question;
ingest-time knobs surface the v1 "hashes don't change" footgun as the
"Re-ingest to apply" affordance instead of a silent no-op.
patch_settings(payload, store, vector_store, gates)
¶
Apply, validate, and persist runtime setting overrides.
A None value clears that override (reverting to the env value).
Validation runs on the merged whole through every config validator,
so linked constraints (the fusion weight pair, the rerank bounds) hold
exactly as they do for .env — an invalid patch changes nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
SettingsPatchRequest
|
The overrides to set or clear. |
required |
store
|
SettingsStoreDep
|
The per-request app-settings store (persistence + flag). |
required |
vector_store
|
VectorStoreDep
|
The vector store (the corpus-emptiness check). |
required |
gates
|
GatesDep
|
The process's per-corpus write gates — during a backup restore the emptiness check is skipped (see below). |
required |
Returns:
| Type | Description |
|---|---|
SettingsResponse
|
The full post-patch catalog; |
SettingsResponse
|
reingest is now needed. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/settings.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
read_settings(store)
¶
Report the effective settings and the corpus-stale flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
SettingsStoreDep
|
The per-request app-settings store. |
required |
Returns:
| Type | Description |
|---|---|
SettingsResponse
|
The overridable catalog with effective (env + override) values. |
Source code in varagity/api/routes/settings.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
varagity.api.routes.documents
¶
Corpus document routes: upload, list, delete (spec_v2 §4.2, spec_v3 §5).
POST /api/documents writes multipart uploads into DOCS_PATH
(validated against ALLOWED_EXTENSIONS + UPLOAD_MAX_MB, no
auto-ingest — ingestion is its own explicit action). v3 adds optional
per-file relative paths (folder uploads): structure is identity, not
decoration — doc_id hashes the path relative to DOCS_PATH, so
q3/notes.md and q4/notes.md must land at distinct paths or the
second silently replaces the first. The optional per-file modified
entries (epoch milliseconds, the browser's File.lastModified) restamp
each stored file's mtime, so the file_modified_at chunk provenance
reflects the document's clock instead of the upload's. GET lists the documents table
with each document's extraction mix and its path relative to DOCS_PATH
(the GUI's folder-grouping key). DELETE removes a document's chunks
from both stores — the v1 gap ("removing a file does not remove its
chunks") turned into GUI-driven GC — Elasticsearch first, pgvector last,
mirroring the reingest ordering rationale (the documents row is the
idempotency marker, so it must go last); with ?remove_file=true the
source file is also unlinked when it lives inside DOCS_PATH, so the
next ingest can't resurrect the document. POST /api/documents/delete
is the same operation over a set (the corpus table's multi-select): the
same ordering applied set-wise, in one round trip per store instead of one
per document. Both deletes answer 409 corpus_busy while a backup export
or restore holds the corpus (ADR-019) — a restore is replacing the very
rows a delete would remove — and every route that reads the tables (the
list, the previews) answers 503 corpus_restoring during a restore
rather than wait out its TRUNCATE for the length of the load.
The …/preview/locate + …/preview/page/{page} pair (ADR-010) backs
the evidence panel's page preview: locate scores the source document's
pages for a chunk's text and returns highlight rects; the page route
serves the rendered PNG. Both degrade per-document (available:false /
a 404 with the reason as its code) rather than 500 — the GUI falls back
to the full-text view.
delete_document(doc_id, store, bm25, gates, remove_file=False)
¶
Remove a document's chunks from both stores (GUI-driven GC).
Elasticsearch first, pgvector last: the pgvector documents row is
the marker — if the ES delete fails, the document still lists and the
delete can be retried; a marker deleted first would strand ES chunks
invisibly (the exact v1 gap this route closes).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_id
|
str
|
The document to remove. |
required |
store
|
VectorStoreDep
|
The per-request vector store. |
required |
bm25
|
BM25StoreDep
|
The per-request BM25 store. |
required |
gates
|
GatesDep
|
The process's per-corpus write gates. |
required |
remove_file
|
bool
|
Also unlink the source file — honored only when it
resolves inside |
False
|
Returns:
| Type | Description |
|---|---|
DocumentDeleteResponse
|
The deletion counts and whether the file was removed. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 | |
delete_documents(payload, store, bm25, gates, remove_file=False)
¶
Remove several documents' chunks from both stores (bulk GUI-driven GC).
The corpus table's multi-select delete (spec_v2 §4.2). A POST
sub-path rather than a body on DELETE — the set to delete is the
request's subject, and bodies on DELETE are the kind of thing
intermediaries feel free to drop.
Same store ordering as the single-document delete, applied set-wise:
one terms delete_by_query against Elasticsearch first, one
DELETE … WHERE doc_id = ANY(…) against pgvector last. So the
invariant holds for the batch exactly as it does for one document — if
Elasticsearch fails, no marker row is gone, every document still lists,
and the whole batch is retryable. Unknown ids are reported in
not_found rather than raising: a concurrent delete must not fail
the rest of the batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
DocumentBulkDeleteRequest
|
The requested |
required |
store
|
VectorStoreDep
|
The per-request vector store. |
required |
bm25
|
BM25StoreDep
|
The per-request BM25 store. |
required |
gates
|
GatesDep
|
The process's per-corpus write gates. |
required |
remove_file
|
bool
|
Also unlink each source file — honored per document,
only inside |
False
|
Returns:
| Type | Description |
|---|---|
DocumentBulkDeleteResponse
|
Per-document outcomes plus the ids that had no |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 | |
list_documents(store)
¶
List every ingested document (the corpus-management table).
Files uploaded but not yet ingested don't appear here — the documents
table records ingests; the GUI pairs this list with the upload outcomes
it already holds. Each entry carries its relative_path under
DOCS_PATH (None for sources living elsewhere) so the GUI can
group the table by the folders a folder upload created.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
VectorStoreDep
|
The per-request vector store. |
required |
Returns:
| Type | Description |
|---|---|
list[DocumentOut]
|
One entry per document, newest ingest first. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 | |
locate_preview(doc_id, payload, store)
¶
Find the source page containing a chunk's text, with highlight rects.
The evidence panel's locate step (ADR-010): resolve the document to an
openable PDF (containment + content-hash verified; PPTX via the cached
conversion), score its pages by word coverage, and return the best page
plus normalized highlight rectangles. Every degradable condition — kill
switch, unsupported format, missing/edited file, unavailable converter,
no textual match — answers 200 available:false with its reason; the
GUI falls back to the full-text view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_id
|
str
|
The chunk's parent document. |
required |
payload
|
PreviewLocateRequest
|
The chunk text to locate. |
required |
store
|
VectorStoreDep
|
The per-request vector store. |
required |
Returns:
| Type | Description |
|---|---|
PreviewLocateResponse
|
The located page, rects, and coverage — or the degrade reason. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 | |
preview_page(doc_id, page, store)
¶
Serve one rendered page of a source document as a PNG.
The image half of the preview pair (ADR-010), immutable-cacheable:
doc_id is content-hashed and the resolution step re-verifies the
on-disk content_hash before rendering, so a drifted file 404s
(file_changed) rather than serving a lying image. Degradable
reasons are 404 codes here — an <img> can't read a JSON envelope,
and the client only requests pages a successful locate named.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_id
|
str
|
The document to render. |
required |
page
|
int
|
1-based page number (a locate response's |
required |
store
|
VectorStoreDep
|
The per-request vector store. |
required |
Returns:
| Type | Description |
|---|---|
Response
|
The PNG at |
Response
|
|
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 | |
upload_documents(files, paths=None, modified=None)
¶
Upload file(s) into DOCS_PATH (no auto-ingest).
Each file is validated and stored independently; rejected files are reported per-file so a mixed batch partially succeeds. A same-named existing file is replaced (the re-upload flow) — its changed content hash makes the next ingest re-process it.
With paths (folder uploads, spec_v3 §5.2), each file lands at its
sanitized relative path under DOCS_PATH, preserving nested
structure — structure is identity: doc_id hashes the relative
path, so q3/notes.md and q4/notes.md must not collapse onto one
name. Entries pair with files positionally; an empty-string entry
keeps that file on the flat path. Without paths the flat contract
is untouched.
With modified (same positional contract), each stored file's mtime
is restamped to the client's declared last-modified time (epoch
milliseconds — the browser's File.lastModified), so the
file_modified_at provenance on the next ingest's chunks carries the
document's clock rather than the upload instant. An empty-string entry
keeps that file's write time; a malformed entry is ignored per file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
list[UploadFile]
|
The multipart file parts. |
required |
paths
|
Annotated[list[str] | None, Form()]
|
Optional relative path per file, positionally aligned with
|
None
|
modified
|
Annotated[list[str] | None, Form()]
|
Optional last-modified epoch-milliseconds per file,
positionally aligned with |
None
|
Returns:
| Type | Description |
|---|---|
UploadResponse
|
Per-file outcomes, in upload order. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/documents.py
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 | |
varagity.api.routes.ingest
¶
POST /api/ingest + GET /api/ingest/status (spec_v2 §4.2).
Triggering returns a run handle immediately (the flow runs on a background
thread — see :mod:varagity.api.ingest_runner); the status endpoint is an
SSE stream of the run's events — a status snapshot, per-stage/per-file
progress frames (with per-chunk contextualize ticks), relayed log
lines, and the terminal status with the summary counters. The stream
replays from the start, so connecting mid-run or after completion renders
the same picture the CLI's rich display showed live.
ingest_status(runner)
async
¶
Stream the current (or last) ingest run's progress as SSE.
Frames: status (snapshot; also terminal, with the summary),
progress (per stage/file/chunk), log (relayed pipeline lines).
With no run in this API process the stream is a single idle status
frame; after a terminal run it replays that run and closes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
RunnerDep
|
The process-wide ingest runner. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[ServerSentEvent]
|
The framed events, oldest first, then live until terminal. |
Source code in varagity/api/routes/ingest.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
start_ingest(payload, runner, preflight)
async
¶
Trigger a background ingest run and return its handle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
IngestStartRequest
|
|
required |
runner
|
RunnerDep
|
The process-wide ingest runner. |
required |
preflight
|
Annotated[Callable[[], Awaitable[None]], Depends(get_ingest_preflight)]
|
The awaitable reachability check (structured 503). |
required |
Returns:
| Type | Description |
|---|---|
IngestRunOut
|
The new run's snapshot (state |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/ingest.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
varagity.api.routes.graph
¶
Graph routes: corpus upload/list/delete, build, status, export (§5.2, §4.4).
The Graph RAG tab's whole server surface, and the deliberate mirror of the
chunk-RAG corpus routes (:mod:varagity.api.routes.documents +
:mod:varagity.api.routes.ingest) with the differences the subject matter
forces:
- Uploads are sniffed, not allow-listed.
ALLOWED_EXTENSIONSgoverns the chunk-RAG corpus and says nothing here (.dbisn't even in it); what decides is whether a registered :class:~varagity.graph.sources.base.MessageSourceclaims the stored bytes — theimessageprobe checks the SQLite magic header and the presence of amessagetable (behind a cheap.dbsuffix gate of its own). A file that lands and then fails the sniff is removed again and reported asunsupported_graph_source. The cap is its own setting (GRAPH_UPLOAD_MAX_MB, default 4 GB): a decade of iMessage history is one enormous file, where the corpus cap is document-sized. WAL sidecars are first-class: a livechat.dbkeeps recent messages inchat.db-wal, so copying all three files (.db,-wal,-shm) is the documented rule — the sidecars are stored beside their database and never sniffed on their own. - The listing is a directory scan, not a table read. The graph's own record of what it indexed lives in the engine's workdir manifest; what this tab has to show is what is on disk, including files the user dropped in that no source claims. Parse summaries come from the last build's scan (parsing a multi-gigabyte database is a build's job, not a GET's).
- Deleting a source file flags the graph stale. The graph still holds
that file's messages — only a
reingestrebuild retracts them — so the flag is set and the tab says so (stage-2 decision #16). A delete is refused (409 corpus_busy) while a backup restore holds the graph: the restore ends by writing the flag its bundle recorded, which would silently undo the delete's. - The reserved
varagity-exports/folder is not corpus (ADR-019): the graph's backup bundles live there, so the listing never shows them, the delete never removes one (it would flag the graph stale for a file that was never a source), and the build's scan never counts them. - Builds are API-only (stage-2 decision #4): the engine's storages are single-writer per working directory, and this process owns them. There is deliberately no CLI graph build.
- Reading the graph never builds one. The export and entity-detail routes — the graph view's data source (spec_graphrag §4.4) — skip the engine entirely when the working directory does not exist, because opening a session creates the storages and pays the tokenizer's startup. A page load must not turn "nothing indexed yet" into an initialized workdir.
GRAPH_ENABLED=false turns every entry point that acts on the graph —
upload, build, and the two read routes the view draws from — into a
structured 403 graph_disabled (ADR-017's degrade semantics: never a
silent no-op). Listing, deleting, and the status poll keep working, because
a disabled graph still has a corpus directory to manage and a tab that must
render honestly.
delete_graph_document(name, store, gates)
¶
Remove one file from the graph corpus directory and flag the graph stale.
Deleting the source does not retract what the graph already extracted from it — that costs a rebuild — so this sets the graph-stale flag and the tab surfaces it, rather than letting the graph and its corpus diverge silently (stage-2 decision #16).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The file's path relative to |
required |
store
|
SettingsStoreDep
|
The per-request app-settings store (the stale flag). |
required |
gates
|
GatesDep
|
The process's per-corpus write gates. |
required |
Returns:
| Type | Description |
|---|---|
GraphDocumentDeleteResponse
|
What was removed and whether the graph is now flagged stale. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/graph.py
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 | |
export_graph(service, label=_WHOLE_GRAPH, max_depth=3, max_nodes=MAX_EXPORT_NODES)
¶
Read a drawable slice of the graph (spec_graphrag §4.4).
The graph view's whole data source. The default is the whole graph,
degree-ordered so the cap keeps the most connected entities, and
truncated says when the cap bit — the view surfaces that rather than
implying it drew everything.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
ServiceDep
|
The process-wide graph service. |
required |
label
|
str
|
Entity name to centre the slice on; |
_WHOLE_GRAPH
|
max_depth
|
Annotated[int, Query(ge=1, le=_MAX_EXPORT_DEPTH)]
|
Hops to walk out from |
3
|
max_nodes
|
Annotated[int, Query(ge=1, le=MAX_EXPORT_NODES)]
|
Node cap, defaulting to its own ceiling
:data: |
MAX_EXPORT_NODES
|
Returns:
| Type | Description |
|---|---|
GraphExportOut
|
The slice; empty (not an error) when nothing has been indexed yet. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/graph.py
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 | |
graph_build_status(runner)
async
¶
Stream the current (or last) graph build's progress as SSE.
Frames: status (snapshot; also terminal, with the summary),
progress (scan → parse per file → bound/reset →
index → sampled process ticks), log (relayed
varagity.graph lines). The stream replays from the start, so a
browser opened six hours into a backfill renders the same picture one
opened at the start did.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
RunnerDep
|
The process-wide graph build runner. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[ServerSentEvent]
|
The framed events, oldest first, then live until terminal. |
Source code in varagity/api/routes/graph.py
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 | |
graph_entity(name, service)
¶
Inspect one entity: its summary, its neighbourhood, its source days.
The graph view's click-through (spec_graphrag §4.4): a name in the picture resolves to what the engine merged about it, the relations around it, and — the drill-down that makes the graph answerable — the transcript days it was extracted from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The entity's name (path-encoded; extracted names may contain slashes). Canonical spelling resolves directly; any other casing resolves through a whole-graph lookup, because the engine's own label match is exact. |
required |
service
|
ServiceDep
|
The process-wide graph service. |
required |
Returns:
| Type | Description |
|---|---|
GraphEntityDetailOut
|
The entity with its depth-1 relations and its source days. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/graph.py
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 | |
graph_status(runner, service, store)
¶
Report the graph's size, staleness, and build state (the tab's poll).
Reads the workdir's summary sidecar rather than the graph itself, so a
poll never walks a multi-megabyte graphml (stage-2 decision #18), and
skips the engine entirely when nothing has been built here — an
unbuilt graph must not make a status poll open a session and pay an
engine's startup. workdir_files lists the workdir (a directory
read, no file opened) by the graph export's own rule, so the backup
panel's "nothing built yet" can never disagree with the export route's
nothing_to_export.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
RunnerDep
|
The process-wide graph build runner (build state). |
required |
service
|
ServiceDep
|
The process-wide graph service (document statuses). |
required |
store
|
SettingsStoreDep
|
The per-request app-settings store (the stale flag). |
required |
Returns:
| Type | Description |
|---|---|
GraphStatusOut
|
The status snapshot; every size field is |
GraphStatusOut
|
nothing has been indexed, never a zero that would read like an |
GraphStatusOut
|
empty graph. |
Source code in varagity/api/routes/graph.py
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 | |
list_graph_documents(runner)
¶
List every file in the graph corpus directory.
A directory scan, not a table read: the graph's own record of what it
indexed lives in the engine's workdir, and what this tab must show is
what has been dropped in — sidecars and contacts files included. Files
the last build parsed also carry their doc_id and a parse summary;
files no build has scanned carry None for both, because parsing a
multi-gigabyte database is a build's job. The reserved
varagity-exports/ folder's backup bundles are never listed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
RunnerDep
|
The process-wide graph build runner (holds the last scan). |
required |
Returns:
| Type | Description |
|---|---|
list[GraphDocumentOut]
|
One entry per file, in path order. |
Source code in varagity/api/routes/graph.py
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | |
start_graph_build(payload, runner, preflight)
async
¶
Trigger a background graph build and return its handle.
This is also how a killed build resumes. The engine keeps document
statuses on disk and re-selects every in-flight or failed document, so
pressing Build again after a crash finishes the work instead of
repeating it — the run's summary reports what it inherited as
resumed. That is why reingest is a separate, explicit flag: it
is the one path that throws the working directory away.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
GraphBuildRequest
|
|
required |
runner
|
RunnerDep
|
The process-wide graph build runner. |
required |
preflight
|
Annotated[Callable[[], Awaitable[None]], Depends(get_graph_build_preflight)]
|
The awaitable reachability check (structured 503). |
required |
Returns:
| Type | Description |
|---|---|
GraphBuildRunOut
|
The new run's snapshot (state |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/graph.py
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | |
upload_graph_documents(files)
¶
Upload message-source file(s) into GRAPH_DOCS_PATH (no auto-build).
Each file is written independently and then sniffed: a file no
registered message source claims is deleted again and reported as
unsupported_graph_source, so a mixed drop partially succeeds and the
corpus directory never fills with unreadable files. The chunk-RAG
ALLOWED_EXTENSIONS list does not apply — contents decide.
Copy the whole SQLite set — chat.db and its -wal/-shm
sidecars: a live database keeps its most recent messages in the
write-ahead log, and uploading the .db alone silently loses them.
Sidecars are stored beside their database without being sniffed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
list[UploadFile]
|
The multipart file parts. |
required |
Returns:
| Type | Description |
|---|---|
GraphUploadResponse
|
Per-file outcomes, in upload order. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/graph.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
varagity.api.routes.backups
¶
Backup routes: list, export, restore, status (SSE), download (ADR-019).
The /corpus "Export & restore" panel's whole server surface, mirroring
the ingest routes' shape — a 202 start, a status stream that replays the
backlog to late joiners, the structured error envelope:
GET /api/backups/{corpus}lists the bundles in the corpus's reservedvaragity-exports/folder, newest first, each with itsbundle.json(read alone through the zip's central directory) and the cheap restore verdict —restorable, thereasonwhen not, and the ingest-settingdriftthat restoring it would flag stale. A file copied in by hand is listed too; an in-flight.partialnever is.POST /api/backups/{corpus}/exportstarts an export run. Everything that can refuse it is checked first, so a refusal is a clean status, never a failed run: postgres unreachable (503), nothing ingested — or, for the graph, nothing ever built (422 nothing_to_export) — the folder unwritable (500), another operation holding the corpus (409 corpus_busy: an ingest or build, another export, a restore).POST /api/backups/{corpus}/restorestarts a restore run, with the same up-front posture for everything a cheap look can see: the stores it writes unreachable (503), no such bundle (404 bundle_not_found), the corpus busy here (409), the bundle unsound — or already found damaged by an earlier restore — (422 bundle_invalid) or not this stack's to restore (422 bundle_incompatible— another embedding space, or for the graph another engine or engine version), and for RAG an ingest in another process holding the corpus lock (409 corpus_busy). The sha256 of every member is checked by the run itself, as it loads the bundle — one read, with progress, and before anything takes effect.GET /api/backups/{corpus}/statusstreams the run'sstatus/progress/logevents.GET /api/backups/{corpus}/files/{name}returns a bundle as an attachment — a plain navigation, so the browser downloads it with no CORS involved — resolvingnameinside the folder or answering404 bundle_not_found.
Why neither the list nor the route hashes. A sha256 pass reads the whole bundle: the list is loaded on every panel visit, and a restore reads the bundle anyway to load it. So both show the cheap verdict, and the restore run — which verifies every member as it loads it, before its transaction commits or its staged graph is swapped in — remembers a bundle it refused, by name, size and mtime, in this process: from then on the list shows that bundle as damaged and the route refuses it, instead of offering it again, until the file changes.
Why "save to the folder" rather than "open the folder". No browser API
can open a folder on the user's machine, and the API runs in a container
that can't reach the desktop; the bundle lands where the ticket wants it
(beside the sources, inside the bind mount that survives down -v), and
the panel shows that path with Copy-path and Download.
corpus is rag or graph (ChatRequest.corpus's vocabulary) on
every route. While GRAPH_ENABLED=false, every graph route answers 403
graph_disabled — except the status stream, which (like the graph build's)
keeps reporting, so the tab can still render whatever ran.
BackupCorpus = Literal['rag', 'graph']
module-attribute
¶
The corpora with backups — ChatRequest.corpus's vocabulary.
backup_status(corpus, runners)
async
¶
Stream the corpus's current (or last) backup run as SSE.
Frames: status (snapshot; also terminal, with the summary — and an
export's saved path), progress (a RAG export's snapshot →
documents → chunks → finalize; a graph export's snapshot
→ pack, in bytes → finalize; a RAG restore's validate →
load → index → bm25; a graph restore's validate →
extract, in bytes → swap → open), log (relayed
varagity.backup lines).
With no run in this API process the stream is a single idle status
frame; after a terminal run it replays that run and closes. Never
refused for a disabled graph: a tab must still render what ran.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
BackupCorpus
|
The corpus whose backup run to follow. |
required |
runners
|
RunnersDep
|
The process-wide backup runners. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[ServerSentEvent]
|
The framed events, oldest first, then live until terminal. |
Source code in varagity/api/routes/backups.py
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 | |
download_backup(corpus, name)
¶
Return one bundle as a file download.
A plain GET answering with Content-Disposition: attachment, so
the panel's Download is an ordinary link — the browser saves the file
(and offers its own "Show in folder") without a cross-origin fetch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
EnabledCorpusDep
|
The corpus the bundle belongs to. |
required |
name
|
str
|
The bundle's file name in the exports folder. |
required |
Returns:
| Type | Description |
|---|---|
FileResponse
|
The bundle's bytes as |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/backups.py
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 | |
enabled_corpus(corpus)
¶
Resolve the {corpus} path parameter, refusing the graph while it is switched off.
A dependency rather than a line in each route body, so it runs first
— before the routes' store dependencies open a connection — and a
disabled graph answers 403 whatever else is down (ADR-017's degrade
semantics: a refusal, never a silent no-op).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
BackupCorpus
|
The path's corpus. |
required |
Returns:
| Type | Description |
|---|---|
BackupCorpus
|
The same corpus. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/backups.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
list_backups(corpus, damaged)
¶
List the backup bundles in a corpus's varagity-exports/ folder.
A directory read plus one small member per bundle — the manifest is found through each zip's central directory, so a folder of large bundles lists in milliseconds. Each entry carries the cheap restore verdict (no hashing — a restore hashes, and a damage it finds is remembered here).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
EnabledCorpusDep
|
The corpus whose backups to list. |
required |
damaged
|
DamagedDep
|
The process's remembered restore failures. |
required |
Returns:
| Type | Description |
|---|---|
list[BundleOut]
|
One entry per bundle file, newest first (empty when the folder does |
list[BundleOut]
|
not exist yet). |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/backups.py
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | |
start_export(corpus, runners, preflight, store)
async
¶
Start a background export and return its handle.
The bundle is written into <corpus root>/varagity-exports/ as
varagity-<corpus>-<UTC yyyymmdd-HHMM>.zip (a same-minute repeat gets
a -2 suffix — never a replace): for RAG, from one consistent
snapshot of the corpus; for the graph, the engine's workdir copied under
the graph's write lock, while graph chat keeps answering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
EnabledCorpusDep
|
The corpus to export. |
required |
runners
|
RunnersDep
|
The process-wide backup runners. |
required |
preflight
|
PreflightDep
|
The awaitable reachability check (structured 503). |
required |
store
|
VectorStoreDep
|
The per-request vector store (the RAG nothing-to-export check). |
required |
Returns:
| Type | Description |
|---|---|
BackupRunOut
|
The new run's snapshot (state |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/backups.py
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | |
start_restore(corpus, payload, runners, preflight, store, damaged)
async
¶
Start a background restore of one bundle and return its handle.
Restore replaces, with no model call. For RAG, the corpus's rows in
both stores become the bundle's, re-rooted onto this stack's
DOCS_PATH — nothing parsed, chunked, contextualized or embedded —
and the BM25 index is rebuilt from the restored rows. For the graph, the
engine's working directory becomes the bundle's — extracted beside the
live one and swapped in, the previous graph put back if the restored one
won't open — and nothing is re-extracted. Everything a cheap look can
see refuses the bundle here, before the 202: the list's verdict, and
a damage an earlier restore of this very file found. The sha256 of every
member is the run's job — it reads the bundle once, to load it, and
nothing takes effect until every member checked out — so a bundle
damaged inside an intact zip fails its first run (the corpus untouched)
and is refused here from then on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
EnabledCorpusDep
|
The corpus to restore. |
required |
payload
|
BackupRestoreRequest
|
The bundle to restore, by name. |
required |
runners
|
RunnersDep
|
The process-wide backup runners. |
required |
preflight
|
PreflightDep
|
The awaitable reachability check (structured 503). |
required |
store
|
VectorStoreDep
|
The per-request vector store (the RAG corpus-lock check). |
required |
damaged
|
DamagedDep
|
The process's remembered restore failures (a bundle an earlier restore found damaged is refused here, before the run). |
required |
Returns:
| Type | Description |
|---|---|
BackupRunOut
|
The new run's snapshot (state |
Raises:
| Type | Description |
|---|---|
HTTPException
|
|
Source code in varagity/api/routes/backups.py
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 | |
varagity.api.runtime_settings
¶
Runtime settings override layer (spec_v2 §4.7, plan decision #9).
The GUI tunes the pipeline without editing .env: overrides persist in
the app_settings table (migration 002, via
:class:~varagity.stores.app_settings_store.AppSettingsStore) and are
merged over the env-loaded :class:~varagity.config.Settings with the
exact mechanism the eval harness proved
(pinned_eval_settings): export the values as environment variables —
which pydantic-settings gives precedence over .env — then
get_settings.cache_clear(). Every module that reads get_settings()
(the convention) sees the override on its next call, so query-time knobs
take effect on the next question with zero pipeline edits.
This module owns the process-env side: the :data:OVERRIDABLE catalog
(which settings the GUI may override, grouped per spec §4.7, flagged when
reingest-affecting), atomic apply-with-validation (an invalid merge rolls
the environment back and raises, so a bad PATCH can never leave the
process half-configured), and the startup replay of persisted rows.
Persistence and the corpus-stale flag live in the store; the route
(:mod:varagity.api.routes.settings) orchestrates both.
Single-worker caveat (plan decision #11): the environment is process-global
state, which is exactly why one uvicorn worker serves the API. A PATCH
racing an in-flight request can hand that request the old or new values —
acceptable for the single-user posture, guarded by :data:_lock against
torn writes.
REINGEST_AFFECTING = frozenset(name for name, spec in (OVERRIDABLE.items()) if spec.reingest_affecting)
module-attribute
¶
The settings whose change marks the corpus stale (spec §4.7).
OverridableSetting
dataclass
¶
Catalog entry for one GUI-overridable setting.
Attributes:
| Name | Type | Description |
|---|---|---|
group |
str
|
The spec §4.7 drawer group ( |
reingest_affecting |
bool
|
Whether changing it does not change content
hashes (the v1 footgun) — the corpus goes stale until
|
choices |
Callable[[], list[str]] | None
|
Lazy provider of the valid values for enum-like settings
(lazy so registries resolve at request time, reflecting any
newly registered implementation); |
Source code in varagity/api/runtime_settings.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
active_overrides()
¶
Snapshot the overrides currently applied to the process environment.
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Setting name → env-string value (a copy). |
Source code in varagity/api/runtime_settings.py
216 217 218 219 220 221 222 223 | |
apply_overrides(overrides)
¶
Atomically make overrides the complete active override set.
The merged environment is validated by constructing
:class:~varagity.config.Settings (every config validator runs — the
weight pair, the rerank constraints, the vocabularies); a failure rolls
the environment back to the previous override set and re-raises, so the
process never serves half-applied settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
overrides
|
Mapping[str, str]
|
Setting name → env-string value; must be the entire
desired set (the route merges patches over
:func: |
required |
Returns:
| Type | Description |
|---|---|
Settings
|
The now-effective settings. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a key is not overridable. |
ValidationError
|
If the merged settings are invalid (the previous overrides remain in effect). |
Source code in varagity/api/runtime_settings.py
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | |
load_persisted_overrides(load)
¶
Replay persisted overrides into the environment (API startup).
Runs after the migration runner in the lifespan, so overrides survive an
api restart. Rows that no longer validate (e.g. a chunker override
from a removed registry entry) or are no longer overridable are dropped
from the applied set with an error log — the API must boot on env
defaults rather than crash on a stale row; the row itself stays put for
inspection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
load
|
Callable[[], dict[str, str]]
|
Zero-argument provider of the persisted rows (a bound
|
required |
Source code in varagity/api/runtime_settings.py
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
reset_for_tests()
¶
Restore the un-overridden environment (test isolation helper).
Clears every active override, restores the baseline env snapshot, and
clears the settings cache — the moral equivalent of the settings_env
fixture's teardown for this module's state.
Source code in varagity/api/runtime_settings.py
325 326 327 328 329 330 331 332 333 334 | |
to_env_value(value)
¶
Convert a JSON scalar to the env-string form pydantic-settings parses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bool | int | float | str
|
The override value as it arrived in the PATCH body. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The environment-variable string (bools lowercase, per the |
str
|
convention). |
Source code in varagity/api/runtime_settings.py
201 202 203 204 205 206 207 208 209 210 211 212 213 | |
varagity.api.ingest_runner
¶
Background ingest runs with a replayable live event feed (spec_v2 §4.2).
POST /api/ingest must return a run handle immediately while the corpus
ingests (minutes of contextualization on a real corpus), and GET
/api/ingest/status must stream per-stage/per-file progress that mirrors
the CLI's rich display. This module owns both halves:
- One run at a time. :class:
IngestRunnerexecutes :func:~varagity.pipeline.ingest_flow.ingest_flowon a daemon thread — the same tracked Prefect flow the CLI runs (and, since it runs in the API process, the ingest Prometheus counters finally reach the scrape). A second start while one is running raises :class:IngestAlreadyRunning(the route's structured409). - One writer per corpus. A run also claims the RAG corpus's
:class:
~varagity.api.corpus_gate.CorpusGatefor its whole life (ADR-019), so an ingest and a backup export or restore never overlap in this process: a start while a backup holds the corpus raises :class:~varagity.api.corpus_gate.CorpusBusy(409 corpus_busy). The run turns terminal and frees the corpus in one critical section, before the terminal frame: a start that sees the run finished — by its frame or by the single-flight check — always finds the corpus free. - Progress without pipeline edits. The runner wraps the flow's
@task-wrapped stages (:data:~varagity.pipeline.ingest_flow.TASK_STAGES) with event emitters — tracking and streaming compose — and passes the loader'son_fileobserver for exact per-file outcomes. The contextualize stage's per-chunk ticks ride a proxy around therichprogress handle it already advances.varagity.ingestlog records (skips, failures, no-text warnings) are relayed aslogevents, so the browser sees what the terminal would. - Replay + live fan-out. Events accumulate per run; a subscriber gets
the full backlog first (connecting mid-run or after completion both
render correctly), then live events until the terminal
statusframe.
Feed = tuple[str, BaseModel]
module-attribute
¶
One feed item: (sse_event_name, payload_model).
IngestAlreadyRunning
¶
Bases: RuntimeError
Raised by :meth:IngestRunner.start while a run is in flight.
Source code in varagity/api/ingest_runner.py
80 81 | |
IngestRunner
¶
Owner of the (single) background ingest run and its event feed.
Source code in varagity/api/ingest_runner.py
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 | |
__init__(flow=None, *, base_stages=None, on_reingest_complete=None, gate=None)
¶
Wire the runner's collaborators (all injectable for tests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flow
|
Callable[..., IngestSummary] | None
|
The ingest callable, |
None
|
base_stages
|
IngestStages | None
|
The stage bundle to wrap with emitters; the flow's Prefect task stages when omitted. |
None
|
on_reingest_complete
|
Callable[[], None] | None
|
Hook fired after a |
None
|
gate
|
CorpusGate | None
|
The RAG corpus's write gate; the process-wide one when omitted. |
None
|
Source code in varagity/api/ingest_runner.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | |
snapshot()
¶
Report the current (or last) run without subscribing.
Returns:
| Type | Description |
|---|---|
IngestRunOut | None
|
The run snapshot, or |
Source code in varagity/api/ingest_runner.py
330 331 332 333 334 335 336 337 | |
start(*, reingest)
¶
Start a background ingest run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reingest
|
bool
|
Delete each discovered document's previous ingest and re-process it (the stale-corpus action). |
required |
Returns:
| Type | Description |
|---|---|
IngestRunOut
|
The new run's handle/state snapshot. |
Raises:
| Type | Description |
|---|---|
IngestAlreadyRunning
|
If a run is already in flight. |
CorpusBusy
|
If a backup export or restore holds the corpus. |
Source code in varagity/api/ingest_runner.py
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | |
subscribe()
async
¶
Stream this runner's events: full backlog first, then live.
The stream ends after the terminal status frame (or immediately
after an idle/terminal snapshot when nothing is running), so a
client connecting at any point renders the same picture.
Yields:
| Type | Description |
|---|---|
AsyncIterator[Feed]
|
|
Source code in varagity/api/ingest_runner.py
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | |
get_ingest_runner()
¶
Provide the process-wide runner (FastAPI dependency, override seam).
Returns:
| Type | Description |
|---|---|
IngestRunner
|
The lazily created singleton. |
Source code in varagity/api/ingest_runner.py
613 614 615 616 617 618 619 620 621 622 | |
varagity.api.graph_runner
¶
Background graph builds with a replayable live event feed (spec_graphrag §5.2).
POST /api/graph/build must return a run handle immediately: a real
archive indexes at ADR-017's measured 7.17 s/message, so a full backfill is
hours to days of extraction on the single llama.cpp slot. This module is
:mod:varagity.api.ingest_runner's shape applied to that job — one run at a
time on a daemon thread, an event feed with full backlog replay, log records
relayed from the varagity.graph namespace — with the three differences a
multi-day job forces:
- Resume is the normal case, not an error path. The engine enqueues
documents into a durable status store and re-selects every in-flight or
failed one at the top of each batch, so a killed build is finished by
starting another (the runner reports what it inherited as
resumed). Nothing here needs to checkpoint; the discipline is simply not to wipe unless asked. - Progress comes from two clocks. Scanning and parsing the corpus
directory takes seconds and emits per-file frames; the engine's own
extraction takes hours and is sampled — :data:
_SAMPLE_INTERVAL_Sapart — from its document-status counts, on a second thread, while the build call is still blocked inside the flow. - Bounds are applied before the engine sees anything.
message_limitandsincenarrow the merged message stream so a spot check costs minutes; a bounded run then tells the session not to prune, because its render is partial by construction and pruning on its say-so would delete the rest of the archive (stage-2 decision #9).
The build itself runs through
:func:varagity.pipeline.graph_flow.graph_build_flow, so every attempt is a
tracked Prefect flow run, and through
:class:~varagity.graph.service.GraphService, so the API stays the single
writer of the graph working directory.
One writer per corpus (ADR-019): a run also claims the graph corpus's
:class:~varagity.api.corpus_gate.CorpusGate for its whole life, so a build
and a graph backup never overlap — a start while a backup holds the corpus
raises :class:~varagity.api.corpus_gate.CorpusBusy (409 corpus_busy),
and the run turns terminal and frees the corpus in one critical section,
before the terminal frame — so a start that sees the run finished, by its
frame or by the single-flight check, always finds the corpus free. The reserved
varagity-exports/ folder, where the graph's bundles live, is never part
of the scanned corpus.
Feed = tuple[str, BaseModel]
module-attribute
¶
One feed item: (sse_event_name, payload_model).
GraphBuildAlreadyRunning
¶
Bases: RuntimeError
Raised by :meth:GraphBuildRunner.start while a build is in flight.
Source code in varagity/api/graph_runner.py
105 106 | |
GraphBuildRunner
¶
Owner of the (single) background graph build and its event feed.
Source code in varagity/api/graph_runner.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 | |
building
property
¶
Whether a build is in flight in this process.
Returns:
| Type | Description |
|---|---|
bool
|
|
service
property
¶
The graph service this runner builds through.
Returns:
| Type | Description |
|---|---|
GraphService
|
The injected service, or the process singleton — resolved |
GraphService
|
lazily, so constructing a runner never touches the registry. |
__init__(flow=None, *, service=None, on_reingest_complete=None, gate=None)
¶
Wire the runner's collaborators (all injectable for tests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flow
|
Callable[..., BuildReport] | None
|
The build callable, |
None
|
service
|
GraphService | None
|
The graph service to build through; the process singleton, resolved on first use, when omitted. |
None
|
on_reingest_complete
|
Callable[[], None] | None
|
Hook fired after a |
None
|
gate
|
CorpusGate | None
|
The graph corpus's write gate; the process-wide one when omitted. |
None
|
Source code in varagity/api/graph_runner.py
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
scanned_documents()
¶
Report what the last build's scan parsed, keyed by relative path.
Returns:
| Type | Description |
|---|---|
dict[str, ScannedDocument]
|
A copy of the per-file parse summaries; empty before any build |
dict[str, ScannedDocument]
|
has scanned the corpus (parsing a multi-gigabyte database is a |
dict[str, ScannedDocument]
|
build's job, not a list request's). |
Source code in varagity/api/graph_runner.py
450 451 452 453 454 455 456 457 458 459 | |
snapshot()
¶
Report the current (or last) build without subscribing.
Returns:
| Type | Description |
|---|---|
GraphBuildRunOut | None
|
The run snapshot, or |
Source code in varagity/api/graph_runner.py
431 432 433 434 435 436 437 438 | |
start(*, reingest=False, message_limit=None, since=None)
¶
Start a background graph build.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reingest
|
bool
|
Wipe the engine's working directory first and index from scratch (the only thing that clears the graph-stale flag). |
False
|
message_limit
|
int | None
|
Index at most this many messages, newest first. |
None
|
since
|
date | None
|
Ignore messages older than this date. |
None
|
Returns:
| Type | Description |
|---|---|
GraphBuildRunOut
|
The new run's handle/state snapshot. |
Raises:
| Type | Description |
|---|---|
GraphBuildAlreadyRunning
|
If a build is already in flight. |
CorpusBusy
|
If a graph backup holds the corpus. |
Source code in varagity/api/graph_runner.py
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | |
subscribe()
async
¶
Stream this runner's events: full backlog first, then live.
The stream ends after the terminal status frame (or immediately
after an idle/terminal snapshot when nothing is running), so a
client connecting at any point — including hours into a backfill —
renders the same picture.
Yields:
| Type | Description |
|---|---|
AsyncIterator[Feed]
|
|
Source code in varagity/api/graph_runner.py
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 | |
ScannedDocument
¶
Bases: BaseModel
One graph source file as the last build's scan found it.
Attributes:
| Name | Type | Description |
|---|---|---|
relative_path |
str
|
The file's POSIX path under |
doc_id |
str
|
The stable id derived for it (relative path + byte hash). |
parse |
GraphParseSummary
|
What the scan parsed out of it. |
Source code in varagity/api/graph_runner.py
109 110 111 112 113 114 115 116 117 118 119 120 | |
apply_bounds(batches, *, message_limit, since)
¶
Narrow parsed batches to the messages a bounded build should index.
since is a date floor and message_limit keeps the newest
messages — both bound toward the recent end, which is what a spot check
on a decade-long archive wants to look at. The cap applies to the
batches' merged timestamp order, so it means "N messages", not "N per
file"; batch identity survives, because a build's document ids and the
manifest diff are derived from it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batches
|
Sequence[MessageBatch]
|
Parsed source files. |
required |
message_limit
|
int | None
|
Keep at most this many messages; |
required |
since
|
date | None
|
Drop messages older than this date; |
required |
Returns:
| Type | Description |
|---|---|
list[MessageBatch]
|
The narrowed batches, in input order, each keeping only its |
list[MessageBatch]
|
surviving messages. A batch left with none is dropped: handing the |
list[MessageBatch]
|
engine an empty document would claim the conversation is empty. |
Source code in varagity/api/graph_runner.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
get_graph_build_runner()
¶
Provide the process-wide graph build runner (FastAPI dependency, override seam).
Returns:
| Type | Description |
|---|---|
GraphBuildRunner
|
The lazily created singleton. Construction resolves nothing — the |
GraphBuildRunner
|
service (and through it the engine registry) is looked up on the |
GraphBuildRunner
|
first build. |
Source code in varagity/api/graph_runner.py
776 777 778 779 780 781 782 783 784 785 786 787 788 | |
parse_summary(batch)
¶
Summarize one parsed source file for the corpus listing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch
|
MessageBatch
|
The parsed batch. |
required |
Returns:
| Type | Description |
|---|---|
GraphParseSummary
|
Its message/thread counts and time span (both timestamps |
GraphParseSummary
|
for a file that parsed to nothing). |
Source code in varagity/api/graph_runner.py
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
scan_graph_corpus(root)
¶
List every file under the graph corpus root, in stable path order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
|
required |
Returns:
| Type | Description |
|---|---|
list[Path]
|
Every regular file under |
list[Path]
|
no registered source claims included (a contacts list, a |
list[Path]
|
|
list[Path]
|
them; the corpus listing shows the user everything they dropped in. |
list[Path]
|
The one exception is the reserved |
list[Path]
|
backup bundles are never corpus, so a build never counts them as |
list[Path]
|
scanned files. |
Source code in varagity/api/graph_runner.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
varagity.api.corpus_gate
¶
One writer per corpus, across every runner that writes it (ADR-019).
The ingest runner, the graph build runner, and the backup runner each keep
their own single-flight check, which stops a second run of the same kind
(ingest_already_running, graph_build_running). A corpus needs more
than that once backups exist: an export must not snapshot a corpus a
restore is replacing, and a restore must not replace one an ingest is
writing. The gate is that shared rule — one :class:CorpusGate per corpus,
claimed for the whole of a run by whichever operation got there first, and
refused to everyone else with :class:CorpusBusy (the routes' 409
corpus_busy).
It is deliberately small and in-process: the API is the only process that
runs these operations through the GUI (the graph's single writer by design),
so a threading.Lock is the whole mechanism. Claims never block — a busy
corpus is an answer, not a wait.
The cross-process half (RAG only). Some RAG writers run in their own
process — uv run main.py ingest, and main.py chat, which ingests on
startup — where no in-process gate can see them. The RAG corpus therefore
also has a Postgres advisory lock
(:meth:~varagity.stores.vector_store.ContextualVectorDB.corpus_lock): every
ingest, in any process, holds it shared for its whole run, and a restore
holds it exclusive. The gate orders the API's own operations (and names the
holder in the 409); the lock is what keeps a CLI ingest and a restore
apart. The graph needs no such half — its single writer is this process.
Operation = Literal['ingest', 'build', 'export', 'restore']
module-attribute
¶
Every operation that writes (or snapshots) a corpus.
CorpusBusy
¶
Bases: RuntimeError
Raised by :meth:CorpusGate.claim while another operation holds the corpus.
Attributes:
| Name | Type | Description |
|---|---|---|
corpus |
The corpus that is busy. |
|
holder |
Operation
|
The operation holding it — named in the message, so the
|
Source code in varagity/api/corpus_gate.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
__init__(corpus, holder)
¶
Name the busy corpus and its holder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The corpus that is busy. |
required |
holder
|
Operation
|
The operation holding it. |
required |
Source code in varagity/api/corpus_gate.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
CorpusGate
¶
The non-blocking, single-holder write claim on one corpus.
Attributes:
| Name | Type | Description |
|---|---|---|
corpus |
The corpus this gate guards ( |
Source code in varagity/api/corpus_gate.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
holder
property
¶
The operation holding the corpus right now.
Returns:
| Type | Description |
|---|---|
Operation | None
|
Its name, or |
__init__(corpus)
¶
Create an unclaimed gate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The corpus this gate guards. |
required |
Source code in varagity/api/corpus_gate.py
71 72 73 74 75 76 77 78 79 | |
claim(operation)
¶
Take the corpus for operation, or refuse at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation
|
Operation
|
The operation starting. |
required |
Raises:
| Type | Description |
|---|---|
CorpusBusy
|
If any operation (the same kind included) already holds the corpus. |
Source code in varagity/api/corpus_gate.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
ensure_free()
¶
Refuse at once if any operation holds the corpus — without claiming it.
For routes that should answer 409 before doing expensive work
(a restore hashing a bundle) or that write outside any run (the
document deletes). The claim that follows can still lose a race;
it raises the same error.
Raises:
| Type | Description |
|---|---|
CorpusBusy
|
If the corpus is held. |
Source code in varagity/api/corpus_gate.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
ensure_not_held_by(operations)
¶
Refuse at once if one of operations holds the corpus — without claiming it.
:meth:ensure_free narrowed to the operations a caller actually
conflicts with: a document delete is fine beside an ingest but not
beside a backup, a graph source delete only clashes with a restore.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operations
|
Collection[Operation]
|
The holders that refuse the caller. |
required |
Raises:
| Type | Description |
|---|---|
CorpusBusy
|
If one of them holds the corpus. |
Source code in varagity/api/corpus_gate.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
release()
¶
Give the corpus back (idempotent: releasing a free gate is a no-op).
Source code in varagity/api/corpus_gate.py
96 97 98 99 | |
get_corpus_gates()
¶
Provide the process-wide gates, keyed by corpus (FastAPI dependency, override seam).
Returns:
| Type | Description |
|---|---|
dict[str, CorpusGate]
|
|
dict[str, CorpusGate]
|
the process, so every runner that claims a corpus shares its gate. |
Source code in varagity/api/corpus_gate.py
147 148 149 150 151 152 153 154 | |
varagity.api.backup_runner
¶
Background backup runs with a replayable live event feed (ADR-019).
POST /api/backups/{corpus}/export must answer at once while the bundle
is written, and GET /api/backups/{corpus}/status must stream where the
run is — the same contract ingest and the graph build already keep, so the
/corpus panels fold all three with the same kind of pure reducer:
- One writer per corpus. :meth:
BackupRunner.startclaims the corpus's :class:~varagity.api.corpus_gate.CorpusGateand holds it for the whole run; a second export or restore — or an ingest, which claims the same gate — is refused with :class:~varagity.api.corpus_gate.CorpusBusy, the route's409 corpus_busy. There is no separate single-flight flag: the gate is the one-run-at-a-time rule. - A tracked flow. The run executes the
rag-export,rag-restore,graph-exportorgraph-restorePrefect flow on a daemon thread, in-process, exactly as the CLI and the ingest runner run theirs; stage progress rides anon_progresscallback and thevaragity.backuplog records are relayed aslogevents. Each corpus has its own runner (and gate), so a graph export and a RAG restore run side by side. - Replay + live fan-out. Events accumulate per run; a subscriber gets
the full backlog first, then live events until the terminal
statusframe — connecting mid-run or after completion renders the same picture. - Each run relays its own lines. Both corpora's runs log to the one
varagity.backupnamespace and can run side by side, so a run's relay forwards only the records its own worker logs (a context variable marks them), and the namespace's INFO pin is shared by count — the first run to finish can't silence the other's feed. - A refused bundle is remembered. A restore verifies every member as
it loads it; when that finds the bundle unsound or damaged, the run
records it in :class:
DamagedBundles, so the list shows it as such and the next restore of it is refused up front.
The terminal transition is one critical section: the run turns terminal, its last frame joins the backlog, live subscribers are detached, and the gate is released together — so a subscriber can never see a finished run without its final frame, and a client reacting to that frame can always start the next run.
BackupOperation = Literal['export', 'restore']
module-attribute
¶
What a backup run can do.
BackupStage = ExportStage | RestoreStage | GraphExportStage | GraphRestoreStage
module-attribute
¶
Every stage a backup run's progress frames can name.
ExportResult = RagExport | GraphExportReport
module-attribute
¶
What an export flow returns — either corpus's published bundle and its timing.
Feed = tuple[str, BaseModel]
module-attribute
¶
One feed item: (sse_event_name, payload_model).
RestoreResult = RagRestore | GraphRestore
module-attribute
¶
What a restore flow returns — what either corpus's restore wrote, and its stale flag.
BackupRunner
¶
Owner of one corpus's (single) background backup run and its event feed.
Attributes:
| Name | Type | Description |
|---|---|---|
corpus |
Corpus
|
The corpus this runner backs up. |
Source code in varagity/api/backup_runner.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 | |
running
property
¶
Whether a run is in flight.
Returns:
| Type | Description |
|---|---|
bool
|
|
__init__(corpus, *, gate=None, export_flow=None, restore_flow=None, ingest_settings=None, damaged=None)
¶
Wire the runner's collaborators (all injectable for tests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
The corpus to back up. |
required |
gate
|
CorpusGate | None
|
The corpus's write gate; the process-wide one when omitted. |
None
|
export_flow
|
Callable[..., ExportResult] | None
|
The export callable — |
None
|
restore_flow
|
Callable[..., RestoreResult] | None
|
The restore callable — |
None
|
ingest_settings
|
Callable[[], dict[str, str]] | None
|
Provider of the reingest-affecting settings —
what a RAG export's bundle records and what a RAG restore's
stale flag is judged against; :func: |
None
|
damaged
|
DamagedBundles | None
|
Where a restore records a bundle it refused for its contents; the process-wide memory the backup routes read when omitted. |
None
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If no export or restore flow exists for |
Source code in varagity/api/backup_runner.py
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | |
ensure_free()
¶
Refuse at once if any operation holds the corpus (without claiming it).
The restore route's first check: a busy corpus answers 409
before the bundle is even opened.
Raises:
| Type | Description |
|---|---|
CorpusBusy
|
If the corpus is held. |
Source code in varagity/api/backup_runner.py
494 495 496 497 498 499 500 501 502 503 | |
snapshot()
¶
Report the current (or last) run without subscribing.
Returns:
| Type | Description |
|---|---|
BackupRunOut | None
|
The run snapshot, or |
Source code in varagity/api/backup_runner.py
475 476 477 478 479 480 481 482 | |
start(operation, bundle=None)
¶
Start a background backup run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation
|
BackupOperation
|
What to run ( |
required |
bundle
|
str | None
|
The bundle a restore restores — a file name in the corpus's exports folder (the route has already resolved and verified it). Not taken by an export. |
None
|
Returns:
| Type | Description |
|---|---|
BackupRunOut
|
The new run's handle/state snapshot (state |
Raises:
| Type | Description |
|---|---|
CorpusBusy
|
If any operation already holds the corpus — a second export or restore included. |
ValueError
|
If a restore names no bundle, or an export names one. |
Source code in varagity/api/backup_runner.py
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 | |
subscribe()
async
¶
Stream this runner's events: full backlog first, then live.
The stream ends after the terminal status frame (or immediately
after an idle/terminal snapshot when nothing is running), so a
client connecting at any point renders the same picture.
Yields:
| Type | Description |
|---|---|
AsyncIterator[Feed]
|
|
Source code in varagity/api/backup_runner.py
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 | |
DamagedBundles
¶
Bundles a restore refused for their contents, remembered in-process.
A restore verifies every member as it loads it — the only check that sees a bundle damaged inside an intact zip, or a line no row can be made from — and the list's verdict is deliberately cheap, so what a restore found is remembered here: the list then shows the bundle as not restorable, with that reason, and the restore route refuses it up front instead of loading it again.
Keyed by corpus, name, size and mtime: a bundle whose bytes were replaced (copied over, re-exported under the same name) is a new file and is judged afresh. In-process only — the next API start forgets, and the next restore of that bundle fails (and is remembered) again.
Source code in varagity/api/backup_runner.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | |
__init__()
¶
Start with nothing remembered.
Source code in varagity/api/backup_runner.py
163 164 165 166 | |
reason(corpus, listing)
¶
Report why a listed bundle was refused, if it was.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The listing's corpus. |
required |
listing
|
BundleListing
|
The bundle as the folder lists it now. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The remembered reason, or |
str | None
|
size, mtime) was never refused. |
Source code in varagity/api/backup_runner.py
184 185 186 187 188 189 190 191 192 193 194 195 196 | |
reason_for(corpus, name, path)
¶
Report why a bundle file was refused, if it was (the restore route's check).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The bundle's corpus. |
required |
name
|
str
|
Its file name in the exports folder. |
required |
path
|
Path
|
The file (stat'ed for its size and mtime). |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The remembered reason, or |
str | None
|
refused (or can't be stat'ed). |
Source code in varagity/api/backup_runner.py
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | |
remember(corpus, name, path, reason)
¶
Remember that a restore refused this bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
str
|
The bundle's corpus. |
required |
name
|
str
|
Its file name in the exports folder. |
required |
path
|
Path
|
The file (stat'ed for its size and mtime). |
required |
reason
|
str
|
What the restore found. |
required |
Source code in varagity/api/backup_runner.py
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
current_ingest_settings()
¶
Snapshot the reingest-affecting settings, in env-string form.
What a RAG bundle records as ingest_settings: the values whose
change marks the corpus stale (CHUNKING_STRATEGY, CHUNK_SIZE,
CHUNK_OVERLAP, CONTEXTUALIZE, OCR_ENGINE), rendered exactly
as app_settings stores an override — so a restore compares like with
like. Read here, in the API layer, so the backup package never imports
it.
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Setting name → env-string value, sorted by name. |
Source code in varagity/api/backup_runner.py
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
get_backup_runners()
¶
Provide the process-wide backup runners, keyed by corpus (dependency, override seam).
Returns:
| Type | Description |
|---|---|
dict[str, BackupRunner]
|
The lazily created runners, one per corpus ( |
dict[str, BackupRunner]
|
each on its corpus's process-wide gate, so a backup and that |
dict[str, BackupRunner]
|
corpus's ingest or build always exclude each other. Constructing one |
dict[str, BackupRunner]
|
resolves nothing: the graph's flow finds the graph service only when |
dict[str, BackupRunner]
|
an export runs. |
Source code in varagity/api/backup_runner.py
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
get_damaged_bundles()
¶
Provide the process's refused-bundle memory (FastAPI dependency, override seam).
Returns:
| Type | Description |
|---|---|
DamagedBundles
|
The one :class: |
DamagedBundles
|
runners record into it, the backup routes read it. |
Source code in varagity/api/backup_runner.py
221 222 223 224 225 226 227 228 | |