Skip to content

Releases: hung12ct/culi

culi v0.8.1

Choose a tag to compare

@hung12ct hung12ct released this 04 Nov 00:11
146fbfa

Culi now remembers the conventions you state, not only the corrections you make.

The gap

Telling an agent "run tests only with make verify, wrap every error with inverr.Wrap, money is int64 cents" used to teach culi nothing. The miner only looked at corrections, rejections, repeats and tool errors, so a session that stated four conventions mined as clean. When a rule was learned, it waited for a second sighting and then only reached prompts that happened to share its words.

What changed

  • Standing instructions ("always", "never", "from now on", "conventions", and Vietnamese equivalents) open a mining window. Up to 4 rules are kept per session.
  • A rule you stated outright (confidence ≥ 0.8 with a quoted line) is your intent, like save_lesson: it confirms at once and is injected at session start for every task in its scope. Lessons inferred from corrections still wait for confirm_at sightings.

Measured

Conventions taught in one session, checked automatically in the next four, Claude Code's built-in auto-memory off: v0.8.0 applied 3 of 11; this release applied every convention in every completed session (8/8 and 11/11 across two runs), at about the same session cost as no memory. Method, limits and the numbers for the alternatives are in docs/comparison.md.

culi v0.8.0 — Bourbon

Choose a tag to compare

@hung12ct hung12ct released this 03 Nov 18:06
36da32a

Culi now installs with one command, says honestly what it delivers, and injects fewer irrelevant cards. The index migrates to schema v6 on first open; the next session start fills the new column, no manual step.

Install

curl -fsSL https://raw.githubusercontent.com/hung12ct/culi/main/install.sh | sh

Prebuilt binaries for macOS, Linux and Windows are attached below; the script checks the SHA-256, installs to ~/.local/bin and runs culi init. culi doctor checks the store, Ollama, the learning backend, failed jobs and the Claude Code/Codex wiring, with a fix per problem. culi uninstall removes only culi's entries.

Fewer irrelevant cards

Learned and generated cards carry generic trigger keywords ("config", "quiz", "size"), and a keyword hit used to pin a card even with no other evidence. A machine-written card now has to rank in the top 6 of the keyword or semantic search, and weak tail matches are dropped. Hand-written triggers keep their pin. On a hand-judged replay of real prompts the useful share of injected cards rose from 44% to 60%, and from 27% to 43% on prompts held out from tuning, with about a quarter fewer tokens. culi eval and a fixture suite now measure retrieval, and CI guards it; method and known misses are in docs/evaluation.md.

Honest numbers

  • "Saved ~99%" is gone: it compared against loading the whole store every session. Stats, the statusline and the console show tokens delivered per session instead.
  • Failed learn jobs keep their reason: culi learn --failed, --retry-failed, --clear-failed.

Privacy

<private>…</private> blocks and credential-shaped strings are removed from transcripts before any learning backend sees them.

Also

expand_card takes up to 10 ids and search_context shows each card's token cost.

culi v0.7.0 — Typica

Choose a tag to compare

@hung12ct hung12ct released this 30 Aug 01:16

Culi no longer creates cards that duplicate cards it already has, and the 17 pairs it had already created are retired. No schema change, no migration.

The importer never asked

culi import deduped within one batch and, at apply time, only caught a file byte-identical to its destination. A different slug is a different path, so that check could not fire. Two runs over overlapping repos — Jul 20 (140 cards), Jul 24 (54) — left 17 duplicate pairs, two of them with byte-identical summaries:

cos=1.000 jac=1.00  gopheragent-docs-for-large-features <-> gopheragent-large-feature-docs
cos=1.000 jac=1.00  mandatory-tdd-workflow-phases       <-> mandatory-coding-workflow-phases
cos=0.964 jac=0.81  no-orm-migration-frameworks         <-> no-orm-migrations
cos=0.950 jac=0.65  skill-invocation-mandate            <-> skill-invocation-mandatory

This is the duplication culi exists to kill, reproduced inside culi's own store.

It was not cosmetic

Each copy collects its own share of the injection and feedback signal. Two halves of one rule can both sit at uncertain forever, where the merged card would have earned a verdict:

