Section Files Stop Being Rewritten at Assembly — Semantic Citation IDs Survive
Citation assembly was rewriting section files. Section files now stay the durable source of truth and keep their semantic IDs.
improve(citation-assembly): preserve semantic citation IDs at assembly
docs(readme): add DataroomHQ + GPGPT to Similar Services
new(gated-publishers): add Deloitte (deloitte.com + www2.deloitte.com/insights)
The 2026-05-26 assembler hardening fixed three bugs but left a fourth
behavior in place that mattered just as much: citation_assembly was
rewriting section files in place with run-dependent numeric IDs.
Section files were no longer durable — running the assembler twice on
the same memo would orphan any citation whose definition lived in
1-research/ rather than in the section itself, because the section
file's IDs had been mutated to numbers the foundational citations no
longer matched.
* src/agents/citation_assembly.py — renumber_citations() now returns
the old→new ID map instead of writing it back into the section
files. The map is applied in memory only when the final draft is
concatenated. Section files stay as the writer wrote them, keeping
their semantic IDs (`[^chroma-site]`, `[^github-2018]`, etc.) and
remaining safe to re-assemble against a different 1-research/ set.
* README.md — Similar Services section adds DataroomHQ and GPGPT.
Both are commercial vendors in the private-markets data-room +
memo-generation space. The orchestrator is open source and the
CLI-and-Python surface won't fit every reader; surfacing the two
most relevant commercial alternatives at the README level gives
commercial-intent traffic a direct exit.
* src/validation/gated_publishers.yaml — adds Deloitte as an
analyst-category gated publisher (deloitte.com and
www2.deloitte.com/insights). Joins the existing analyst-firm
entries so research that surfaces Deloitte URLs gets routed
through the same gated-source handling as the other consultancy
publishers.
No agent prompts changed. No outline YAML changed. The assembler's
external interface is unchanged; only the side-effect on section
files moved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Changelog — 2026-05-28_01
The behavior change that mattered
citation_assembly.py previously did two things at once: it walked all
section files, computed a global numbering, and (this is the part that
caused pain) wrote that numbering back into the section files. After
the first assembler run, the section file's citations were no longer
the semantic IDs the writer had produced — they were the numeric IDs
that run had assigned. The next assembler run would then see the
numbers, renumber them again against a possibly-different set of
foundational citations from 1-research/, and silently orphan any
citation whose definition had moved.
The fix is structural, not algorithmic. renumber_citations() still
computes the old→new mapping, but it returns the mapping instead of
writing it back. The mapping is applied in memory only when the final
draft is concatenated. Section files stay as the writer wrote them —
the semantic IDs survive, the section file remains safe to re-assemble
against a different 1-research/ set, and the assembler stops having
a side effect on the source of truth.
Why this matters for codified-source mode
The codified-source mode (curated Sources.md per memo) was always
going to compound this bug. Foundational citations defined in
1-research/ are exactly the codified-source case — the writer
references them by semantic ID and expects them to exist in the
research files, not in the section. Once the section file's IDs got
renumbered, the codified definitions stopped resolving. Fixing the
side effect was a prerequisite for codified mode to be production-safe.
Similar Services additions
The orchestrator's README has carried a Similar Services section for a while as a courtesy to readers who land here looking for a hosted product rather than a CLI to run themselves. DataroomHQ and GPGPT are the two most relevant commercial vendors in the private-markets + memo-generation space; adding them to the README means commercial traffic has somewhere to go without having to fish around for alternatives. The fuller list still lives on memopop-site.
Gated publishers: Deloitte
Deloitte joins the existing analyst-firm entries in
gated_publishers.yaml. The orchestrator already routes McKinsey,
Bain, BCG, and a few others through the gated-source handling for
the consultancy-publisher category. Deloitte was an obvious omission
that surfaced when a recent research run pulled
www2.deloitte.com/insights URLs that the gated-source classifier
ignored.
What didn't change
No agent prompts.
No outline YAML or template.
The assembler's external interface (function signature, where it reads from, what it writes to disk for the final draft) is unchanged. Only the in-place rewriting of section files is gone.
What's next
Re-run a previously-flaky memo through the assembler and confirm re-assembly produces identical output (idempotence test).
Codified-source mode (Sources.md per memo) can move forward now that the section-file mutation is gone — the orphaning failure mode it would have hit is closed.
References
src/agents/citation_assembly.py— the changed filesrc/validation/gated_publishers.yaml— Deloitte entryREADME.md— Similar Services additionschangelog/2026-05-26_01.md— the assembler hardening this entry sits on top of
