All notable changes to kibble, a mass-knowledge & data platform — consume anything, extract
and structure it, and turn it into clean datasets and knowledge bases at scale (discover →
acquire → extract → understand → curate → retrieve → tune). Entries are grouped by milestone;
the project is pre-1.0 and unversioned.
- Opt-in emergent document topics. With
[classify].enabled = true,buildtags every catalog document with a hierarchicalauto_topic(e.g.Systems › Linux › Memory) +topic_confidence, and writesdata/catalog/topics.json. It reuses the train answer-cluster space (rebalance's in-memory centroids when available — matchingclusters.json— else clustered fresh), LLM-names each cluster hierarchically, and assigns each document the majority topic of its rows (ties → lowest cluster index). Default off → build stays offline anddocuments.jsonlbyte-identical. Fail-soft (missing/ erroring embed backend, no[ask]LLM, or empty centroids → auto-topics skipped, rest of catalog intact); reporting-only (not read byindex/ask); LLM naming is non-deterministic. No new dependency. (Spec A shipped the complete inventory; Spec B makes it self-organizing.)
buildcatalogs every source, not justdata/raw:data/catalog/documents.jsonlandsummary.jsonnow contain one entry per document for all source types — Files/Web/Blog (theDocumenteach already builds) and Dataset/Codebase (one synthesized entry perdoc_id, classified on the rows' non-systemtext). Codebase entries default torole: "code"unlesssource_defaultsor a per-doc override sets one. Reporting-only — the catalog is not read byindex/ask(retrieval runs off[index].sources), so this changes the inventory, not what gets retrieved. No new dependency. (Spec B will add emergent auto-topics.)
kibble trainruns the user-configured[train].command(an argv list, no shell) against thekibble tunepackage: it validates the package (config.yaml+ at least onesprint/<phase>/ train.jsonl), substitutes placeholders ({config}/{out_dir}/{sprint_dir}/{model}→ absolute paths; unknown token = error), then spawns the command withcwddefaulting to[tune].out, streaming its output and propagating its exit code.--dry-runprints the resolved command and runs nothing;-- <extra>appends substituted args. Backend-agnostic — worked examples for unsloth/Kaggle, MLX, and generic commands indocs/TRAIN.md, which also carries the Tune C runbook (upload → train → download adapter →kibble benchthe tuned model). No new dependency.
buildcounts all sources' documents:stats.total_documentsanddocuments_by_sourcenow count distinct input documents acrossdata/rawand every[[source]](Files/Dataset/Codebase/ Web/Blog) — previously they counted only thedata/rawpile, so a[[source]]-only build reported0 docs.clusterguards degenerate tiny topics:[cluster].min_cluster_size(default2) merges any cluster smaller than the threshold into its nearest neighbor after k-means (deterministic;0/1= off), so a too-highkno longer leaves singleton topics (raise the threshold to also fold larger runts — e.g.3merges size-1/2 clusters). The post-merge topic count can be lower thank. No new dependency.
kibble bench --streamstreams the model's output to stderr duringmethod="research"benchmark runs, for live visibility. Research-only (non-research methods are single-shot and unaffected); the scored answer, score,--strictgate, and the printed results table are byte-identical with or without the flag — only live stderr output is added (the streamed turn's latency is measured locally, same as buffered;report.json's wall/latency fields vary run-to-run either way). Opt-in, default off. Reusesllm::chat_turn_stream; no new dependency (#21).
kibble ask --json --streamnow streams the answer as newline-delimited JSON events —{"type":"search",…}for each agentic retrieval,{"type":"token","text":…}as the model generates, then a terminal{"type":"answer",…}(same fields as the buffered--jsonobject) or{"type":"notfound",…}. Lets programmatic consumers stream an agentic RAG answer; unlike text streaming it does not require a TTY.--jsonalone is unchanged (single buffered object). No new dependency (#22).
kibble tuneturns the built dataset into the Unsloth trainer's phased sprint curriculum (sprint/<phase>/train.jsonl) +config.yaml, mapping each[[tune.phase]]to a set of sources.buildnow emits a per-row provenance sidecar (<split>.sources.jsonl;train.jsonlstays messages-only), whichtunereads to split rows by source.config.yamlis written as JSON (valid YAML for the trainer'syaml.safe_load). Closes thebuild → tune → pack → trainloop; training orchestration + adapter-back/eval are deferred follow-ups. No new dependency.docs/TUNE.md.
[cluster].llm_labels = true(opt-in) names each topic via the[ask]LLM from its distinctive terms + a sample (0-old-css→Old vs modern CSS), replacing the term-based labels inclusters.json, thekibble clusterreport, and eval's topic breakdown. Applies everywhereclusters.jsonis written (command, build auto-cluster, rebalance). One LLM call per run; fail-soft — any problem keeps the distinctive-term labels. Default off (zero LLM calls). Centroids/sizes/assignment stay deterministic; only label text varies when on. Reuses the[ask]endpoint; no new dependency (#26).
kibble asknow surfaces the retrieved passages when a (weak local) model answers from the corpus but emits no[n]citation markers — instead of0 source(s), it prints aReferences (retrieved — the model didn't mark citations)block (top 3 by score) and a state-aware footer (0 cited · N retrieved · M search). General-knowledge answers (--chance/ not-found "try anyway") carry a sharpened⚠ Not in your corpus …, verify independently.banner, and the JSON output gains a boundedretrievedarray. Found by dogfooding (#35), where a correct grounded answer showed zero sources with a 4B local model. Display-layer only — retrieval and the agentic loop are unchanged.
[cluster].rebalancenow clusters the curated train in-memory, caps over-represented topics against those centroids, and writes them asdata/clusters.json— so the evalmax_topic_sharegate and the rebalance cap read the same centroids and it converges in a single build (fixes #34, where the old "cap against the prior clusters.json, then re-cluster" path oscillated/diverged). The first-build no-op is gone. With rebalance on,clusters.jsonreflects train-only topics; off, it clusters all splits as before. One embed + one clustering per build; no new dependency. Replaces the Approach-A mechanism from #31.
kibble evaladds amax_topic_sharemetric (stage C of clustering, #25): it assigns each train answer to its nearest topic centroid fromdata/clusters.jsonand gates on the largest topic's share of the training set. Auto-runs whenclusters.json+ an embed backend are present; fail-soft otherwise (missing file, model mismatch, dimension mismatch, or embed error → the metric is skipped andevalstill completes). Counts toward the quality score /overalland is gated by--strictwith no extra flag. Reuses thedata/embeddingscache (embeds raw train answers). Config:[eval.thresholds].max_topic_share(default0.60). No new dependency. Build-side topic-aware rebalancing remains a follow-up.
kibble cluster [--k N] [--json]clusters the built dataset's rows on their answer embeddings (deterministic spherical k-means + k-means++ seeding, no new dep), labels each topic by its distinctive terms, prints a breakdown, and writesdata/clusters.json(centroids + labels). Auto-runs duringkibble build([cluster]config,k=12default; fail-soft — skipped without an embed backend). Centroids are the seam for a futureevaltopic-balance gate (#25); LLM-named labels are a follow-up (#26). Reuses thedata/embeddingscache.docs/CLUSTER.md.- Labels filter a small static stopword list so grammar particles (
the,and,to, …) never dominate a topic label.
- Labels filter a small static stopword list so grammar particles (
kibble asknow streams the answer token-by-token to the terminal as the model generates it ([ask].stream, default on;--stream/--no-stream; active only on a TTY and not--json). During tool rounds it prints stderr activity notes (⋯ searching corpus …). Newllm::chat_turn_stream(SSE viareqwest::Response::chunk()— no new dep) accumulates content + tool-call deltas and returns the same shape aschat_turn, so citations/loop are unchanged. Follow-ups: bench streaming (#21),--jsonstreaming (#22).
kibble ask "<q>" [--web] [--no-web]gives the modelweb_search+fetch_pagetools (sharedwebsearchmodule, SearXNG or DuckDuckGo) alongsidesearch_corpus, so it answers from the corpus first and the live web when needed. Citations tag [local] vs [web] (--jsonsources gain"kind").[web]config (default,base_url,max_results,allow_hosts, fetch caps); web off = corpus-only (unchanged).fetch_pagestays SSRF-guarded- capped. No new crate dep; offline tests via localhost mocks.
benchreuses the samewebsearchmodule.
- capped. No new crate dep; offline tests via localhost mocks.
kibble ask "<q>" [--k N] [--json] [--chance] [--show-context]answers questions grounded in the indexed corpus: it seeds asearch, lets the served LLM call asearch_corpustool up to[ask].max_roundsto dig further, then returns a cited answer ([n]→ source) or, when the corpus doesn't cover it, an interactive not-found flow (provide a source & retry / add detail /--chancegeneral-knowledge fallback, flagged). Reuses the retrieval index + a sharedllmchat helper (extracted frombench). Needs a served LLM ([ask].base_url); retrieval stays fail-soft. No new crate dep; deterministic offline tests via a scripted-chat seam.docs/ASK.md.
kibble index [path]builds a retrieval index over the corpus (data/raw/data/ingest/data/extracted, or an explicit path): chunks each document, embeds chunks via the shareddata/embeddingscache ([understand.embed]), and builds a hand-rolled BM25 inverted index →data/index/{chunks.jsonl,bm25.json,meta.json}.kibble search "<q>" [--k N] [--json]ranks passages by Reciprocal Rank Fusion of semantic cosine + BM25, returning score + provenance. Fail-soft: with no/unreachable embed backend it runs lexical-only (BM25). No new crate dep; deterministic offline tests.docs/RETRIEVE.md.
- New
kibbleembedding backend ([understand.embed], OpenAI-compatible/embeddings) with a content-hash vector store/cache (data/embeddings/, reused acrossbuild→eval) and a hand-rolled SimHash LSH (random hyperplanes → cosine confirm → union-find). No new crate dep. - Semantic dedup in
build([curate].semantic_dedup, opt-in): collapses near-paraphrase answers, keeping the longest; reported asdropped_semantic_duplicates. - Semantic metrics in
eval([eval].semantic, opt-in):semantic_near_duplicate_rate+semantic_leakage_rate, gated (defaults 0.02 / 0.0 at cosine 0.90) into the score and--strict. - Backend:
llama-server --embedding(nomic-embed-text, 768-dim) on localhost. Deterministic, offline tests via a stub embedder.docs/UNDERSTAND.md.
- kibble now loads the nearest
.env(searching the working directory upward) into the process environment at startup, so secrets (OCR_API_KEY,KIBBLE_API_TOKEN,OPENAI_API_KEY, …) no longer need manualsource-ing. Real environment variables always win (a shellexportoverrides.env). Hand-rolled parser (KEY=VALUE,#comments, optionalexportprefix, quote-stripping) — no new crate dep; prints a one-line notice when it loads anything. Ships.env.example;.envis gitignored.
kibble soul build [--soul] [--out]compiles a versionedsoul.toml(persona + system prompt(s)- sampling + tools + optional base template) into serving artifacts:
generation_config.json,system_prompt.txt,tools.json,chat_template.jinja(marker substitution —{{SOUL_DEFAULT_SYSTEM}}→ JSON-escaped prompt), andmanifest.json(sha256 — the registry seed). Pure/deterministic, no new deps; shipssoul.example.toml+docs/SOUL.md. Automates/versions the hand-assembled serving bundle.
- sampling + tools + optional base template) into serving artifacts:
kibble crawl <url> [--depth][--max-pages][--out][--all-hosts]— a Firecrawl-style discovery layer: BFS-crawls a site (same-host default, depth/page limits,robots.txthonored, politeness delay, per-page byte cap, SSRF-guarded, realUser-Agentsent) and writes each page as markdown intodata/ingest/crawl-<host>/forbuild.[crawl]config;docs/CRAWL.md.- Static HTML only (no JS rendering — follow-up). Reuses
web/serve-guard/scraper; no new deps. Verified against live sites.
kibble mcpruns a stdio JSON-RPC 2.0 server (hand-rolled, no MCP SDK dep) exposing the full measure/emit loop as tools an agent/harness (or Claude Code/Desktop) can call:build,eval,bench,soul_build,caps_list,caps_install.initialize/tools/list/tools/call/ping+ notifications + correct error codes (-32601/-32602/-32700); tool failures →isError.- The harness integration surface (the harness itself is a separate project). stdio = local-trust,
no listener; URL sources reuse the fetch SSRF guards; results are JSON.
docs/MCP.mdhas client registration. No new crate deps.
- Adds an agentic benchmark method: the model calls
web_search/fetch_page, the harness executes them (SearXNG JSON when[bench.search].base_urlset, else a DuckDuckGo scrape withuddgdecode; page fetch viaweb::fetch_url_capped+extract_main_text), feeds results back over the OpenAI tools protocol, and the final answer is scored (contains/judge) plus turns / tool-calls / wall-time (the measurable "beat-Perplexity").max_turnscap; per-case faults score 0 (never abort). - Security:
fetch_pageis SSRF-guarded (serve::url_is_allowed+[bench.search].allow_hostsbypass — model-chosen URLs can't reach loopback/private) and byte-capped. No new deps.
kibble bench [--strict]benchmarks a served model (any OpenAI-compatible endpoint) against an extensible suite:[[bench.benchmark]]+ acases.jsonl, scored bycontains/mcq/judge, with per-request latency + tokens/sec. Per-benchmark thresholds → overall PASS + 0–100 score;--strictexits nonzero for CI. Writesbench/report.{json,md}; ships a starter suite +docs/BENCH.md; keys env-only; panic-safe on malformed endpoint responses.- This is the self-improvement loop's steering signal ("did the model get better / fast enough?"). KIBBLE is the tool, not the harness/model. A live web-research / tool-execution ("beat-Perplexity") benchmark is the flagship follow-up.
- Dedup + leakage-safe split:
buildnow collapses rows with the same normalized answer and splits by answer content (split_bucket(answer_key)) instead ofdoc_id, so identical answers can never land in two splits — exact train↔valid/test leakage is impossible by construction.stats.jsongainsdropped_duplicates. On by default;[curate](dedup,leakage_safe_split) to tune. Consolidated the per-source split/stat logic into onecurate_splitstage. - On the real 59,632-row corpus: duplicate 8.5% → 0%, leakage 14.1% → 0% (5,694 dupes dropped).
- Curation filters:
buildalso drops malformed (non-SFT-valid), degenerate (empty/echo), and too-short rows ([curate]drop_malformed/drop_degenerate/min_answer_chars, default on/on/20), reported asdropped_filtered. The degeneracy check is clean-aware (matches the post-clean_textcontentevalreads), so a filtered build clears eval'smalformed_rate/degenerate_rate/short_rateby construction (verified: real build → all 0). - Fixed
eval's balance metric to readstats.json's object-shapedsources(was silently null). - Eval near-dup metrics (opt-in
[eval].near_dup): MinHashnear_duplicate_rate+near_leakage_rate, gated, so the quality gate certifies fuzzy dedup/leakage (build fixes, eval certifies). - Opt-in fuzzy near-dup (
[curate].near_dedup, default off): a deterministic MinHash + LSH + union-find pass collapses near-paraphrase/boilerplate answers (char k-grams, threshold 0.85), reported asdropped_near_duplicates. Conservative on real prose (~0.6% dropped, raisesdistinct2); no new deps. evaldistinct2perf: hashes bigrams to u64 instead of storing String pairs — full 53k-row eval went from timing out (>180s) to ~45s.- Next (deferred): source rebalancing; DPO/pretraining formats; the MCP/harness track.
- Dropped the original personal "voice/style" framing. KIBBLE is a general knowledge & data acquisition and curation platform; voice tuning was only the first use-case. Docs reframed.
evalrepurposed from a voice-fidelity scorer into a dataset-quality gate:kibble eval [--strict]reads the builttrain/valid/test.jsonl+stats.json, validates SFT readiness, and scores duplication, train↔valid/test leakage, length, source balance, degeneracy, and diversity →report.{json,md}with a 0–100 score;--strictexits nonzero on FAIL so CI can block a bad dataset. The voice scorer (VoiceProfile/LLM judge) is removed. Dataset-shaping prompt defaults de-voiced. (Real-corpus run caught 14% leakage + 8.5% dupes.)
Skill installation applied to the agent itself: consume a source → gain its skills.
kibble caps install|scan|list|remove— detectSKILL.mdskills in a source (local path or URL; URL reusesfetch::resolve), install them under<root>/skills/<name>/, and maintain<root>/registry.json.[caps].root(default~/.kibble/caps),--force,--project.- Detection prunes nested skills (a skill dir is atomic); frontmatter
name/descriptionwith dir-name fallback; names sanitized. - Install is copy + register only (skill scripts never executed): dests confined under the
skills root, all symlinks skipped (no escape, no recursion), cumulative byte cap, sha256 per
skill, atomic registry write, collision skip-unless-
--force. - Foundation for the rest of the harness layer (MCP exposure of the registry, capability assembly, autonomous build/tune loop + emry).
Turn any acquired artifact into clean text, staged under data/extracted/ for build
(fetch → extract → build). Each backend tries a configured OpenAI-compatible endpoint, then a
local CLI, then errors. Keys from env only; CLIs via arg-vectors with canonicalized input
paths (no shell, no argv-flag injection); inputs size-capped.
- 3.3 — video + office:
ffmpegextracts a video's audio → transcription;pandocconverts office docs → plain text.extractnow covers all artifact kinds (no unsupported gap). - 3.2 — PDF: text-layer extraction via
pdf-extract; scanned/sparse PDFs render pages withpdftoppm→ OCR each → concatenate. Errors (instead of emitting empty) if every page fails. - 3.1 — framework + OCR + transcription:
kibble extract+ artifact detection; image OCR (vision endpoint,tesseractfallback); audio transcription (/audio/transcriptions,whisperfallback); html/text passthrough.
- mega.nz: download + decrypt public file and folder links — hand-rolled MEGA protocol (AES-128 ECB/CBC/CTR, key derivation, node-tree walk) with no heavy crate; offline known-answer tested.
- 3c — file hosts: Dropbox (
dl=1), Google Drive (uc?export), MediaFire (scrape), gofile (guest-token API). - 3b —
kibble serve: axum HTTP API —POST /ingest→ job id,GET /jobs/{id},GET /health. Bearer-token auth (fail-closed), an SSRF guard (blocks loopback/private/link-local incl. IPv4-mapped IPv6 and userinfo tricks;allow_hostsbypass), in-memory job registry. - 3a —
kibble fetch: pluggable handler layer — git clone, HuggingFace dataset, open HTTP directory crawl, direct HTTP file, archive extraction (zip / tar.gz, zip-slip & tar-symlink safe). Streamedmax_bytescap; traversal-safe filenames; proxy-aware.
kibble eval: blended voice-fidelity score — deterministic heuristic profile + a configurable LLM judge (Anthropic or OpenAI-compatible) anchored on real held-out posts; writesreport.json+report.md. Generate-or-consume samples; keys from env only; judge failures non-fatal.kibble pack: package the build output into a Kaggle dataset bundle ([pack]config: splits, includes, metadata) + zip;kaggle/RUNBOOK.mddocumentsbuild → pack → upload → train.- Polish: configurable eval
reference_max; auth header only sent when a key is set (local endpoints need none);refresh = trueupdates a cached git clone viagit pull.
[paths]config:data_root/dataset_dirconfigurable (defaults preserve the layout).- Phase 2.6:
extract_main_textrefinements (<pre>preserved, nested-block de-dupe); host-anchored github detection;Files(.txt/.mddir) andBlog(.html) source types; datasetpreserve_code(bypass the prose cleaner for code-bearing datasets). - Phase 2.5 (a–c): tokio runtime + proxy-aware
netclient +kibble.tomlsource spine; external chat-dataset merge; local codebase scan; git-URL clone; web/URL fetch + main-text extraction. - Phase 2 —
kibble build: raw docs + configured sources → catalog (role/topic) → chunk → deterministic 78/12/10 split (SHA256(doc_id)) →{messages}JSONL +stats.json; code bypasses the prose cleaner, prose is cleaned at write.
- Phase 1 —
kibble clean+kibble ingest: the text-cleaning rules (strip mentions/URLs/hashtags/tickers/subreddits/invites/zero-width/empty-brackets, emails preserved) and local ingest (Twitter GDPR archive, textfiles tree).
Cross-cutting throughout: Rust orchestrator routing to external backends; everything
offline-tested (localhost mocks + stub binaries, no real network/tools in tests); responsible
acquisition (SSRF/size guards). Design specs and implementation plans live in
docs/superpowers/ in the source project.