rules/atomic-commit-staging   4 injections
rules/atomic-commits-rule     1 injection     ← same rule

What changed

The similarity thresholds now live in one package, internal/carddedup, shared by the miner and the importer. A threshold that means one thing in the miner and another in the importer lets a card in one way that the other would have rejected — that is a correctness constant, not a tuning knob worth duplicating.

culi import merge gained a store-backed duplicate check, with two deliberate departures from the miner:

Both arms run and the closer verdict wins, rather than vectors-or-lexical. Cards staged earlier in the same run have no vectors yet and would be invisible to a vector-only pass — and the within-batch twin is exactly one of the cases this exists to catch.

Only an outright duplicate is dropped. A near match still stages, with a note. The importer's input is guidance you wrote by hand; discarding it because it resembles an existing card would lose content you cannot get back (C4). The miner makes the opposite call on the same band because its input is an inferred lesson that re-mines for free.

Everything fails open: a dead Ollama, an unopenable index, or a failed check all stage the card and say so.

culi card dupes

culi card dupes            # dry run — list the pairs
culi card dupes --near     # the review band, never retired automatically
culi card dupes --retire   # retire the weaker twin of each pair

Retiring is a +1 line in a git-tracked file — reversible, never a delete.

Two bugs the tests caught before the sweep ran:

Clusters, not pairs. Three phrasings of one rule produce three duplicate edges, and greedy pairwise matching named a card the keeper of one pair and the drop of another. Retire both sides and the rule loses every live copy. Union-find gives one keeper per cluster by construction.

Scope is part of identity. Orientation ranked cards by injections and feedback alone, so a global rule could be retired in favour of a repo-scoped twin — silently dropping coverage in every other repo. Cross-scope matches are demoted to the review band instead. The 17 pairs here all shared a scope, so nothing was lost: luck, not design.

124 cards culi could not retire

One pair refused to collapse, and the reason was a third bug. culiAuthored listed learn, mcp and import — but culi also writes cards with provenance patterns (the branch pattern miner) and gen. That is 124 cards culi wrote itself and then treated as hand-authored, so no path could retire them: not culi review, not the serve console, not a supersedes chain. They could only accumulate, which is most of why patterns dominate the stale list.

The guard itself was right and stays — a hand-authored file must never round-trip through Render. The list was incomplete. A test now enumerates every provenance the codebase writes, so a sixth writer cannot be added without updating it.

Result

duplicate pairs   17 → 0
retired cards     98 → 115
rules             65 → 52
stale            194 → 178

89 near-duplicates remain, almost all branch-derived patterns. culi card dupes --near lists them; those are judgement calls and stay yours.

Upgrading

go install github.com/hung12ct/culi/cmd/culi@v0.7.0
culi index

No migration. culi index is only needed if you retire cards.

culi v0.6.0 — Excelsa II

Choose a tag to compare

@hung12ct hung12ct released this 07 Aug 10:44

Pointer lines are off by default. No schema change, no migration.

The bet the granularity ladder was making

The third rung — ▸ id Title: teaser lines that the model was meant to pull with expand_card — was always a bet. Three weeks of daily use settled it:

expand_card calls, all time:   1
pointer injections:           78,  costing 4,177 tokens  (11.4% of all spend)

The MCP server was connected and working throughout — save_lesson and search_context were both used — so this measures the mechanism, not the plumbing. A pointer only does something if it gets pulled, and in practice it doesn't.

It was also corrupting the feedback signal

Those injections were the sole feed for PenalizeAbandonedPointers. Since expansion effectively never happens, every pointer became a downvote at a ~100% base rate:

negative feedback:  0.62 / day
positive feedback:  0.29 / day     → 2.1x faster

That dragged cards toward noisy based on exposure rather than merit. The effNoisyFloor added in v0.5.1 raised the bar but didn't change the slope — noisy still climbed 4 → 12 in eight days. Removing the emission fixes the token waste and the signal corruption together, at the source.

A default, not a removal

# ~/.culi/config.yaml
pointer_lines: 4   # restore the third rung

The packer test exercises both modes so the ladder can't silently rot. With pointers off, a card that only fits as a pointer is dropped rather than teased — fewer cards per prompt, each carrying real content.

