Skip to content

Repository files navigation

ardur-article-synthesizer

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.

CI  Schema: ardur-content-pipeline/v1 · License: MIT · Node ≥ 22


What it does

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:

  1. Resolves the entry's cluster members (20–30 sources) from the AggregationArtifact.
  2. Plans the weave — dedups and orders sources (primary/paper first), caps the reference list.
  3. 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.
  4. Assembles the prose into the in-app ArticleBlock[] render model.
  5. Proves provenance — every factual claim is mapped to the sources that support it; ungrounded claims are dropped.
  6. 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.md for the full design and ARCHITECTURE.md for the pipeline-wide contract.

Baked-in guarantees

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

Quick start

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.json

Ollama Cloud key management

The 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.

Provider precedence

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.

Public API

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).

Relationship to the existing ardur.ai system

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.

Repository layout

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

License

MIT

About

AI synthesis of one original, copyright-safe article per Top-10 topic from clustered sources

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages