Version. 0.1
Date. 2026-06-09
Status. Scoping note. Scopes Phase 5 of the API documentation effort: describing the response side of the API so every endpoint shows not only what goes in (parameters, request fields — done in Phase 3) but what comes back and what each returned field means. Operator-approved direction is Scope B (see §2). Operator-facing. HTML primary, Markdown source alongside. Precedes the change request and the effort plan; nothing is built from this note until the CR lands.
Author posture. Discovery — the size, the two forks, and the reasoning behind the recommended scope are preserved, not only the chosen direction.
Grounded against. loomworks-engine on main (clean, 2026-06-09): the generator scripts/generate_api_docs.py, the live OpenAPI schema (API version 0.1.0, 195 operations, 276 component schemas), and the response-model classes in src/loomworks/api/schemas.py and siblings. Reads the API documentation effort plan v0.1, the annotation standard v0.3, and the glossary v0.7 as the governing standards.
The API reference document is complete on the request side: every endpoint lists its parameters, and Phase 3 described them. The response side is not. When a reader looks at an endpoint today, they see what to send but not what they get back — and the fields that are returned have almost no descriptions (about 769 of 772 response fields are blank). Phase 5 fixes both halves of that gap.
Two pieces of work, in order. First, a small change to the document generator so that each endpoint shows a response table — what comes back — sitting right under the parameter table, the way the request side already renders. Second, the grind: writing a plain description for each of the ~769 blank response fields, authored in the engine code the same way Phase 3 authored request descriptions, regenerated, and guarded by the same drift check. The grind is larger than the word "sweep" suggested — it is comparable in weight to the whole Phase 3 request grind, concentrated in two rooms (Memory and Cross-cutting carry 77% of it), so it runs across several sessions, phased by room, with a worked slice approved first to set the quality bar.
When Phase 5 is done, every endpoint in the reference shows parameters in and results out, each described — the document the Operator asked for.
The generated API reference is two files: the Markdown index (docs/api/api-reference.md) and the HTML detail document (docs/api/api-detail-blocks.html). After Phase 3 and the batch-9 close, the request side is fully documented: 195 operations, every parameter and request field carrying a summary. The response side has two distinct gaps.
Gap one — responses are not shown at the endpoint. Today the per-endpoint block renders only the request-body table. Response fields surface only in the HTML "Data models" appendix at the bottom of the document, and not at all in the Markdown. A reader at GET /engagements/{id}/renders/{render_id} sees what to send but must scroll to the appendix and cross-reference a schema name to learn what comes back. The results are in the document but not where the reader is looking.
Gap two — response fields are undescribed. The response models are distinct classes from the request models (a request CommitAssertionRequest and a response AssertionResponse are different classes). The request classes were annotated in Phase 3; the response classes never were. They carry bare type annotations with no Field(description=...). Of 772 response-only fields, 3 are described — effectively zero. The 15 schemas shared between request and response are 89% described, which is Phase 3 work showing through; everything response-only is blank.
Closing both gaps is what makes the reference the document described: every endpoint showing parameters in and results out, each described.
Two forks surfaced in grounding. They combine into one scope decision, settled by the Operator as B.
Fork — present results at the endpoint, or only in the appendix?
Why B. A delivers described fields but leaves them in an appendix a reader rarely opens; it does not match "results expected" shown at the endpoint, which is what the Operator asked for. B is the only version that delivers that document. The generator change is small relative to the grind.
Why the generator change goes first. Doing the generator enhancement before the grind means the empty response slots appear under every endpoint immediately. The grind's progress then becomes visible per-endpoint as it proceeds, and the worked-slice quality bar is set against the real surface the reader will see — not against an appendix. Grinding first and changing the generator second would mean annotating 769 fields blind to how they present, then discovering presentation issues after the fact.
A trajectory note (recorded, not chosen): "Phase 5 sweep" was the phrase carried into this from the effort plan and from earlier conversation. The grounding corrected it. The response surface is ~769 blank fields across ~166 distinct response classes — comparable in weight to the entire Phase 3 request grind, not a quick pass. The word "sweep" understated it. Phase 5 is a multi-session grind. Naming this now so the effort plan phases it honestly rather than scoping a single session against a several-session surface.
From the live schema. Response-side closure (every schema reachable from a 2xx response), with shared schemas counted in each room that returns them.
| Room | response schemas | response fields | described | blank | |---|---|---|---|---| | Foundation | 17 | 50 | 3 | 47 | | Memory | 96 | 392 | 21 | 371 | | Manifestation | 8 | 31 | 8 | 23 | | Shaping | 12 | 63 | 8 | 55 | | Rendering | 14 | 69 | 5 | 64 | | Cross-cutting | 40 | 221 | 0 | 221 |
Totals: 181 distinct response/output schema classes (166 response-only + 15 shared); 809 response-side fields; 36 described; 773 blank (the response-only blank count is ~769, the rest of the blanks falling in the partly-shared set).
The shape of the work. Memory (371) and Cross-cutting (221) together are 592 of the blank fields — 77% of the grind in two rooms. The other four rooms total ~189 blanks combined and are light. This lopsidedness drives the phasing in §5.
No new infrastructure. Phase 5 reuses the Phase 3 loop exactly.
Field(description=...) on the Pydantic response-model fields in engine code — overwhelmingly in src/loomworks/api/schemas.py (the bulk of the *Response classes), with smaller sets in src/loomworks/orchestration/schemas.py (operator/Companion responses), src/loomworks/notifications/schemas.py, and a scatter of response models defined inline in routers (uploads, saved filters, admin grants, workspaces, engagement tags, and a few singletons).scripts/generate_api_docs.py reads each field's description from the live OpenAPI schema (components.schemas.{name}.properties.{field}.description) — the same path it already uses for request fields. It does not need a sidecar; it needs the descriptions present on the classes.scripts/check_api_docs_fresh.py and .github/workflows/api-docs.yml regenerate in CI and fail on drift — the identical guard Phase 3 used. A field annotated but not regenerated, or regenerated but not committed, fails the check.
The one piece that is new — the generator enhancement (Scope B). Today the per-endpoint block renders only the request-body table; the generator has no per-endpoint response rendering. Scope B adds it: a _response_fields(op) function paralleling the existing _request_fields(op) (resolving the operation's 2xx response $ref to its schema and reading the same field table), and a response table emitted in the per-endpoint block in both the HTML detail document and the Markdown index. This is a contained generator change, modeled on code that already exists for the request side. It carries one design question for the worked slice (§5): whether the Markdown index gains a response table too, or whether response detail stays HTML-only consistent with the index's current "concise slots only" posture. Recommended default: response field names and types in the Markdown index (parity with how request fields appear there), full response descriptions in the HTML — preserving the index/detail split the reference already follows. The worked slice settles this against a real rendered example.
Seven steps. Step 0 (generator) and step 1 (worked slice) gate the grind; the grind is phased by room weight.
Step 0 — generator enhancement. Add _response_fields and the per-endpoint response table (HTML, and the Markdown index per the §4 default). Regenerate; confirm response slots now appear under every endpoint, blank where undescribed. No descriptions authored yet — this step makes the gap visible at the endpoint. Commit; CI green.
Step 1 — worked slice and quality bar. Annotate one coherent slice — recommended: the Memory assertions-core response models (AssertionResponse and its immediate kin), the response counterpart to a Phase 3 slice, so the request/response voices can be compared directly. Author the descriptions, regenerate, and present the rendered endpoints for Operator sign-off. This sets the response-description voice against the glossary v0.7 and annotation standard v0.3 before the grind commits to it across 769 fields. The grind does not proceed until the slice is approved.
Step 2 — Memory (remainder). The largest room (371 blank). Split into batches following the Phase 3 Memory batching where it maps; each batch a commit, CI green, per the per-batch rhythm.
Step 3 — Cross-cutting. 221 blank, one heavy room (admin, grants, credit, notifications response models). Likely two batches.
Step 4 — Rendering. 64 blank.
Step 5 — Shaping. 55 blank.
Step 6 — Foundation + Manifestation. 47 + 23 = 70 blank, paired as one light batch (Foundation response models are mostly auth/boundary; Manifestation is small).
Steps 4–6 are light and may compress into fewer sessions than listed; the effort plan sizes them once the worked slice fixes the per-field annotation pace.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks API documentation — Phase 5 scoping note — v0.1 — 2026-06-09