The SessionStart pointer explainer is skipped when pointers are disabled. It teaches the ▸ syntax, so shipping it would document something the model will never see while spending baseline budget to do it. That budget now goes to cards.

Upgrading

git pull && make install

Verdicts are computed on read, so buckets recover as the existing penalties decay (14-day half-life). Nothing to reindex.

culi v0.5.1 — Excelsa hotfix

Choose a tag to compare

@hung12ct hung12ct released this 30 Jul 14:11

Accuracy fix from several days of real use. No schema change; no config change.

The noisy bucket was measuring the wrong thing

It had degenerated into "was ever shown as a pointer".

A single unexpanded pointer is a 0.1 penalty — weighted 0.5 — which beat a zero Positive and tripped Negative > Positive && Negative > 0. That is the identical verdict given to a card the user explicitly rejected at weight 5.

Two measurements condemn it:

28 of 45 penalized cards sat at exactly one penalty
 1 expand across 107 pointer injections   (~1% base rate)

Non-expansion is the base rate, not evidence. The bucket was punishing precisely the silence that ClassifyEffectiveness's own doc comment promises not to punish — bodies and pointers can help without ever being pulled.

Fix: effNoisyFloor — negative evidence must now exceed 1.0 weighted.

Evidence Weighted Verdict
One explicit downvote 5.0 noisy immediately — the user is speaking
Three ignored pointers ~1.4 noisy
Two ignored pointers ~1.0 not noisy
One ignored pointer 0.5 not noisy

Strictly greater, so two undecayed penalties (exactly 1.0) land on the same side of the line as two decayed ones — a verdict must not depend on when it was read.

On a live store: noisy 35 → 4, uncertain 164 → 195, helpful unchanged at 4. The four survivors carry three to four sustained ignores each plus real token cost, which is what the bucket was always meant to mean.

Usage attribution no longer credits dead cards

Retired and candidate cards are skipped. Observed for real: a lesson retired for being factually wrong still earned credit, because the session that retired it necessarily quoted it — putting a dead card in the helpful list.

The general self-reference case (a session about a card reads as use of that card) is documented in the code, not solved. It mainly affects people editing culi's own knowledge base.

Upgrading

git pull && make install

Verdicts are computed on read, so the corrected buckets appear immediately — no reindex, no migration.

culi v0.5.0 — Excelsa

Choose a tag to compare

@hung12ct hung12ct released this 26 Jul 18:02

Culi can finally tell whether a card earned its tokens.

Usage attribution — the missing feedback loop

Culi had no automatic positive signal. expanded requires an MCP expand_card call that in practice never happens; referenced was written in exactly one place — the miner re-observing a lesson, which records that a lesson recurred, not that a card was ever used. Lifetime totals across a whole store were:

total_expanded  total_referenced
0.0             0.0

So every card classified uncertain, every UtilityMultiplier stayed pinned at 1.0, and ranking never learned anything from months of injections.

At SessionEnd the miner now re-reads the finished transcript and credits cards whose own prose reappears in the assistant's replies.

Word overlap was tried first and rejected. Measuring term rarity against the card corpus marks ordinary English as distinctive — card summaries are short and technical, so "also", "keeps" and "development" look rare. On real sessions it credited 10 of 18 injected cards on matches like [also keeps]. Useless as evidence, and actively harmful: any positive marks a card helpful permanently.

Phrase reuse replaced it — five-word prose sequences, with guards:

  • only cards injected at summary/body granularity (a pointer the model never expanded cannot have been used)
  • phrases the user typed are excluded, or a card earns credit for the topic that retrieved it
  • code fences, inline code, URLs and paths stripped — two texts quoting the same path prove no influence
  • at least one substantial word, so "all clean and the" cannot match

Four words still collided on stock English ("starting a new feature"); five credits 1–2 cards per session on real transcripts. Credit is 0.5, below the 1.0 a confirmed re-observation earns, and lands at most once per session.

The embedding arm was down more than you'd think

The hot path gives Ollama 100ms. Warm, nomic-embed-text answers in 36–62ms — but Ollama unloads idle models after ~5 minutes and the cold reload takes seconds. Every gap in a working session was a guaranteed timeout, and three trip the retrieve breaker for a 30s cooldown during which retrieval is BM25-only.

