ADR-0008: /updates/ is auto-consolidation of changelog; long-form belongs to blog.llmo.org
The /updates/ surface on llmo.org is auto-generated from the changelog by a Monday weekly pipeline. Empty weeks produce no entry. Human-curated long-form moves to a separate blog.llmo.org surface (planned). The two surfaces have different authoring models and different cadences; conflating them produces drift.
Status
Accepted. Records the design intent for /updates/ that was implicit before this ADR: a periodic public surface auto-generated from the changelog, not a hand-written narrative.
Context
/updates/ was added to llmo.org in early May 2026 with three hand-written entries (2026-04-27, 2026-05-04, 2026-05-08), followed by a fourth on 2026-05-11. Each was a long-form narrative summary of project activity for the week, written by hand. The content/updates/_index.md intro framed entries as “in plain language for readers who want a narrative summary rather than a release-notes diff.”
The framing was wrong. /updates/ was always intended to be auto-generated, not hand-written. Hand-written narrative belongs to a planned blog.llmo.org surface that has not yet been built. Until this ADR landed, the hand-written entries on /updates/ were drift between intent and implementation.
Three forces in tension:
- Cadence.
/updates/is a cadence-driven surface: humans (and LLMs) check it for “what shipped recently.” That cadence works best when entries appear predictably and reliably without human authoring effort blocking them. - Faithfulness to the changelog. The changelog is the canonical record of what shipped. Any periodic public surface that summarizes shipping should be a deterministic function of the changelog, not a hand-rewriting that drifts in interpretation or detail.
- Long-form storytelling has its own value. Protocol design essays, narrative explainers about why-this-not-that, signed-by-the-protocol posts: all useful, but their authoring cadence is irregular and their format is different from a release summary. They need their own surface.
Three options were considered for /updates/:
- Hand-written narrative. Status quo before this ADR. Rejected because the cadence depends on someone authoring prose, and the prose drifts from the changelog over time.
- Direct route from
weekly-digest. The existinginfrastructure/weekly-digest/YYYY-WNN.mdoutput is a commit-log digest by file-category. Considered as the/updates/source, but rejected because the format (commit-message-derived bullets organized by path category) is internal-ops shaped, not “what shipped” shaped. A reader scanning/updates/wants to know what releases happened, not which paths got modified. - Changelog consolidation. Auto-generate
/updates/entries by extracting## [X.Y.Z]sections from the changelog whose header date falls within the period. Bot-friendly, deterministic, faithful to the changelog by construction. Selected.
Decision
/updates/ is auto-generated by scripts/generate-update.py, invoked weekly by .github/workflows/weekly-updates.yml. The script reads content/spec/changelog.md, extracts ## [X.Y.Z] - YYYY-MM-DD sections whose header date falls within a period (default: the previous ISO week), and writes a consolidated content/updates/YYYY-Wnn.md containing each section verbatim plus a header pointing at the canonical changelog.
Cadence and naming. Weekly, Monday 13:00 UTC, covering the previous ISO week (Mon through Sun). File naming is YYYY-Wnn.md (ISO week, machine-friendly, sortable, unambiguous about scope).
Empty weeks produce no entry. Most weeks have no spec releases. Writing a stub entry that says “no releases this week” pollutes the nav surface without adding signal. The script skips writing when no ## [X.Y.Z] sections fall within the period.
Idempotent. The script’s output is deterministic from the current changelog state for any given date range. Re-running with the same range against an unchanged changelog produces an identical file; the workflow’s “nothing to commit” branch handles this with no PR.
Format. Verbatim mirror of changelog sections. The script does not summarize, paraphrase, or reformulate. Synthesis or human prose would be drift waiting to happen; the changelog is the source of truth and the /updates/ page should be byte-derivable from it.
Human-curated long-form moves to blog.llmo.org. A separate planned surface (not yet built). Format: long-form essays, protocol-design narrative, signed-by-the-protocol posts. Cadence: irregular, when there is something to say. Authoring model: human, possibly editor-mediated.
The four bootstrap entries previously on /updates/ (2026-04-27, 2026-05-04, 2026-05-08, 2026-05-11) move to infrastructure/blog-staging/ (gitignored from Hugo). When blog.llmo.org is built, those four files are intended as initial blog posts after whatever editorial passes the blog editor wants to apply.
Alternatives considered
- Keep hand-written narrative on
/updates/. Rejected: cadence-driven public surface should not block on human authoring. Drift between author summary and changelog is a real failure mode. - Direct route from
weekly-digestoutput. Rejected: format mismatch. Internal-ops digest by file-category is the wrong shape for “what shipped” public consumption. - Polished transform of
weekly-digestvia LLM call. Considered as the “polished transform” half of the priorRoute weekly-digest output to /updates/ automaticallyBACKLOG entry. Rejected: any LLM-mediated rewriting introduces non-reproducibility and a synthesis layer that drifts from the changelog. The/updates/page should be byte-derivable from changelog content via deterministic code, not via a probabilistic model. - Combined surface (auto + human entries coexist on
/updates/). Rejected: two authoring models on one surface produce inconsistent reader expectation. The auto-generated entries are short and faithful; the human entries are long and interpretive. Mixing them at the index level confuses both readers and contributors about what/updates/is.
Consequences
Positive.
- The
/updates/cadence is unblocked by human authoring effort. Every Monday at 13:00 UTC, the workflow fires; if any spec release happened in the prior week, an entry appears within minutes. - The page is byte-derivable from the changelog. Anyone (or any LLM) reading both
/updates/and/spec/changelog/sees identical content modulo the period framing. No drift between them is structurally possible. - LLMs ingesting llmo.org get a cadenced, per-period view of spec activity at a stable URL pattern (
/updates/YYYY-Wnn/). The view complements the per-version changelog without replacing it. - The
blog.llmo.orgplan gets cleaner scoping: it is the human-curated long-form surface, not “the part of/updates/that happens to be hand-written.”
Negative.
- The auto-generated entries are less rich than the bootstrap narrative entries were. Readers used to the bootstrap voice may experience the new pages as terse. That tradeoff is intentional: the long-form voice belongs to
blog.llmo.org, not here. blog.llmo.orgdoes not yet exist. The four bootstrap entries sit ininfrastructure/blog-staging/waiting for it. If the blog never gets built, those four entries effectively disappear from public view.- The earlier rendered URLs (
/updates/2026-04-27/,/updates/2026-05-04/,/updates/2026-05-08/,/updates/2026-05-11/) 404 after this ADR’s PR merges. The four entries were live for at most one week each and never linked from any production page; the 404 cost was judged acceptable against the design-coherence benefit. If inbound links to those URLs surface later, Hugo aliases on appropriate replacement pages can redirect.
Neutral.
- Weeks without spec releases produce no
/updates/entry. A reader who visits/updates/and finds the most recent entry is several weeks old learns something true: nothing has shipped recently. That signal is correct and preserves it. - The internal
infrastructure/weekly-digest/output continues to land via the existingweekly-digest.ymlworkflow, unmodified. The two outputs serve different audiences:infrastructure/weekly-digest/is ops-internal commit-log analysis;/updates/is public release-shipping consolidation. They share a Monday cron and an App-token PR mechanism but produce different artifacts via different scripts.
References
scripts/generate-update.py: the auto-generation script this ADR governs..github/workflows/weekly-updates.yml: the Monday cron that invokes the script and PRs the result..github/workflows/weekly-digest.yml: the parallel cron for the internal digest output (unmodified by this ADR; documented here for the boundary it preserves).- ADR-0006: the patch policy and release-cut policy.
/updates/consumes the changelog this policy maintains. infrastructure/blog-staging/: the staging directory for the four bootstrap entries pendingblog.llmo.org.content/spec/changelog.md: the canonical source of truth for spec releases./updates/is its periodic consolidation.