Chapter 12
The lessons pipeline
A system's interactions contain the information that should make it better. This chapter is how Holonograph turns that information into shipped improvements: captured human overrides cluster, the drafter proposes concrete corrective artifacts for each cluster, a named human approves or rejects at the gate, and only then does anything get published. Nothing bypasses the gate.
What it is
A five-step pipeline, one endpoint per step:
- Override — a human corrects the system's output. Recorded as first-class ground truth: what the system produced next to what good looks like.
- Cluster — similar overrides group together, so one fix can address a pattern instead of a single instance.
- Draft — the drafter proposes a concrete corrective artifact for a cluster. Four kinds:
skill,lesson,fixture,code-change-recommendation. - Approve or reject — a named approver reads the draft, edits or rejects. This is the gate.
- Publish — an approved draft becomes substrate. Versioned, captured, and attributable in future readings like any other change.
Why it matters
Feedback in most agentic systems is either noted-and-forgotten or wired into a slow fine-tuning loop. Neither is a good fit for the operator running a real product on a real timeline. The lessons pipeline gives that operator a third path: capture the correction, cluster the pattern, ship a concrete artifact through a named gate. The artifact — a new skill, a lesson, a fixture, a code change — is the smallest unit of "the system got better," and every one of them is attributable in future drift readings.
How to use it
1. Record an override
When a human corrects the system, capture both sides:
POST /hil/override
x-holonograph-run-mode: production
Content-Type: application/json
{
"agentId": "billing-triage",
"surfaceId": "classify-intent",
"correlationId": "corr_01H...",
"originalOutput": { /* what the system produced */ },
"correctedOutput": { /* what the human sent instead */ },
"rationale": "Refund lookup wasn't invoked; agent guessed.",
"tags": ["fix_tier=tool-input-schema"]
}
Response: an eventId. The override is now in the substrate, tagged and correlated with the original call.
2. Cluster overrides over a window
Run the clustering endpoint to group similar overrides — usually on a schedule (nightly), sometimes on-demand when an operator wants to see what's been building up.
POST /lessons/cluster
x-holonograph-run-mode: production
Content-Type: application/json
{
"surfaceId": "classify-intent",
"since": "2026-07-01T00:00:00Z",
"until": "2026-07-05T00:00:00Z"
}
Response: clusters[] plus a total eventCount. Each cluster carries its member override ids and a summary shape the drafter can consume.
3. Draft a corrective artifact
Ask the drafter to propose an artifact for a cluster. Pick the kind that fits:
| Kind | When it fits |
|---|---|
skill | The agent needs a new capability it doesn't currently have. |
lesson | The agent has the capability but keeps using it wrong. A prompt-level correction is enough. |
fixture | The correction should be a reproducible check, so future regressions get caught before shipping. |
code-change-recommendation | The problem lives in code (tool schema, dispatch logic) and needs a developer to fix. |
POST /lessons/draft
x-holonograph-run-mode: production
Content-Type: application/json
{
"kind": "fixture",
"cluster": { /* cluster payload from the previous step */ },
"parentSurfaceId": "classify-intent",
"seedEventId": "evt_01H..."
}
For fixture drafts, provide either a seedEventId (a real event to base the fixture on) or a seedRawRequest (a hand-authored request shape). Optionally supply a parentFixtureId if this fixture should stack on an existing one.
Response: a draft object, an optional classification label the drafter applied, and an enqueued flag reflecting whether the draft was queued for the gate.
4. Approve or reject at the gate
Approve:
POST /hil/approve
Content-Type: application/json
{
"draftId": "draft_01H...",
"approverId": "brian",
"comment": "Correction verified against the surface's rules. Approving."
}
Reject (a comment is required — the record of why matters):
POST /hil/reject
Content-Type: application/json
{
"draftId": "draft_01H...",
"approverId": "brian",
"comment": "This one's not a pattern — the customer was testing us. Discarding."
}
Both endpoints return the current status and a history[] of the actions taken on this draft so far.
5. Publish
Once approved, publish the draft to make it live. Provide any existing lessons the publisher should consider superseding — the publisher will merge/replace/link as appropriate.
POST /lessons/publish
Content-Type: application/json
{
"draftId": "draft_01H...",
"candidateExistingLessonIds": ["lesson_01G..."]
}
Response: a publish report naming the new artifact id, any superseded artifacts, and the substrate event the publish itself produced.
Re-run a published fixture
Once a fixture is live, you can re-run it any time against its parent surface:
POST /fixtures/rerun
Content-Type: application/json
{ "fixtureId": "fx_01H..." }
Response includes the actual result the run produced and the expected[] assertions the fixture carries — the diff between the two is your regression signal.
Reading the output
Published artifacts become substrate. That means they show up as their own events, they get versioned, and — crucially — future drift readings can point at "this artifact was published on Tuesday" as a candidate explanation for a movement in the numbers.
The pipeline is recursive by construction. The drafter's own model call passes through the lens, so the act of proposing a correction is itself observed. Getting better is a measured event.
Reference
Endpoints
| Method & path | Purpose |
|---|---|
POST /hil/override | Record a human override on a captured event. |
POST /lessons/cluster | Cluster overrides in a window. |
POST /lessons/draft | Ask the drafter to propose a corrective artifact for a cluster. |
POST /hil/approve | Approve a draft at the gate. |
POST /hil/reject | Reject a draft at the gate (comment required). |
POST /lessons/publish | Publish an approved draft as substrate. |
POST /fixtures/rerun | Re-run a published fixture against its parent surface. |
POST /hil/override — request
| Field | Shape | Notes |
|---|---|---|
agentId | string (req) | The agent this override applies to. |
surfaceId | string (opt) | The surface, when known. |
correlationId | string (opt) | Ties the override to the original call. |
tags | string[] (opt) | Cohort tags — fix_tier=…, severity=…, and any other identifiers that will help clustering later. |
originalOutput | object (req) | What the system produced. |
correctedOutput | object (req) | What the human sent instead. |
rationale | string (opt) | Human explanation of the correction. |
activeArtifacts | array (opt) | Which lessons / fixtures were active at the time of the original call. |
POST /lessons/draft — request
| Field | Shape | Notes |
|---|---|---|
kind | enum (req) | skill, lesson, fixture, or code-change-recommendation. |
cluster | object (req) | Payload from /lessons/cluster. |
enqueue | boolean (opt) | Queue the draft for the gate immediately. Default true. |
parentSurfaceId | string | Required for fixture drafts. |
seedEventId | string (opt) | For fixture: base the fixture on a real event. |
seedRawRequest | object (opt) | For fixture: hand-authored request shape when there's no seed event. |
parentFixtureId | string (opt) | For fixture: stack this fixture on an existing one. |