As-built completion record for phase 3 of the knowledgebase: on-demand consolidated repair-article generation from a cross-source cluster via claude -p --model opus, in a draft → human review → publish workflow. This is the final phase — the knowledge system is now complete end-to-end (OCR ingest → vectorize → semantic clustering → on-demand cited consolidation). Proven live: two real articles generated and one published to https://knowledgebase.taild4189d.ts.net/consolidated/engine-cooling/.

Design in 2026-07-25-kb-consolidated-gen-phase3-design, task-by-task plan in 2026-07-25-kb-consolidated-gen-phase3-plan — this note is the completion record and hard-won knowledge, not a restatement of either. Builds directly on 2026-07-24-kb-semantics-phase2-complete (whose clusters.json is the retrieval bundle) and reads the 2026-07-24-knowledgebase library.

For Agents

Code: /home/levander/knowledgebase/ on telep-mainframe (runs as levander). New modules: genprompt.py (pure — builds the never-invent prompt), speccheck.py (pure — number-token spec check), gen.py (background job: cluster → gather → claude -p → draft → speccheck), kbgen.py (CLI) + tests. Co-located in knowledgebase/, NOT kb-vectors/ (the design’s proposed home) — deliberately, to avoid cross-venv imports; the generator reuses the KB app’s build_site, slugs.safe_slug, and docs/ access directly. Routes added to the existing knowledgebase Flask app (app.py): POST /generate/<cluster_id>, GET /drafts, GET /drafts/<slug>, POST /drafts/<slug>/publish, POST /drafts/<slug>/discard, GET /api/genjobs/<id>. Plus a “Generál” button per cluster on /semantics. Generator: claude -p --model opus — Claude Code natively installed at /home/levander/.local/bin/claude, logged in as levander (subscription). --model opus is explicit (never Sonnet, per house rule); empty/error output → job fails loudly, nothing published. Artifacts: drafts in drafts/<slug>/ (draft.md, meta.json, images); published articles in docs/consolidated/<slug>/ (browsable, vectorizable on next kbvec index). NO new always-on service — routes on the already-running KB app; single serialized background worker like the OCR ingest.

The flow

/semantics cluster  ──"Generál"──▶  POST /generate/<id>   (enqueue, returns job id)
                                          │
                    single serialized background worker:
                      gather cluster member section .md + images
                      → rewrite image refs to SOURCE-UNIQUE names (see gotcha ⚠)
                      → build never-invent prompt (genprompt.py)
                      → claude -p --model opus  (prompt on stdin)
                      → write DRAFT: drafts/<slug>/ {draft.md, meta.json, images}
                      → run speccheck (advisory number-token flags → meta.json)
                                          │
GET /drafts/<slug>  ◀── AI-provenance banner + speccheck warnings + article
                          │
              ┌───────────┴───────────┐
   Közzététel (publish)          Elvetés (discard)
   move → docs/consolidated/<slug>/     delete drafts/<slug>/
   → build_site() (atomic swap)
   → browsable + vectorizable

What was built

  • genprompt.py (pure): builds the claude -p prompt from a cluster + its member section markdown. Each source delimited/labelled (=== SOURCE [<manual_id>] — <heading> ===); enforces the cite-everything / never-invent contract (see the tip).
  • speccheck.py (pure): extracts number tokens from the draft and flags any not string-present in the concatenated source text — an advisory flag surfaced in the review UI, never a publish gate.
  • gen.py (background job): loads a cluster from clusters.json, gathers member .md + images, rewrites image refs to source-unique names, runs claude -p --model opus, writes the draft + meta.json (cluster id, sources, state, speccheck warnings, created), runs speccheck. Single serialized worker (idempotent start_worker; the box is shared/power-flaky).
  • kbgen.py (CLI): cluster <id> (generate a draft), list-drafts, publish <slug>, discard <slug>.
  • KB app routes + “Generál” button on /semantics — draft rendered with a prominent AI-provenance banner, the speccheck warnings, and Közzététel / Elvetés buttons; publish moves the draft into docs/consolidated/<slug>/ and rebuilds the site atomically; discard deletes the draft dir.
  • Claude Code on the box: native installer to ~/.local/bin, claude login (interactive, subscription), then headless claude -p --model opus with the prompt on stdin.

Result (the real proof — two live articles)

Two real cross-source clusters were generated and read against their sources:

  • Engine-cooling cluster — PUBLISHED LIVE at https://knowledgebase.taild4189d.ts.net/consolidated/engine-cooling/. 3 real source tags (chevy-tracker/geo-tracker-repair, suzuki-vitara/5door-supplement, suzuki-vitara/workshop-1988-1998), 49 images rendering, AI-provenance header present.
  • Parking-brake cluster — generated as a draft (still pending review). 40 real source citations; specs verbatim-traceable to their sources (e.g. 20 to 25 kg (44 to 55 lbs), 7 to 9 notches); nothing invented; it honestly noted a source that contributed nothing to the topic, and flagged a genuine both-ways disagreement between sources with a ⚠ marker rather than picking one.

