The System
Four public repos talk over frozen HTTP contracts. An audit found each side's tests asserting against its own copy of the shape.
Case study summary
- Problem: Make four independently deployable repositories operate as one system without hiding coupling in shared code.
- Ownership: I directed the solo build, defined the cross-repo decisions and contracts, and audited whether their controls worked.
- Constraints: Note creation had to stay responsive when the classifier was unavailable; reprocessing had to be safe; and each repository kept its own release boundary.
- Actions: I designed an asynchronous, idempotent enrichment loop, recorded system-level ADRs, added contract checks, and built a portal linking decisions to the source.
- Result and evidence: Notes are classified and tagged automatically and the system is browsable end to end. The audit also exposed a false-green contract seam; it remains open and is documented rather than presented as solved.
Two unit tests that happen to agree are not a contract test. Each side was green. The seam was not.
I direct Claude Code to build it. The operating layer is public:
agent-ops —
the PreToolUse guards that enforce the security posture
mechanically, the working agreements parallel sessions run under, and an
incident log held to a deliberate severity bar, failures left in.
The four repos
- notes-apiFastAPI REST service. The knowledge base and data layer.
- defense-news-classifierLLM classifier with a real eval harness. Labels the content.
- kb-agentRAG and tool-use agent. The hub that reasons over the system.
- learning-notesPlain-language notes on the techniques behind it all.
All four repos are public. The ADRs and contract tests below live in those repos.
The map
Click a box to see what each part does. The same flow is written out below.
How it flows
- A note is created in notes-api (
POST /notes), which enqueues a FastAPIBackgroundTaskto classify and tag it. The201returns immediately; the task runs after the response is sent. - The background task POSTs the note text to the classifier's
/classifyendpoint. - The classifier classifies the text (one Sonnet call, structured output forced via tool-use) into a category, an operational domain, and a region, and returns them.
- The task writes the labels back as namespaced tags (
PUT /notes/{id}/tags, replace semantics), preserving the user's own tags. It currently maps the first two; the region field is newer than this consumer.CLASSIFIER_URLunset makes the whole step a no-op. - The kb-agent reads notes (
GET /notes) to ground its RAG answers, and can also call the classifier synchronously.
That is the loop in words. Watch one note go through it, with the real schemas →
The problem
The four repos should act as one system and stay independently deployable. That lives in the contracts between them, not in shared code.
The decisions
Decision · SYS-004 / SYS-006
Decouple with contracts, not shared code
The repos talk over frozen HTTP contracts with explicit versioning rules, and each side carries tests pinning the shape.
Why: a renamed field breaks the consumer silently at runtime. Tradeoff: freeze the wire shape and write the tests, so drift fails a build instead of production.
I audited my own decision log in July 2026 and found this seam broken. The classifier shipped a third field. The frozen contract required a coordinated consumer update. That update never happened, and both repos' CI stayed green. Each side's “contract test” asserted against its own copy of the shape. The provider's fixture was updated in the same commit that changed the provider. The consumer checked itself against a stub it wrote. Neither could see the other.
A real contract test needs a single shared artifact both sides assert against, so changing one copy fails the other's build. Amended in the open at SYS-004, with the falsified consequences struck through rather than deleted. False Green collects six more checks that reported confidence they had not earned. Context Architecture is the same split one layer up, where the rules an agent reads get promoted from prose into hooks.
Reversed. Decision · SYS-005
Make the enrichment loop async and idempotent by design
Creating a note triggers a FastAPI BackgroundTask in notes-api. The task
calls the classifier, then writes the returned labels back as namespaced
tags (category: / domain:) with replace
semantics — notes-api owns the writeback, the classifier stays a pure
provider.
Why: running classification in a BackgroundTask keeps note creation fast and decouples it from classifier latency, while replace semantics ensure reprocessing converges instead of accumulating duplicate tags. Tradeoff: each task needs its own retry path and its own error handling.
Decision · SYS-002
Gate model choice on evidence
Default to Sonnet across the system. Escalate to Opus only where an eval shows the quality gain pays for itself. The same gate ran the July 2026 migration to Sonnet 5: eval before and after on the same gold set.
Why: the escalation has now been built and measured three separate times, and declined all three — the verdicts are on the classifier and the judge pages, where they were measured. Tradeoff: you have to run the eval to justify the tier instead of defaulting to the biggest model.
Decision · SYS-001 / ADR-001
Record decisions, then make the system legible
Cross-repo decisions live as two-tier ADRs (repo-local choices stay local;
system-level ones are SYS-NNN in one place). A generated
portal pulls every repo's docs into one browsable view that deep-links
back to the code. It links to each doc where it already lives instead of
copying it, so there's a single source of truth.
Why: a decision that is not written down gets re-argued.
The outcome
- Create a note and it is automatically classified and tagged, asynchronously, with no coupling between note creation and classifier availability.
- The four repos stay independently deployable — though cross-repo drift is not yet caught by a red build. I thought it was; an audit proved otherwise, and closing that gap needs one shared contract artifact both sides assert against.
- The whole system is browsable in one portal, with links straight to the real code.
- A skill map (SYS-007) spells out what the system exercises: evals, context engineering, agents, and observability. It also names where I'm weak, which is security on the agent's tool seam.
Where it goes next
The roadmap is an autonomy ladder: the same classifier, more self-direction one level at a time. The ladder, level by level →