← Back

Product & Program

I frame the same system as a product, and I run it as a program. Solo build. The program layer is simulated.

Case study summary

This is a solo build. There is no team to coordinate, so the program layer is simulated. The dependency map and the risk register are real work. I did that work as if a team existed.

The problem (product framing)

Defense developments come from many sources. They arrive faster than one person can triage. An analyst spends a disproportionate share of the time to find them and to sort them. Real analysis begins after that.

Plate 01 Triage sorts the defense-news firehose by relevance and category into a queryable knowledge base. The analyst briefs from a prepared store, and not from a raw feed. Stages in order: Firehose, Triage, Queryable KB, Brief. Firehose Triage Queryable KB Brief raw feed rel+category ask store analyze

Firehose in; brief out. Triage and a queryable knowledge base sit in the middle, so the analyst does not spend analysis time on the sort.

Contents

Who it is for

Primary user: a defense-news analyst. The analyst must stay current across programs, contracts, geopolitics, and technology, and must brief others on what matters. Defense is a deliberately chosen vertical, and not a claim of domain expertise. Design partner: me. I use the system myself, as the first user and the early validation loop.

Job to be done

When a stream of defense news comes in, help me quickly know what matters and where it fits, so I spend my time analyzing instead of sorting.

Contents

Success metrics

Two kinds. They answer different questions.

User-outcome metrics (does it help the analyst?)

I defined these outcome metrics. I did not measure them yet. An honest measurement needs the analyst in the loop over real use, and this is a solo build. What I measure today is classification quality, below.

AI-quality metrics (is there a measurement, or only a demo?)

Plate 02 Three independent measurements, and one verdict. RETRIEVAL measured ESCALATION measured JUDGE STUDY measured THE SAME VERDICT three independent measurements
Three separate measurements returned the same verdict: the retired retrieval layer, the premium-model escalation, and the judge-reliability study. I built each one and I measured it. I did not assume any of them. The plate does not show what each measurement measured, or by how much it moved. The bullets above keep those figures, and the judge-reliability study keeps its own page.

Contents

Dependencies

kb-agent calls notes-api and the classifier as tools, so both services must work before the agent works as one system. SYS-003 sets that tool-layer contract, and SYS-004 and SYS-006 freeze the two wire seams. The dependency map in the architecture repo draws the graph.

Contents

Roadmap (program framing)

I sequenced the work Now / Next / Later. I harvested the sequence from delivery.

Plate 03 Now shipped, Next queued, Later deferred. Now shipped Next queued Later deferred harvested from delivery

Solid is shipped. Hairline is queued. Dashed is deferred, and it includes the things I will not build.

Now

  • Async enrichment loop: create, classify, idempotent writeback
  • Durable SQLite outbox: a crash after 201 does not drop the job
  • Integration tests across both seams (pytest, fully offline)
  • Agent tool-layer contract (SYS-003), accepted and built
  • Evals-as-CI: classifier gold evals and kb-agent retrieval both gate a PR
  • Local compose and kind manifests, with /health on each service
  • Weekly status cadence harvested from merged PRs
  • Product one-pager and this program view

Next

  • None. The 2026-09-09 wave closed the queued rows.

Later

  • Operate-what-you-built beyond local compose and kind
  • Other verticals: an explicit non-goal, articulated not built

Contents

Risk register

RiskSeverityMitigation
Enrichment lost if the process restarts after createLowA SQLite outbox stores the job in the same commit as the note. Namespaced-tag replace keeps a re-run idempotent. Note creation never fails on classifier errors
Classifier accuracy ceiling (residual label ambiguity, not the model)MediumDo not escalate the model. I measured it at +0 rows for ~1.97× cost. Refine the rubric instead. A confusion breakdown found the misses clustered on one boundary, and two clauses lifted category 90.7% → 94.4%. The 90.7% start is the post-rubric eval snapshot of 2026-07-17, and the fix cost one domain row
Breadth creep (verticals without depth)MediumDeep on one vehicle; other verticals are an explicit non-goal
Planning theater (docs drift from delivery)MediumKeep the artifacts thin and current, and tie them to real delivery only
Silent contract drift (separate repos)This is the one risk that happened. The callout below carries the severity, what happened, how I closed it, and what the fix still does not cover
Simulated program (no real cross-team coordination)Low (honesty)I state it plainly. The reasoning and the artifacts are real. The org is not
Strip 04
Six risks sit in the register, and one of them happened. This callout carries that one at full weight: frozen contracts that no shared artifact enforced, and the artifact that now enforces them. The callout does not show the other five risks. It states the fix and the date, and it does not show the CI steps that run it. Those sit on The System.

Contents

I pre-registered six axes for a capacity review, then measured that nothing wrote to them.

This record applies an evidence gate to a model choice. I applied the same gate to the build capacity behind the work. On a fixed date I wrote the review down. It named six evidence axes, a close date, and a rule that nothing changes until the close. Then I let the axes decide.

Plate 05 Pre-register the axes, freeze until the close, rule on the record. PRE-REGISTER axes first FREEZE no changes CLOSE on the record the gate said step down

Two design choices did all the work. I wrote the axes before the evidence existed, and nothing changed until the close.

Those two choices are the whole method. I wrote the axes first, so nothing could pick its own success criterion after the fact. I froze every change until the close, so nothing could be cut on a bad week or kept on a good anecdote. The close came early, on a full month of evidence. One reason justifies an early close: the last stretch added no new evidence on any axis.

The results, by axis

This section reports by evidence axis. It never reports by lane and never by name. An axis is a question. A lane is a seat, and a seat maps to somebody’s product. A report by lane turns a one-operator measurement into a public claim about a third party. A measurement is not an opinion. A table around an opinion does not make it a measurement.

Strip 06

The rulings, as principles

The board argues that the record was lopsided. What the axes produced sits beside the rulings that followed, so the reader does not have to join them by hand. The board names no seat, no product, and no lane. It reports the axes only, and it does not close the review. The limit on the whole reading sits below it.

The limit on this reading, at the point of the claim: one month, one operator, one body of work, and no controlled comparison. It is a routing decision for me. It is not a verdict on anybody’s product.

The result: I pre-registered the six axes and nothing ever wrote to them. There was no friction log, no tally file, and no dead-rows list. So I had to reconstruct the evidence at the end out of dated records. The gate still returned a clear answer, because the answer was lopsided. A closer result would have been unrecoverable.

The lesson: pre-registration without an instrument is half a measurement. I built a gauge for this same capacity, and it does not write this log today. That is the open item. I name it here, because that is the only honest place to end.

telltale, the instrument for the same capacity →

Contents

What it demonstrates

The scale is personal. The program is simulated. The source documents live in the architecture repo: the product one-pager and the program view.