Pending review artifact

The parking-brake draft is still unreviewed — it lives in drafts/<slug>/ and is a normal review artifact the user may publish (Közzététel) or discard (Elvetés) from /drafts/<slug>. It is not lost work; it’s the human gate doing its job.

Read-only discipline held: the source manuals and Qdrant were untouched; the only additions to the live KB were under docs/consolidated/.

Key design decisions

  • Cite-everything / never-invent trust model — every spec/step/number/torque/wire-color tagged with its source [manual_id]; numbers/units reproduced verbatim (no rounding/converting); conflicts shown both-ways with a ⚠ warning, never resolved by the model.
  • On-demand, one cluster at a time — no batch, no auto-generation.
  • Draft → human review → publish, never auto-publish — publish is the only step that changes the live KB, and it’s human-gated + atomic (reuses build_site’s existing atomic swap).
  • --model opus, explicit — never Sonnet; unavailable/empty/error → fail loudly, nothing published.
  • speccheck is advisory, ON TOP OF human review — an automated number-token spec-check that flags, but never blocks; the human is the backstop.
  • Published articles carry an AI-provenance banner and live in a separate consolidated/ folder — they are never presented as a manual.
  • Modules co-located in knowledgebase/ (not kb-vectors/) — avoids cross-venv imports; reuses the KB app’s helpers directly. (This is the one deliberate deviation from the design doc, which proposed kb-vectors/ + generate.py; the built file is gen.py in knowledgebase/.)

Hard-won gotchas

Image basename collision across manuals — every OCR'd manual reuses names like _page_62_Figure_16.jpeg

A consolidated article pulls images from MULTIPLE manuals, but every marker-OCR’d manual names its images the same way (_page_62_Figure_16.jpeg, etc.). Copying them naively means a draft would silently get the WRONG manual’s diagram — a real accuracy failure for a repair document. Fix: before generation, rewrite each source’s image refs to SOURCE-UNIQUE names (safe_slug(manual_id) + "__" + basename) and build an image_map (unique-name → real file). The draft carries the unique refs, so copies resolve 1:1 to the correct source image. Verified: 4 refs → 4 correct copies, all prefixed.

The never-invent contract + claude -p on the box

Prompt contract (genprompt.py): use ONLY facts in the provided sources; tag every spec/step/number with [<manual_id>]; reproduce numbers/units/torque/wire-colors verbatim (never round/convert/infer); where sources disagree, present both with tags + a ⚠ (don’t pick one); carry through source images with their tags; if the sources don’t cover something, omit it — don’t fill gaps. Output is clean markdown, # <label>, a short source-naming intro, then the consolidated procedure. Claude on the box: native installer → ~/.local/bin/claude (add to PATH), claude login (interactive, subscription), then headless claude -p --model opus with the prompt on stdin works. It shares the box with Frigate / OCR / vectorize → serialize, one at a time.

  • speccheck “false flags” are mostly REAL verbatim-deviations. The number-token check flags any draft number not string-present in the sources. These fire mainly when the model reformats a number (1,0001000, 4040.0) — which the never-invent prompt forbids — so a “false flag” is usually a real deviation worth surfacing. Keep it advisory (never a publish gate); human review is the backstop. It also flags year-digits from citation slugs (e.g. 1988/1998 from [...workshop-1988-1998]) — harmless. Deferred minor: strip thousands-commas + ignore digits inside [citation] brackets.
  • start_worker must be idempotent (guard with a module flag) or a second call spawns a concurrent worker, breaking the one-at-a-time serialization that the power-flaky box depends on.
  • publish() safety: safe_slug guarantees a non-empty single path component (resolve_within alone returns base for empty parts), so a degenerate label can’t make rmtree wipe the whole consolidated/ folder; build_site’s atomic swap keeps the served site consistent even if a publish build fails.

Ops / failure modes

  • Single serialized background worker (shares the box with Frigate/OCR/vector); a job lost to a reboot is just re-run (idempotent by slug — re-generating overwrites the draft).
  • claude failure / empty output → job failed with the stderr tail; no draft published.
  • speccheck warnings never block; they inform the human gate.
  • Publish is the only step that mutates the live KB; human-gated + atomic build swap.
  • Read-only over the source manuals + Qdrant; only writes drafts/ and (on publish) docs/consolidated/.
  • Runs on telep-mainframedown whenever the box is (unstable mains — see 2026-07-23-mains-power-shutdowns).

Roadmap position — full system COMPLETE

The knowledge system is now complete end-to-end: OCR ingest → vectorize → semantic clustering → on-demand cited consolidation. Possible future work: an optional claude -p relabel of clusters (behind the make_label seam from phase 2); diagram understanding (hybrid CV path-tracing + a vision model, accuracy-guarded) as a later phase; and the deferred speccheck normalization minors above.