keep_alive now rides on every embed call, refreshing residency on use. Observed live: ollama ps UNTIL moved 4min → 29min and candidates went from bm25 to hybr.

Console fix

The overview's "Noisy cards" panel always rendered empty — it read a downvotes-only top-3 while its label described the effectiveness bucket. Also removes two seed-data fallbacks that displayed invented numbers when the real payload was empty.

Upgrading

git pull && make install

Schema migration v3 → v5, applied automatically. Forward-only ALTERs under BEGIN IMMEDIATE; runtime history (injections, card_stats, session_state, meta) is preserved, and a user_version newer than the binary fails closed. Verified on a live store with 198 injections, 23 card stats and 18 session rows intact.

New default: ollama.keep_alive: 30m, which keeps ~270MB resident for the embedding model. Set "-1" to never unload, or a shorter duration to trade hit rate for memory.

Hard-refresh the console after upgrading — web assets are served from embed.FS, so restarting culi serve alone shows a stale UI.

culi v0.4.1 — Liberica hotfix

Choose a tag to compare

@hung12ct hung12ct released this 25 Jul 16:38

Patch release on the Liberica line.

Fixes

Console overview: "Noisy cards" panel was always empty. The panel is labelled injected a lot · never expanded, but it read stats.cards.noisy — which gatherCards computes as the top-3 cards by decayed downvote score. Downvotes only accrue when you click Down in the console, so on a normal install that list is null and the panel rendered as a blank box.

The bucket the label describes already existed and had data: cards.effectiveness.noisy (exposed in the injection-log window, never pulled). The view model now reads it. The score pill shows window token cost (695 tok) instead of a downvote float — that's the number that makes a card worth retiring.

Empty states. Both the Noisy and Stale panels now explain themselves when there's nothing to show, instead of rendering an empty card body.

Removed fabricated fallbacks. staleHeader fell back to the seed placeholder "12 never pulled (30d)" and staleMore to "+ 9 more" whenever the real payload was empty. Since staleMore is "" for 3 or fewer stale cards, that "+ 9 more →" link was showing on every install with 0–3 stale cards.

Also included

Pull request and issue templates (added after v0.4.0).

Upgrading

git pull && make install

Then hard-refresh the console — the web assets are served from embed.FS, so restarting culi serve alone shows the stale UI.

culi v0.4.0 — Liberica

Choose a tag to compare

@hung12ct hung12ct released this 24 Jul 06:44

Fourth release of culi — the cross-repo reconcile flow moves into the console, plus card-history revert and edit-form polish, on top of v0.3.0. Fully backward-compatible.

Reconcile from the console

The culi import scan → merge → apply dance is now a guided three-step Reconcile screen in culi serve — no terminal required:

  • Scan — inventories every watched repo and clusters shared agents/skills by how far their copies have drifted (read-only, no LLM). Repo cards + a class-tagged cluster list.
  • Merge — runs the paid LLM merge as a tracked background job with a live progress bar. Arms behind a cost/time confirm first; output is staged, nothing touches your store yet.
  • Review & apply — staged cards grouped as ready / conflict / residual with expandable side-by-side diffs. A force toggle overwrites conflicts; every apply is one git commit. Residual CLAUDE.md files show their target repo and a diff against the live file, to be hand-placed.

Reuses importer.Scan/Apply verbatim, so the console and CLI stay behaviorally identical. All local, guarded, and off the hot path.

Knowledge base

  • Card-history Revert now works — each history row restores that one card file to its content at the chosen commit (scoped to the single file, never the whole multi-file commit), recorded as a fresh reversible commit. Two-step confirm; the revision is validated before it reaches git.
  • Inline edit form restyled — block labels, full-width inputs, and a clear button row so Save / Cancel are no longer bare browser controls.

Install / upgrade

go install github.com/hung12ct/culi/cmd/culi@latest   # or @v0.4.0

Existing users: hooks/MCP stay registered and your config is preserved. Restart culi serve (after a fresh go install) to pick up the Reconcile screen. Confirm with culi version.

culi v0.3.0 — Arabica

Choose a tag to compare

