The Problem
Every knowledge base accumulates contradictions. A new source arrives that disagrees with something already compiled, an updated report revises a market-share figure, two documents describe the same event differently. Under normal conditions these conflicts merge silently and get served to anyone who queries.
The manual fix is conceptually simple: find conflicting pages, review them, decide which source wins, rewrite the content, promote the page back to active. In practice this becomes a recurring burden. You have to track which pages are affected, remember why each was flagged, and repeat the process every time new sources arrive.
Two things make it worse. First, the affected pages keep answering queries while broken — a contradicted page with stale or incorrect information is indistinguishable from a clean one in search results. Second, the effort scales linearly with how many pages are affected.
Synthadoc v1.3.0 addresses both halves: automated detection through the adversarial lint gate, and structured interactive remediation through the contradiction resolver workflow.
Detection: The Adversarial Lint Gate
The adversarial reviewer has been in Synthadoc since v0.5.0. It runs a second, independent LLM pass over each wiki page, specifically tasked with identifying unsupported claims, overstated superlatives, and contestable facts. Warnings land in page frontmatter and surface in lint reports.
Before v1.3.0, that was the end of the story. Warnings were informational. A page with ten adversarial warnings still answered queries at full weight alongside a clean, reviewed page.
The adversarial lint gate changes this. Configure a threshold in config.toml:
[lint]
adversarial_gate_threshold = 2
adversarial_max_per_page = 3
When any active or stale page reaches that warning count during a lint run, it is automatically demoted to contradicted. The transition is recorded in the lifecycle audit trail — "auto-demoted: 2 adversarial warning(s) ≥ gate threshold 2" — and the page is immediately excluded from the BM25 search index. The next query will not see it. No restart required, no polling delay.
New wikis created with synthadoc init have the gate active at threshold 3 out of the box. Existing wikis upgrading from an earlier release are unaffected — if your config.toml has no adversarial_gate_threshold key, the gate is disabled until you add one. Compliance-sensitive environments typically run at 1.
After lint demotes pages, the web UI signals the problem through two simultaneous paths: a persistent hint chip in the sidebar ("⚔ N contradicted → Run resolver"), and a pre-filled suggestion that appears below any assistant reply mentioning contradicted pages. Click either and the contradiction resolver starts.
From the CLI, with no web UI required:
synthadoc workflow run --name contradiction-resolver
synthadoc workflow run --name contradiction-resolver --slug grace-hopper
synthadoc workflow run --name contradiction-resolver --type adversarial
How the Resolver Works
The resolver is an interactive agentic loop. Everything it does requires your approval before anything is written.
Step 1 — Scope and cost estimate. Before any LLM call, the workflow lists the pages in scope and shows the estimated token count and cost. You approve to proceed or cancel.
Step 2 — Diagnosis and proposal. For each page, the agent reads the current content, the adversarial lint_warnings or source-conflict contradiction_note, and the original source file where available. It selects a strategy, generates a rewrite, and shows you the exact before/after diff.
Step 3 — Approval gate. You approve the change, skip this page, or ask for a different strategy. Nothing is written until you say so.
Step 4 — Scoped re-lint. After an approved write, lint runs on only that page — adversarial and contradiction checks only. If the page passes, it is promoted automatically: contradicted → active, with a lifecycle event naming the strategy used.
Step 5 — Strategy rotation on failure. If re-lint fails, the agent diagnoses why and selects a different approach: content rewrite → web ingest for better sourcing → forced source re-ingest → cross-page resolution. Maximum four attempts per page; escalation with a plain-language diagnosis after the cap.
Step 6 — Ground-truth confirmation. After all pages are processed, the workflow runs synthadoc status and appends the live lifecycle counts. The final output is not "we tried to fix three pages" — it is contradicted: 0.
Your Escape Hatch: Content Snapshots
The diff-before-write approval gate is the primary guard. But if you approve a change and later decide the resolver's rewrite missed the editorial nuance you had in mind, content snapshots give you a fallback.
Every lifecycle event — including the contradicted → active transition the resolver writes — captures a frozen copy of the page body at that moment.
From Obsidian: open the Synthadoc plugin → Manage Page Lifecycle → switch to the Content Snapshots tab. Find the page, click View to see the diff against the current file, then Rollback to this version — no terminal required.
From the CLI:
synthadoc lifecycle history grace-hopper
synthadoc lifecycle rollback grace-hopper --index 2 --reason "reverting resolver rewrite"
Either way, rollback saves the current content before overwriting, so the operation is always undoable. After rollback the page returns to contradicted on the next lint run, at which point you can re-run the resolver with a different strategy, edit manually, or archive the page.
Architecture: How the Pieces Connect

Lifecycle management and search index invalidation are a shared service used by both the lint agent and the contradiction resolver. Whichever path transitions a page state, the search corpus updates immediately. There is no eventual consistency window and no restart required.
The resolver is one of six AgenticWorkflow implementations registered with the action agent. They share the same loop machinery — tool dispatch, SSE progress events, confirmation timeout, tool budget — and differ only in their system prompt and the tool set they operate on.
A unified CLI command surfaces all of them:
synthadoc workflow list # see everything available
synthadoc workflow run --name <name> [--slug SLUG] [--type TYPE]
What We Learned
Scoped re-lint was the hardest design decision. Running a full wiki lint pass after each strategy attempt is correct but slow enough to make the workflow feel unresponsive. Scoped re-lint — checking a single slug with adversarial and contradiction passes only — is what makes per-attempt feedback feel interactive. Threading that path through existing lint infrastructure without affecting full-wiki behaviour required care.
The approval gate is load-bearing, not cosmetic. A workflow that applies all changes and then asks whether you wanted that is functionally useless. Showing the diff first and applying only on explicit approval is the feature — it is what separates an agent that acts on your behalf from one that acts instead of you.
System prompts are the policy layer. Strategy selection, iteration cap, escalation criteria, the ground-truth status check at the end — all of these live in the system prompt, not in Python. When we needed to raise the attempt cap and allow strategy reuse on later attempts, four lines of prompt text covered it. No code changed.
What's Next
The pluggable workflow architecture was designed for exactly this kind of extension. Adding a new workflow means implementing three methods on AgenticWorkflow, writing a system prompt, and registering the class. The CLI and web UI routing are inherited automatically — synthadoc workflow list picks it up immediately, and synthadoc workflow run --name <name> runs it without any other plumbing.
The next candidates:
- Orphan page resolver — pages with no inbound wikilinks are a known structural problem. A workflow that proposes index entries and cross-links for each orphan, shows the diff, and applies approved changes follows the same loop directly.
- Broken citation resolver — pages where citation markers point to deleted source lines are detectable at lint time. An agentic loop that re-reads the source, finds the correct line range, and proposes an updated marker is a natural fit.
Neither requires changes to the core. The loop machinery, the confirmation gate, the SSE progress events, and the CLI integration are all shared. Only the system prompt and the tools it can call are new.
Synthadoc v1.3.0 is an open-source LLM knowledge compilation engine that turns raw documents into structured, local-first wikis. A transparent, human-readable alternative to traditional RAG, which can be self-managed and self-improved without the use of any tools.
GitHub
PyPI