← Back to The System

One Note, End to End

One gold note through the real schemas. The writeback maps all three labels. It dropped one of them until a contract check found the difference.

The article text and all three predicted labels are real. They come from gold row g002 in the classifier's eval set. That prediction matches the hand label and the LLM judge on every axis. The note's title, id, and timestamps are illustrative placeholders.

Plate 01 One note end to end. notes-api creates the note. The classifier returns three labels. The writeback maps all three as namespaced tags. kb-agent can then search by tag. notes-api POST classifier 3 fields writeback 3 tags kb-agent search cat + domain + region same note, real schemas

Create, classify, write three tags, search. The writeback maps every field the classifier returns. Step 3 below records the one it dropped, and the check that closed the gap.

  1. A client creates a note

    POST /notes takes a title and content. It returns 201 immediately and schedules the enrichment as a background task, so tags is still empty at this point.

    POST /notes
    {
      "title": "USS Ronald Reagan first ordnance drop",
      "content": "F/A-18E Hornets assigned to the \"Eagles\" of Strike Fighter Attack Squadron 115 ... in support of Operation Iraqi Freedom."
    }
  2. A forced tool call keeps the classifier in schema

    The background task POSTs the text to the classifier's /classify endpoint. One claude-sonnet-5 call pins tool_choice to the classify_article tool. That forces the model to answer only as an enum-validated tool call. The endpoint returns the call as {category, operational_domain, region}. There is no confidence score, by design.

    // forced tool_use emitted by the model
    {
      "name": "classify_article",
      "input": {
        "category": "operations",
        "operational_domain": "air",
        "region": "middle-east"
      }
    }
  3. Labels become namespaced tags

    notes-api owns the writeback. It maps the fields it knows to prefixed tags. Note the operational_domaindomain: rename. It applies the tags with replace semantics, so a second run converges and writes no duplicate. It keeps the user's own tags.

    The background task writes through the ORM in-process, and it does not call the service's own HTTP endpoint. The equivalent PUT /notes/{id}/tags exists for external callers, and it carries the same replace semantics.

    This step dropped region, and that is what the audit found. The classifier grew a third field in v3. This consumer read two fields, and it ignored the rest. It did not break, because the writeback pulls the fields out by name and does not assume the response shape. A drop that breaks nothing is the drop that no build reports.

    The frozen contract for this seam requires a provider change and a coordinated consumer update together. The classifier shipped region, and that update did not land with it. No build went red, because each repo's “contract test” asserted against its own copy of the shape rather than a shared one. Nothing broke here, and nothing would have noticed if it had. The SYS-004 amendment records it.

    That gap is closed. SYS-018 landed on 2026-07-18, and the same change maps region. The writeback writes three tags now, and the payloads on this page are the current ones. The classifier now publishes contracts/classify-response.schema.json, generated from its live response model, and this consumer fetches that file in CI and fails when its own field set diverges. A fetch failure still warns and exits 0, so a provider outage reads as a pass. The full decision →

    Plate 02 Two rows. In the first, the consumer reads the fields it knows by name, so a new field costs it nothing and nothing breaks. That is hygiene. In the second, a check now fails the build when the two shapes drift apart. It does not fail on a fetch failure. Hygiene a new field provider read by name consumer nothing breaks result A guard a check that fails the build Built it fails on drift, not on a fetch failure
    Tolerance of an unknown field is consumer hygiene, not a guard. The writeback reads the fields it knows by name, so a new field costs it nothing. A shared check now fails the build when the two shapes drift apart. A fetch failure still exits 0, so a provider outage reads as a pass. It does not show the seam that let the drift through. Each repository asserted against its own copy of the shape, and The System shows that seam. The notes above keep the reasoning and the record link.
    classifier_tags({
        "category": "operations",
        "operational_domain": "air",
        "region": "middle-east",
    })
    # -> ["category:operations", "domain:air", "region:middle-east"]
    
    # written in-process; note.tags = merge_tags(note.tags, new_tags)
    { "tags": ["category:operations", "domain:air", "region:middle-east"] }
  4. The note is now enriched

    A GET returns the labels in its flat tags list, and it returns enrichment_status beside them. The status starts at pending. The background task sets it to done after a writeback, and to failed after a classifier error. It stays pending when CLASSIFIER_URL is unset, so a local run never reads as a failure. The tags show the labels, and the status shows whether the task that wrote them finished.

    GET /notes/1
    {
      "id": 1,
      "title": "USS Ronald Reagan first ordnance drop",
      "content": "F/A-18E Hornets ... Operation Iraqi Freedom.",
      "tags": ["category:operations", "domain:air", "region:middle-east"],
      "enrichment_status": "done",
      "published_at": null,
      "created_at": "...",
      "updated_at": "..."
    }
  5. The agent can find it

    The kb-agent's search_notes tool filters by tag over the same HTTP seam, so the freshly-tagged note is now retrievable for grounding. The result follows the system-wide observation contract (SYS-003): a status, a summary, the payload, and a cited source.

    search_notes(tag="domain:air")  ->  GET /notes?tag=domain:air
    {
      "status": "success",
      "summary": "1 matching note(s).",
      "payload": [
        {
          "id": 1,
          "title": "USS Ronald Reagan first ordnance drop",
          "tags": ["category:operations", "domain:air", "region:middle-east"]
        }
      ],
      "source": "notes-api service, http://127.0.0.1:8000/notes"
    }