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 like 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. |