Skip the AI rewrite entirely for single-source clusters

A cluster of one item still went through synthesizeArticle to be
"lightly rewritten" — the only recent real-world example fabricated
a fake two-outlet merge out of one genuine article (see the
opaque-sourceId attribution fix). There's no actual synthesis to do
with one source, so the rewrite step only added risk (hallucinated
attribution, subtly altered facts) for no benefit.

priorityQueue.ts's runSynthesisCycle now routes a 1-item cluster to
publishDirect instead of publishCluster — same verbatim-text path
already used for youtube/nitter/telegram items and AI-disabled
categories. publishCluster is now only ever called with 2+ items, so
its doc comment and synthesis.ts's system prompt no longer reference
the single-source case.

Verified directly: a 1-item cluster now publishes with the original
body untouched and zero calls to the model, while a 2-item cluster
still goes through the AI merge path unchanged.
This commit is contained in:
Claude
2026-07-27 13:41:11 +00:00
parent 80fc31c844
commit dae6a51db0
3 changed files with 11 additions and 5 deletions
+3 -2
View File
@@ -349,8 +349,9 @@ export async function publishDirect(
/**
* Publishing is always automatic — there's no draft/review state (see schema doc).
* A cluster of size 1 publishes as-is via the same path; synthesizeArticle lightly
* rewrites rather than merges when there's only one source.
* Callers should route a size-1 cluster to publishDirect instead — there's nothing to
* merge, so an LLM rewrite would only add risk (hallucinated attribution, altered
* facts) for no synthesis benefit. See priorityQueue.ts's runSynthesisCycle.
*/
export async function publishCluster(
provider: InferenceProvider,
-2
View File
@@ -37,8 +37,6 @@ const SYSTEM_PROMPT = `You are a neutral news synthesis assistant. Given summari
- Stays neutral and factual, without editorializing
- Is 2-4 short paragraphs
If only one source is provided, lightly rewrite it in your own words rather than merging, and do not attribute it to any outlet other than that single given source.
After the article, on a new line, write exactly "${TAG_DELIMITER}" followed by 2-4 short comma-separated topic/entity tags (e.g. proper nouns, named events) that this article is about. If nothing salient qualifies, leave the tag line empty.`;
export interface SynthesisResult {
+8 -1
View File
@@ -186,7 +186,14 @@ export async function runSynthesisCycle(provider: InferenceProvider, settings: G
// in practice a cluster's items are all near-duplicate coverage of the same
// story, so they'd all match the same event's filter anyway when they match at all.
const eventId = cluster.items.map((i) => claimedEventId(i, activeEvents)).find((id) => id !== null) ?? undefined;
const article = await publishCluster(provider, settings, cluster, { eventId });
// A single-item cluster has nothing to merge — publish the source's own text
// verbatim instead of asking the LLM to "lightly rewrite" it, which only risked
// introducing errors (or fabricated attribution — see synthesis.ts) with no
// actual synthesis to justify the risk.
const article =
cluster.items.length === 1
? await publishDirect(cluster.items[0], settings, { eventId })
: await publishCluster(provider, settings, cluster, { eventId });
contentItemsDb.assignCluster(
cluster.items.map((i) => i.id),
cluster.id