# Ten stories is a maximum: letting a morning briefing run short

Author: Chris Chabot
Canonical URL: https://chabot.dev/blog/ten-stories-is-a-maximum/
Published: 2026-11-03

How a briefing pipeline lets issues run short: a per-source cap, an evidence floor, smaller budgets for thin stories and a lexical audit of the final prose.

Give a language model ten slots and a thin morning and you will get ten confident paragraphs, and the weak ones will read about as well as the strong ones, because fluency and evidence have very little to do with each other. That is the central problem in building a briefing from a high-volume feed, and the rule I used to answer it is a modest one: ten stories per issue is an upper bound, never a quota to fill.

The briefing is part of my personal knowledge and assistant system. By August 2026, its pipeline selected candidate stories, composed reader copy and then audited that copy.

An issue on 6 August contained five stories, all from GitHub, because 1,804 of that morning's 1,958 items were GitHub and ranking by raw score simply reproduced the shape of the feed. The rule that came out of it allows no more than three stories from any one source kind, and what matters most is what happens when the rule bites. The cap applies after the signal floor and never lifts, so when only capped kinds remain, the issue gets shorter and the shortening is recorded. In the tests, five GitHub candidates against a target of five produce three stories, two survivors produce two, and none produces none. Nothing below the floor gets promoted to make up the numbers. The cap counts source-kind labels, so those labels have to be consistent for the rule to work.

## The model's score doesn't get the last word

The floor is where the model's opinion of a story stops mattering. A candidate can carry a high model score and still fail, because the floor reads only what its evidence actually contains: points and comment counts from Hacker News or Reddit, stars and forks from GitHub. Where those engagement counts are available, discussion posts need more than two points and at least one comment, while a GitHub candidate fails when both its stars and forks are two or fewer. A finding that amounts to nothing having changed can't lead an issue whatever its score. The floor was wrong in the other direction too. Using Hacker News's early vote count as a veto had been throwing away first-party technical articles, so when the system has captured the external article behind a submission, the candidate is treated as that article. Its vote count becomes a note about how it was discovered, and it moves on to the substance checks, where a resolved link with no captured substance still fails.

Duplicates are a subtler way to fool yourself, because they look like corroboration. A card is one capture rather than one story, so the Hacker News discussion, the primary article and an RSS reference to the same event are meant to strengthen one dossier and take one slot. Engagement belongs to the underlying post, which means that fetching the same one-point item through two configured origins doesn't turn it into independently corroborated news. Evidence-origin keys collapse captures back to the article or URL behind them, and where older rows carry no keys, the code keeps only the stronger proven count rather than turning unknown overlap into invented independence. Repositories needed an identity of their own, because one project can show up under several addresses. With invented names:

```text
acme/forge                  -> acme/forge
acme/forge/tree/main/docs   -> acme/forge
acme/forge-staging-2        -> acme/forge
acme/forge-staging          -> acme/forge-staging
```

Sub-paths and numbered staging or mirror copies fold into the parent, while an unnumbered suffix stays separate because it might be a real project of its own. A numbered staging copy need not represent a second development worth another slot.

Grouping by event keys, repository identity and text overlap can merge too much or too little. When repository-related candidates are merged, their evidence-origin keys merge too, so two candidates describing the same repository can come out with a higher card count than the facts behind them deserve. Grouping saves a slot without necessarily solving the thin-evidence problem.

Survivors are then picked greedily, by score, then by whether they corroborate a story already chosen, then by card count, recency and a stable ID, with the corroboration term recomputed after each pick. Corroboration here means sharing at least two content terms with a selected story, which is a reasonable hint that you have found a second angle and no evidence at all that either angle is true. Every decision, including tiebreaks, runners-up and demotions, is recorded, so a short issue can explain itself.

## Writing less when there is less

Selection happens before any model is asked to write, which gives a quiet morning a real outcome. If nothing survives, no composition calls are made and the issue records its model version as "none". A story that survives on thin evidence gets a smaller budget: one un-researched source card becomes a case of two to four sentences, with short caps on the headline, context and so-what, on the order of 90 words for an opening and 150 for a story. That is a prompt constraint: it limits what the model is asked to write, without guaranteeing that the result will be proportionate to its evidence.

Composition also produced the failure I find most instructive, because a gate and a prompt were working against each other. The persona text going into the prompt contained owner and system terms that the output gate forbids. The model repeated them in reader copy, the gate rejected those stories, and the pipeline backfilled from further down the ranking, reaching as deep as rank 33 for replacements. Each rejection was correct on its own terms, and together they pushed the issue toward candidates the ranking had already judged much weaker. I worked with a coding agent to redact the persona before it entered the prompt and make the prompt and the gate share one list of forbidden terms.

## What the audit can and can't do

After composition, a deterministic audit runs over the exact prose a reader would receive. A finding it can attribute to one story drops that story and keeps the issue, and a finding no single story owns blocks the issue. It looks for figures that appear nowhere in the cited evidence, allowing no derived arithmetic, with single digits exempt. It looks for leaked internal identifiers, placeholders and process language. It looks for a lead asserting an event, trend or rate when the evidence only describes a repository's landing page, after stripping capture bookkeeping so that a note of when something was observed doesn't count as something happening. And it looks for cross-story relationships the evidence doesn't carry, by stemming the content terms in a sentence that claims a connection and counting those it can't find. One unfamiliar word is paraphrase, and two or more is treated as an invented relation. The real defect sentence behind that threshold carried three: "abstraction", "backend" and "tool".

All of these checks are lexical. They tokenise, apply a simple stemmer and compare strings, so they find identifiable defects and prove nothing about what remains. A sentence can pass every one of them and be false, or dull.

An issue's length now carries information. A short issue means the morning was thin, that one source kind dominated it, or that stories failed their checks, and the pipeline knows which. A padded issue would have hidden that, and a fluent model would have been delighted to do the padding. Whether the stories that survive are worth reading remains a judgment the code doesn't attempt.
