Stage 4 of the Ardur AI content pipeline. Turns each Top-10 topic and its 20–30 clustered sources into one original, copyright-safe article, rendered in-app with no navigation away.
Schema:
ardur-content-pipeline/v1 · License: MIT · Node ≥ 22
Every 6 hours, the upstream engines decide what is worth reading. This engine decides how it reads:
aggregator ─► (NewsItem[], TopicCluster[]) ─► ranking-engine ─► RankedItem[]
─► top10-engine (every 6h) ─► Top10Entry[] ─► article-synthesizer ─► SynthesizedArticle[]
─► ardur.ai in-app render
For each Top10Entry, the synthesizer:
- Resolves the entry's cluster members (20–30 sources) from the
AggregationArtifact. - Plans the weave — dedups and orders sources (primary/paper first), caps the reference list.
- Generates original prose per section via a cost-guarded, pluggable AI provider (deterministic → Ollama → OpenAI), with a deterministic fallback that is itself a complete, publishable article.
- Assembles the prose into the in-app
ArticleBlock[]render model. - Proves provenance — every factual claim is mapped to the sources that support it; ungrounded claims are dropped.
- Gates on copyright + renderability — original text only, quotes < 25 words with attribution, canonical links for every source, no reproduced bodies. Anything that fails the gate degrades to a stricter deterministic article and records a warning. It never aborts the cycle.
The output is an ArticleArtifact — one SynthesizedArticle per Top-10 entry —
that the ardur.ai app renders directly.
This repo is a design spec + scaffold. The synthesis logic is intentionally not implemented. See
docs/spec.mdfor the full design andARCHITECTURE.mdfor the pipeline-wide contract.
| Guarantee | How it is enforced | Module |
|---|---|---|
| Copyright-safe | Original text only; quotes < 25 words + attribution; canonical link per source; verbatim-overlap + credential screens; never reproduce article bodies. Fails closed. | src/copyright.ts |
| Cost-guarded AI | Auto-detected precedence: Ollama Cloud (OLLAMA_API_KEY set) → local Ollama (OLLAMA_HOST set) → deterministic. ARDUR_AI_MAX_GENERATIONS budget + per-call timeout; any failure falls back to deterministic. CI is always deterministic (zero cost, no network). |
src/provider.ts |
| Provenance per claim | Every factual claim is tied to its supporting sources; ungrounded claims never ship. | src/provenance.ts |
| Privacy | No PII in URLs or logs; tracking params stripped; metric/log keys screened against FORBIDDEN_METRIC_KEY_FRAGMENTS. |
src/privacy.ts |
| In-app render | Typed ArticleBlock[], source trail kept separate from prose, no external navigation. |
src/render.ts |
| House voice | "GenZ-but-professional" — engaging, plain-language, fully sourced, never dry newswire or hype. Same voice on the LLM and budget=0 paths. | src/style.ts · docs/voice.md |
npm install
npm run typecheck # tsc --noEmit
npm test # node --test (deterministic, zero network)
npm run build # tsc -> dist/
# Deterministic (offline, zero cost):
ARDUR_AI_PROVIDER=deterministic npm run synthesize \
--in data/runtime/combined.json \
> data/runtime/articles.json
# Ollama Cloud (GenZ-but-professional AI writer — primary):
export OLLAMA_API_KEY=$(security find-generic-password -s "ollama-api-key" -w)
# Optional model override (default: gpt-oss:120b):
export OLLAMA_MODEL=gpt-oss:120b
npm run synthesize --in data/runtime/combined.json > data/runtime/articles.jsonThe API key is loaded from the environment at runtime and never committed. The recommended pattern is to store it in the macOS keychain:
# Store once:
security add-generic-password -s "ollama-api-key" -a "$USER" -w "<your-key>"
# Load per session (add to your shell profile or CI secrets):
export OLLAMA_API_KEY=$(security find-generic-password -s "ollama-api-key" -w)In CI, inject OLLAMA_API_KEY as a repository secret. The test suite mocks all
network calls — no real API calls are made in CI regardless of whether the key
is present.
| Condition | Selected provider | Articles published? |
|---|---|---|
ARDUR_AI_ENABLED=0 or ARDUR_AI_PROVIDER=deterministic |
Deterministic | No (held) |
OLLAMA_API_KEY set |
Ollama Cloud (gpt-oss:120b) |
Yes (if grounded) |
OLLAMA_HOST set (no cloud key) |
Local Ollama | Yes (if grounded) |
| Neither set | Deterministic | No (held) |
ARDUR_AI_ENABLED=0 and ARDUR_AI_PROVIDER=deterministic always win — no
network calls are made even if a key is present.
Configuration lives in .env.example — provider, kill switch,
generation budget, timeout, and optional Ollama/OpenAI settings.
import { runSynthesis } from '@ardurai/article-synthesizer';
import type { Top10Artifact, AggregationArtifact, ArticleArtifact } from '@ardurai/article-synthesizer/contracts';
const articles: ArticleArtifact = await runSynthesis({
top10, // Top10Artifact — what to write about
aggregation, // AggregationArtifact — the sources to weave (same cycle.id)
});runSynthesis returns one SynthesizedArticle per Top10Entry, each carrying
body: ArticleBlock[], references, provenance, ai (provider meta), and a
legalNote. See src/contracts.ts (the shared, vendored
contract — identical in all four repos).
This engine extracts and generalizes working code on
ardur.ai main (the "Hermes" content
engine): src/lib/aiProvider.mjs (generateSignalBrief), the digest assembly in
scripts/build-news-digests.mjs, the privacy/metrics policy in
scripts/refresh-article-intelligence.mjs, and the in-app source-trail render in
src/components/ArticleSourceTrail.astro. The promotion is single-paragraph
briefs → full original articles. Migration points are documented in
docs/spec.md §10.
ARCHITECTURE.md Pipeline-wide architecture (mirrored across all 4 repos)
README.md This file
docs/spec.md Full design specification (diagrams, schemas, rules)
docs/voice.md Authoritative voice & style spec (GenZ-but-professional)
src/contracts.ts Shared wire contract (vendored, identical across repos)
src/index.ts Public entrypoint: runSynthesis()
src/synthesize.ts Cycle orchestration
src/provider.ts Pluggable, cost-guarded AI provider chain
src/assemble.ts Article assembly rules (weave + sections + blocks + voice)
src/style.ts Voice & style config (VOICE_STYLE) wired into assembly
src/copyright.ts Copyright-safety gate
src/provenance.ts Per-claim provenance
src/render.ts In-app render contract
src/privacy.ts PII / URL scrubbing
src/cli.ts CLI runner
src/smoke.test.ts Scaffold smoke tests