This file provides guidance to coding agents working in this repository.
The public repository intentionally starts at the scrubbed root commit
d01649189fde035d962fe7876f7ce16851a62de1. Earlier development history is not
part of the public repository and must remain private.
-
Treat
origin/mainas the canonical base for all new work. -
Never use
--allow-unrelated-histories, force-push an older lineage, or merge, rebase, or pushrefs/archive/private-pre-public-historyinto a public branch. -
The old lineage is retained only as the local non-branch ref
refs/archive/private-pre-public-history. This keeps it out of normal branch and tag pushes. Do not convert it to a branch or tag, publish it, or configure a push refspec for it. -
If an old change is needed, reimplement the smallest reviewed change on a branch created from
origin/main; do not merge or wholesale cherry-pick the private lineage. Inspect it explicitly withgit log refs/archive/private-pre-public-history. -
Before pushing
mainor a release tag, verify that the public root is still unique:test "$(git rev-list --max-parents=0 main)" = "d01649189fde035d962fe7876f7ce16851a62de1" git merge-base --is-ancestor d01649189fde035d962fe7876f7ce16851a62de1 main
A CLI tool for splitting documents into chunks, counting tokens, and distilling documents into fact-preserving rewrites for LLM processing. Built with Go 1.26 and Kong.
# Build
go build -o distill .
go build -o distill-eval ./cmd/distill-eval
# Run tests
go test ./...
# Run a single test
go test -run TestCountStdin .
# Run internal package tests
go test ./internal/...
# Install
go build -o distill . && ln -sf "$(pwd)/distill" ~/go/bin/distill
go build -o distill-eval ./cmd/distill-eval && ln -sf "$(pwd)/distill-eval" ~/go/bin/distill-evalCLI layer (cmd/) — two Kong binaries share the same cmd package and internal packages: distill is wired via Execute, and distill-eval via ExecuteEval (no globals/init()):
distill:
count— Counts estimated tokens (cl100k_base), characters, and lines from file or stdin. Outputs JSON or plain text.chunk— Splits documents into chunks. Three modes:headings,semantic,cramit. Writes numbered.mdfiles +manifest.jsonto an output directory.digest— Distills a document into a fact-preserving article via a four-role pipeline: research (extract atomic facts per chunk) → fuse (merge notes) → write (narrative draft) → edit (polish); also owns the deterministic offlinedigest scoreself-check. Seeinternal/actions/digest.
distill-eval:
eval— Parent foreval judge(LLM-judged precision/recall/F1, ranked INDEX),eval facts(deterministic golden scorer),eval structured(schema-backed JSON scoring), andeval optimize(tune the research prompt against golden fixtures). Seeinternal/actions/eval.grade— Merit and pairwise tournament grading commands; wasdigest grade.models— Model-benchmark harnesses formodels rankings,models code,models trace-go,models comedy, andmodels label.
Chunking engine — provided by the external github.com/dotcommander/reliquary/pipeline/chunking package (NOT in-repo). cmd/chunk.go maps --mode to a reliquary Strategy (headings→HeadingAware, cramit→SentenceBoundary, semantic→SemanticChunker), then applies an optional cl100k_base preflight budget for remote profiles. Local Qwen flows use character budgets because Distill does not own the model-distributed tokenizer.
AI layer (internal/ai/) — wraps github.com/garyblankenship/wormhole for both text completion (Complete, used by digest) and batch embeddings (EmbedBatch, used by semantic chunking). Provider/API-shape fixes belong upstream in Wormhole; distill should select built-in Wormhole providers and must not add custom provider protocol code.
Default models,
--local, and direct-provider routing: the committed config default (internal/config/defaults/config.yaml) uses OpenRouter for non-prefixed remote text models and semantic embeddings. Text model IDs that identify DeepSeek (deepseek/...ordeepseek-*), Z.AI/GLM (z-ai/...,zai/...,glm-*), or Gemini (google/gemini-*,gemini-*) route to direct built-in Wormhole providers using their provider API keys. Remote custom API endpoints are forbidden;--base-url,$DISTILL_BASE_URL, and$DISTILL_EMBEDDING_ENDPOINTare local-only escape hatches with--local. Direct DeepSeek completions sendthinking: disabledby default for efficiency and temperature control; digest also sends a stable hasheduser_idso DeepSeek can isolate/cache related requests without local path disclosure. Local models are strictly opt-in: pass--localto switch AI text commands pluschunk --mode semanticto the on-box Qwen profile (Qwen3.6-35B-A3B-oQ4-fp16-mtp/Qwen3-Embedding-4B-mxfp8athttp://127.0.0.1:8000/v1). Pass--deepseekonly when the whole text-generation/judging command should use the direct DeepSeek profile. If an eval needs a second extraction set, vary the prompt/chunking/temperature, not the model.
DeepSeek QA note: direct DeepSeek has no separate batch-completions endpoint for this workload; use digest's
--concurrencyfor client-side parallelism. A live cache probe on a 6 KB input document showed the second identical request hitting 1280 prompt-cache tokens after a cold first call. Keep existing-user config compatibility by preserving the embedded-default merge before user config overlay. Details and commands:docs/deepseek-provider.md.
Model selection is remote-eval-primary. The per-role model choices (
research_model/fuse_model/write_model/edit_model/judge_modelinconfig.yaml) are derived from public benchmark leaderboards (EQ-Bench, Vectara HHEM, llm-stats Open LLM, Aider Polyglot) cross-referenced against the cost-capped OpenRouter roster (runs/roster.sh). distill's own local evals (eval facts,grademerit/panel tournaments,models comedy,models label, andmodels code) are now a SECONDARY / fallback signal — used only when the public boards don't cover a roster model or can't decide a role. Process of record:docs/remote-eval-crosswalk.md.
Embedding cache (internal/embedcache/) — disk cache wrapping the embedder, keyed by sha256(model‖text) under os.UserCacheDir()/distill/embeddings/, so repeated semantic runs skip re-embedding.
Prompts (internal/prompts/) — research/fuse/write/editor/judge templates, embedded as defaults and materialized to ~/.config/distill/prompts/ on first run (config data, not Go literals).
Manifest (internal/manifest/) — JSON manifest (snake_case keys) written alongside chunk files, tracking source, mode, chunk metadata, and totals. Remote preflight manifests identify cl100k_base; local-model manifests set token_counts_available: false and omit unavailable token totals.
Tokenizer (internal/tokenizer/) — code-owned tokenizer interface backed by the canonical pkoukk/tiktoken-go cl100k_base estimator for count, chunk preflight, and manifest totals. It estimates Claude usage; provider-returned usage is authoritative when available.
FS helper (internal/fsutil/) — WriteFile for atomic temp+fsync+rename durable writes.
Transcript cleaning (internal/transcript/) — deterministic, offline detection and cleaning of YouTube auto-caption transcripts (VTT/SRT). Detect classifies input; Clean drops the WEBVTT header / cue numbers / timing lines, strips inline timestamps, <c> tags, and [bracket] labels, deduplicates the auto-caption triplication / rolling overlap, then reflows fragments into paragraphs (timing-gap or fixed-line-window boundaries). Wired into digest and chunk via cmd/maybeCleanTranscript (auto-clean on detect; --no-clean/--clean override). count stays raw. No LLM, no network, stdlib only.
Polished digest recipe — docs/polished-mode.md is the current high-quality path for long documents: --cascade --merge-facts --outline-from-clusters --cite --repair --check-precision plus gates and --max-sections. Treat --max-sections as a cap for cluster-outline mode, not an equal-size section guarantee; stub clusters merge before cap coalescing and separable oversized clusters split by original fact order.
github.com/dotcommander/reliquary— Chunking engine (pipeline/chunking). Pinned remote dependency (currentlyv0.3.1).github.com/garyblankenship/wormhole— Provider-agnostic LLM SDK (text + embeddings). Pinned remote dep (currentlyv1.24.0); no local checkout required.github.com/pkoukk/tiktoken-go—cl100k_basepreflight token estimates (countcommand, chunking, manifest totals).github.com/alecthomas/kong— CLI framework.
- The
digestpipeline never asks one model call to hold the whole source: it researches atomic facts per chunk, fuses the notes, writes a narrative draft, then edits it — recording per-chunk failures instead of aborting the run. Per-chunk research runs with bounded parallelism (--concurrency, default 4) and a per-call timeout (--timeout, default 300s); a hard LLM error fails the run fast, while an empty response is a recorded soft skip. The editor stage runs by default (skip with--no-edit); the fuse stage is off by default (enable with--fuse— it can time out on large inputs). - Semantic embeddings are cached on disk (keyed by model) so re-running
chunk --mode semantic(e.g. tuning--threshold) skips re-embedding. - Integration tests in
cmd_test.gobuild the binary and run it as a subprocess; package-level tests cover the digest pipeline, embedding cache, and prompts offline (fakes, no network). - Reliquary and Wormhole are pinned remote dependencies; neither requires a sibling checkout for
go build.