From newsroom-os
Config-driven, channel-agnostic editorial-curation capability for an installed newsroom. The agent reads the source pool (filed/linked wire notes) and the user-approved Editorial Plan, clusters same-story signals semantically (Claude reasoning — NO vector search), checks prior coverage against the dossiers' coverage_history (dedup so the newsroom never re-litigates a dropped angle), applies the INSTALLED company-context relevance lens (Tier 1/2/3 read from config, never hardcoded), and writes ONE candidate shortlist artifact for the human editorial checkpoint. Two-layer reading: frontmatter-only for clustering, full content only for shortlisted clusters. Any relevance/priority signal it records is an INTERPRETED INSTRUMENT with a written interpretation — it NEVER composes into a verdict, NEVER auto-selects a story, and NEVER auto-writes a status:/arc_state:/plan_state: (R32/R68/P9). The ranked shortlist is a proposal; a human greenlights at the checkpoint (P8/R25) — the skill never auto-greenlights. Candidates are channel-agnostic (they may target any installed channel). Use when the user asks to "curate", "select topics", "what should we cover this issue", "review candidates", "run curation", "rank the source pool", "build the shortlist", or any request to turn the open source pool into a ranked editorial briefing. Manifest-first: reads MANIFEST.md before globbing zones.
How this skill is triggered — by the user, by Claude, or both
Slash command
/newsroom-os:newsroom-curationThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
The editorial-judgment layer of the newsroom: it turns the open source pool into a
The editorial-judgment layer of the newsroom: it turns the open source pool into a ranked candidate shortlist the editor-in-chief reviews at the human checkpoint. It is a skill, not a CLI and not a scorer — the agent reads the invocation, reads the inputs, and applies editorial judgment grounded in the installation's own relevance lens. All intelligence is the agent reading frontmatter + content and making judgments. There is no scoring formula that auto-selects a story; any number the agent writes is an interpreted instrument with a written interpretation (R32 / R68 — see "The interpreted-instrument rule" below).
This skill operates on an installed newsroom (a project-workspace-contract@2
workspace). Every installation-specific value it needs — the relevance tiers, the
audience, the format slots, the channel — is read from config and the bundled
contracts at runtime. Nothing about any one company is hardcoded (R63
self-containment; the company-context slot-vs-content separation).
Generic by construction. This skill ships zero source-installation content. All worked examples below use the synthetic Meridian Robotics (a fictional industrial-automation vendor) carried by the bundled contracts — mechanics only, never a real installation's tiers, audience, or topic slugs.
Per the substrate I/O spine ([[newsroom-os/skills/newsroom-install/templates/substrate/contracts/io-contracts.md]];
curation conforms to it and operates on the installed copy at runtime), curation is
the Plan → shortlist step that feeds commissioning. Its place in the graph:
wire notes (filed/linked) ─┐
dossiers (coverage_history) ─┼─▶ CURATION ─▶ candidate shortlist ─▶ human checkpoint (P8)
approved Editorial Plan ────┘ (processing/candidates/<id>) │
▼
managing-editor → commissions
| Direction | Artifact | Path | Role |
|---|---|---|---|
| Reads | wire notes | newsroom/wire/<wire-id>.md | the open source pool (`wire_state: filed |
| Reads | dossiers | newsroom/dossiers/<slug>.md | prior-coverage dedup via coverage_history |
| Reads | Editorial Plan | newsroom/editorial-plans/<plan-id>.md | the user-approved per-issue contract (plan_state: approved) |
| Reads | company-context | config/company-context.md | the relevance lens (§5 Tier 1/2/3) + audience (§4) |
| Reads (optional) | story-arcs | newsroom/story-arcs/<slug>.md | candidate arcs the plan pulls in (candidate_story_arcs[]) |
| Writes | cluster notes | processing/candidates/<plan-id>/<cluster-slug>.md | one editorial hub per same-story cluster |
| Writes | candidate shortlist | processing/candidates/<plan-id>/shortlist.md | the ranked editorial briefing → human checkpoint |
processing/**is operational, not a primitive zone. Curation's output lands underprocessing/candidates/— the in-flight editorial workspace, which is OPERATIONAL/sweepable working state, not one of the sevennewsroom/primitive kinds (directory-structure.md). It therefore carries nonewsroom-indexmanifest entry and there is nometa/candidate-index; the candidate zone is never manifest-indexed or graph-audit-regenerated. Story-research reads it directly fromprocessing/candidates/.
The shortlist is consumed by the managing-editor (it turns approved candidates into
commissions) and by story-research/drafting downstream. Curation does not write
commissions, editions, or any *_state: transition.
Operational runbook. Read
references/curation-runbook.mdbefore executing — it carries the step-by-step procedure (the clustering index pattern, two-layer reading, mandatory cluster notes, the prior-coverage dedup against dossiers, and the shortlist write). The shortlist format lives inreferences/candidate-shortlist-format.md. The end-of-run gate lives inchecklists/curation-acceptance.md.
Read MANIFEST.md at the newsroom root FIRST, before any files-first globbing
of the zone folders. The manifest is the routing surface: it lists which wire
notes, dossiers, and editorial-plans exist, with their kind / status / path /
edges. Discover the source pool and the approved plan via the manifest; use the
zone folders (newsroom/wire/, newsroom/dossiers/, newsroom/editorial-plans/)
only to resolve the concrete files the manifest points at. The meta/*-index
caches are derived caches subordinate to the manifest (manifest wins on conflict).
Files-first discovery — globbing a zone to learn what exists — is the refused
anti-pattern.
The Editorial Plan is curation's contract. Its state decides whether curation runs:
| Plan state | Action |
|---|---|
| No plan exists for this slot | Run default curation (no plan directives; record no editorial_plan: linkage). |
Exactly one plan_state: approved | Read it. Apply format_decision, curation_directives (hauptthema, reader_promise, length_target, full_story_target, quick_note_target, must_consider, prefer_angles, avoid_angles, candidate_story_arcs, candidate_quick_notes, defer_or_reserve). Record editorial_plan: + format_decision: in the shortlist frontmatter. Set the plan's plan_state: in_curation so the orchestrator sees curation has consumed it. |
Exactly one plan_state: proposed | STOP. A proposed plan is awaiting the user's approval. Surface to the user: "There's a proposed Editorial Plan for this issue awaiting approval — approve it, abandon it, or proceed without it?" Do not silently bypass the approval gate (P8/R25). |
plan_state: in_curation already | Resume — curation is mid-flight against this plan. |
| `plan_state: superseded | archived` |
Two or more approved plans for one slot | STOP. Invalid state — report the conflict; never pick one heuristically. |
Curation may deviate from an approved plan's directives only by recording the
deviation explicitly in the shortlist frontmatter (plan_deviations[] with a
prose reason) — never silently. Setting plan_state: in_curation is the one plan
field curation writes; it never writes plan_state: approved (that is the user's
signal at the planning gate, owned by the managing-editor flow).
Five moves, in order. Moves 1–4 are the agent reasoning over the pool; move 5 is
writing the proposal that goes to the human checkpoint. None of them transitions a
status:/*_state: autonomously.
Build a clustering index from the LIGHT frontmatter fields only, across every wire note in the pool (and any plan-pulled story-arc):
source_name, source_channel,
signal_strength, signal_reason, entities, tags, linked_dossiers.source_url,
provenance_anchor.Read clustering fields from the whole pool first; pull body content only for
clusters that make the shortlist. This two-layer pattern is what lets curation
scale to a large pool without exhausting context. Do not load every wire body
upfront. (Detail: references/curation-runbook.md §"The clustering index".)
Group wire notes that report the same underlying story across different source
channels. This is editorial judgment, NOT algorithmic similarity — there is no
vector search and no embedding step (permanently out of scope). Cluster by:
shared entities (2+ overlapping + same event), matching/adjacent tags, and the
same core event described differently across channels. Split when topics share an
entity but cover different products/events. One unmatched wire is its own
single-signal cluster. Write one cluster note per cluster (mandatory — see
"Mandatory cluster notes"). Rules + the merge/split test:
references/clustering-guidelines.md.
For each cluster, resolve its linked_dossiers (from the member wires) and read
each dossier's coverage_history — the hand-maintained canonical ledger of
what this topic has already been covered as, and what was deliberately dropped
(with reasons). This is the newsroom's institutional memory; it stops curation
re-proposing an angle the editor already rejected. Classify each cluster's
coverage relationship in prose (the agent's judgment, written as an interpretation,
never a number that gates):
| Judgment | Meaning |
|---|---|
new | no meaningful prior coverage in any linked dossier |
new-angle | same topic, a genuinely new development since last coverage |
arc-progress | the next chapter of an arc already on the dossier |
minor-update | a small detail, not enough on its own to re-cover |
rehash | the same material already covered (or already dropped — cite the coverage_history reason) |
A cluster that matches a coverage_status: dropped entry in coverage_history
must cite that entry's reason in the cluster note, so the human sees why it
was dropped before re-proposing it. No-match clusters are new. When unsure, say
so (low confidence) — that flags the cluster for the human, it does not suppress
it. Detail: references/curation-runbook.md §"Prior-coverage dedup".
Read the relevance tiers from config/company-context.md §5 (tier_1_existential,
tier_2_adjacent, tier_3_background) and the audience from §4. These tiers are
config, never hardcoded — a different installation has entirely different tiers,
and this skill must read them, not assume them. For each cluster, the agent judges:
which installed tier the cluster falls in, how many distinct source channels
carry it (cross-channel frequency = a signal of importance), its novelty (Move 3),
and its fit to the approved plan's directives. The agent then ranks the
clusters into a shortlist and a quick-note tier, and writes its reasoning in
prose for each.
The interpreted-instrument rule (R32 / R68 / P9 — the load-bearing constraint). Any number the agent records (a tier, a cross-channel count, a "priority") is an interpreted instrument: it ships with a one-line written interpretation and is never a verdict. There is NO scoring formula that multiplies signals into a single number and auto-selects the shortlist. Curation never auto-gates a transition and never auto-writes a
status:/arc_state:/plan_state:/dossier_state:. The ranking is the agent's reasoned proposal; the human decides at the checkpoint (Move 5). The only way a number ever gates is an explicit user--thresholdthe user passes — and even then it gates visibility/ordering in the proposal, never an auto-greenlight. (This is a deliberate divergence from prior-art newsletter pipelines that ship afinal_score = source_count × tier × novelty × relevanceauto-ranking formula — that formula is exactly the auto-gate R32/R68 forbid, and this skill replaces it with a written-interpretation lens. Seereferences/relevance-lens-application.md.)
Write the candidate shortlist to processing/candidates/<plan-id>/shortlist.md
(format: references/candidate-shortlist-format.md). It is a proposal — the
editorial-meeting agenda, not a decision. Its frontmatter starts at
status: draft with empty approved_* fields. Present three tiers to the
editor-in-chief for independent approval:
The skill never marks anything approved on its own. Only on an explicit user
signal does it write status: greenlit-adjacent approval into the shortlist's
approved_* fields and the per-cluster notes. No autonomous greenlight (P8/R25).
Every cluster — including single-signal clusters — gets a cluster note at
processing/candidates/<plan-id>/<cluster-slug>.md. The note is the editorial hub
that keeps the chain traceable: it links back to its member wire notes (so the
provenance chain to an external anchor stays walkable, Commandment V) and to the
dossiers it dedup-checked against. The shortlist references the cluster notes;
it never replaces them. Cluster notes are working artifacts for the current issue
(they archive with the issue's editorial trail), not permanent memory — the
dossier is the permanent memory. Schema: references/curation-runbook.md
§"Cluster-note schema".
Curation produces channel-agnostic candidates. A cluster carries an
editorial_fit hint (content kind + which channel format slots it could fit, read
from the channel module's closed format set) but does not assume a newsletter,
a blog, or any specific channel. The same shortlist can feed a commission targeting
any installed channel. Channel-specific format decisions belong to the channel
module and the managing-editor's commissioning step, not to curation.
processing/candidates/<plan-id>/, plus the single plan_state: in_curation
transition on an approved plan it consumes.*_state: transition on a dossier,
story-arc, commission, or edition; never writes plan_state: approved; never
writes status: approvals without an explicit user signal (P8/R25).config/company-context.md §5 lens; if that file is missing or its §5 is a stub,
STOP and tell the user to run the company-context wizard first.This skill's own runtime references — the runbooks under references/ and the
checklist under checklists/ — resolve inside this skill dir, with no read
outside the plugin. Intra-skill ../ traversal (SKILL.md ↔ references/ ↔
checklists/) is legitimate self-contained navigation and is not an R63
violation — the AC1 self-containment grep should not false-flag it.
The schemas + I/O contract curation conforms to are not bundled here. Following
the plugin's bundle-vs-install rule (the plugin ships templates under
newsroom-install; skills reference those templates and read the installed
copies at runtime — no per-skill duplication, exactly as managing-editor and
story-research do), curation cites them by in-plugin wikilink:
[[newsroom-os/skills/newsroom-install/templates/substrate/contracts/io-contracts.md]][[newsroom-os/skills/newsroom-install/templates/substrate/schemas/wire-note.md]] (input)[[newsroom-os/skills/newsroom-install/templates/substrate/schemas/dossier.md]] (input — coverage_history)[[newsroom-os/skills/newsroom-install/templates/substrate/schemas/editorial-plan.md]] (input — curation_directives)[[newsroom-os/skills/newsroom-install/templates/substrate/schemas/story-arc.md]] (optional input — candidate_story_arcs)The wikilinks resolve inside the plugin (R63-clean); the operative copies at curation time are the newsroom's installed ones.
newsroom-curation/
├── SKILL.md # this file
├── references/
│ ├── curation-runbook.md # step-by-step: index, two-layer read, cluster notes, dedup, shortlist write
│ ├── clustering-guidelines.md # how to cluster same-story signals (no vector search)
│ ├── relevance-lens-application.md # reading config §5 tiers; the interpreted-instrument rule (no auto-gate)
│ ├── prior-coverage-dedup.md # the coverage_history memory check
│ └── candidate-shortlist-format.md # the shortlist output format + frontmatter contract
└── checklists/
└── curation-acceptance.md # the end-of-run gate
| Condition | Action |
|---|---|
MANIFEST.md missing | Stop. The newsroom is not installed / not manifest-routable; tell the user to run the installer. |
config/company-context.md missing or §5 stub | Stop. No relevance lens to apply; route to the company-context wizard. |
| Source pool empty (no filed/linked wires) | Stop. Nothing to curate; tell the user to run intake first. |
| Proposed (unapproved) plan present | Stop at Phase 0.5; surface the approval choice to the user. |
| Two approved plans for one slot | Stop. Report the invalid state; do not pick heuristically. |
| Fewer clusters than the plan's full_story_target | Present all available; note the shortfall; consider promoting single-signal clusters — do not invent material. |
| A cluster's wires resolve to no anchor | Flag the provenance gap in the cluster note (Commandment V); surface it — do not silently shortlist an unanchored cluster. |
| Move | Type | What happens |
|---|---|---|
| 0. Manifest + plan gate | Verification | Read MANIFEST first; resolve the approved plan's state |
| 1. Read pool for clustering | File I/O | Two-layer read — light fields across the whole pool |
| 2. Cluster | Agent judgment | Group same-story signals; NO vector search; write cluster notes |
| 3. Prior-coverage dedup | Agent judgment | Read dossier coverage_history; classify novelty in prose |
| 4. Relevance lens + rank | Agent judgment | Read config §5 tiers; rank as interpreted instrument (no auto-gate) |
| 5. Write shortlist | File I/O | Write the proposal; present three tiers to the human |
| —. Approve | Human checkpoint | The user greenlights; curation never auto-greenlights (P8/R25) |
Curation's human-facing output language is config-driven, never hardcoded — the
shortlist, the cluster notes, and the editorial briefing follow the installed
config/company-context.md §7 output_language. Respond to the user in the
conversation's language unless asked otherwise.
output_language (company-context §7) — honor that config declaration;
do not hardcode a language.ue, oe, ae, ss).type, status,
editorial_plan, format_decision, plan_deviations) regardless of content
language.Standard runs end normally: the candidate shortlist + the cluster notes + the three-tier proposal presented to the human checkpoint. Do not fire a generic "anything to add?" prompt.
When a deviation specific to curation occurred during the run, surface it and ask
whether to fold the change back into SKILL.md / the bundled references (the
curation runbook, the clustering guidelines, the relevance-lens application, the
prior-coverage dedup, the shortlist format) before going idle. Curation-specific
triggers:
config/company-context.md
§5 and the operator corrected it (propose folding into the lens-application
guidance, not a hardcoded tier);plan_deviations[] had to be recorded because an approved-plan directive did not
fit the actual pool;coverage_history: dropped match (a re-litigated
angle slipped through).Name the concrete cluster / shortlist and the reference doc the fix would touch. If nothing deviated, end without the prompt.
npx claudepluginhub cmgramse/skill-development --plugin newsroom-osGenerates brand assets: logos (55+ styles, Gemini AI), CIP mockups, HTML slides (Chart.js), banners (22 styles), SVG icons (15 styles), and social media photos. Routes to sub-skills for design tokens and UI styling.