@hung12ct hung12ct released this 24 Jul 04:44

Third release of culi — card effectiveness verdicts, an injection-count fix, and a console footer polish on top of v0.2.0. Fully backward-compatible.

Card effectiveness verdicts

  • Every live card is now classified helpful / noisy / expensive / uncertain — combining decayed pull/reference/downvote signals (the same weights that tune retrieval ranking) with 7-day token cost.
  • Surfaced in culi stats (summary line, plus per-card verdicts under cards.effectiveness in --json), the console's Knowledge Pulse view (verdict strip + column, stable across harness/repo filters), and a badge with explanation on each card's detail page.
  • Deliberately asymmetric: a full card body pushed into the prompt can help an agent without ever being expanded, so silence alone never marks a card noisy — only explicit downvotes and abandoned pointers count as negative signal.

Fixes

  • Console Knowledge Base was showing 0 injected for every card. Root cause: the underlying card_stats.injected counter is never written by any code path. Injected counts now read the injection log directly.

Polish

  • Console footer now shows the running build version.

Install / upgrade

go install github.com/hung12ct/culi/cmd/culi@latest   # or @v0.3.0

Existing users: that's it — hooks/MCP stay registered and your config is preserved. Restart culi serve (after make install or a fresh go install) to pick up the new console UI. Confirm with culi version.

culi v0.2.0 — Robusta

Choose a tag to compare

@hung12ct hung12ct released this 23 Jul 16:45

Second release of culi — Codex harness support, a full learning-backend lineup, background-learning auth, and review-console analytics on top of v0.1.0. Fully backward-compatible; every new knob has a safe default.

Codex harness

  • First-class OpenAI Codex support alongside Claude Code — normalized hooks (UserPromptSubmit / SessionStart / Stop / SessionEnd), a typed Harness enum across culi core, and aligned SessionEnd timeouts.
  • Rollout discovery & backfill — culi finds and mines existing Codex rollout history; drops known synthetic user envelopes so only real turns are learned.
  • Auto-recovery scan — a throttled, read-only rollout scan fills in when transcript_path is missing. Scanner telemetry is content-free and surfaced by culi doctor.

Learning

  • Five backends — OpenAI API, Codex CLI, Claude terminal, Anthropic API, and Ollama, all behind hard daily caps. OpenAI/Codex also power import-merge.
  • Headless background auth — claude -p / Anthropic API work from the detached session-end worker. A persisted claude auth login is enough; optional learn.oauth_token_file / learn.anthropic_api_key_file for env-var-only or headless setups. Auth/config failures halt cleanly and no longer burn the daily cap.
  • Throughput controls — newest-first mining, learn.max_jobs_per_run (default 50), and culi learn --no-cap to drain the whole backlog in one parallel run.
  • Configurable auto-confirm — learn.confirm_at (default 2) tunes how many observations promote a candidate to a confirmed, injecting card; 1 = confirm on first sighting.
  • No more self-ingestion — culi no longer mines its own claude -p learning calls.

Review console (culi serve)

  • Knowledge Pulse analytics — new overview analytics, learning settings, and light/dark theming.
  • Editable Settings — safe knobs (budgets, caps, models, credential files, confirm_at, cheap_model / strong_model) write straight to config.yaml with comments preserved.
  • Activity filters — filter the injection log by harness, repo, and date (Today / 7d / 30d), with per-session badges.
  • "To learn" indicator — queued-transcript backlog in the status strip.
  • Clearer savings baseline, stale-card navigation, and a repos-manager note (watched repos drive learning/generation; injection works in any repo).

Storage & tooling

  • Runtime history preserved across schema changes via explicit forward migrations (injections, card stats, session state, metadata survive rebuilds).
  • culi doctor — health/telemetry command for hooks, backends, and the Codex scanner.

Fixes

  • Overview crash on empty data; prompt-drain halt-on-backend-down; comment-preserving config writes.

Install / upgrade

go install github.com/hung12ct/culi/cmd/culi@latest   # or @v0.2.0

Existing users: that's it — hooks/MCP stay registered and your config is preserved (new knobs default safely). Optionally re-run culi init (idempotent) and restart culi serve to pick up the new UI. Confirm with culi version.