Expand description
The self-learning store: reflections, learned rules, and the miner.
Reflexion-style (Shinn et al. 2023) with LEAP consolidation (Zhang et al. 2024) to come. Three stages: reflection (one contextual note per user intervention — this module), abstraction (reflections → candidate rules, batched), and consolidation (a fixed token budget per domain, so learning never grows the system prompt without bound).
Storage is files, not a database, on purpose: everything in mecha is
inspectable text (JSONL transcripts, TOML config), and the user’s explicit
requirement for this system is that it can be inspected and edited. The
layout under ~/.mecha/learning/:
reflections.jsonl append-only evidence, one line per reflection
mined.jsonl session ids already mined, one per line
distilled.jsonl session ids already distilled to the graph
rules/<domain>.user.toml the user's own rules — never written by code
rules/<domain>.learned.toml rewritten at consolidationThe directory is a git repository (created best-effort on first open), and
passes commit their changes: git log is the audit trail, git diff the
review UI, git revert the undo for a bad consolidation. If the workload
ever outgrows files — the CIPHER retrieval tier is the likely reason — the
swap to a database happens behind this module’s API. Noted as a real
possibility, not a failure of this design.
Split of responsibilities: extraction from transcripts is pure and
unit-tested here; the Reflector holds the one model call, mirroring
crate::eval::Judge. What counts as an intervention:
- Steering — user text riding in the same message as tool results. Unambiguous: the user reached in mid-run to redirect.
- Denial — a tool result reading “Denied by the user: …”. A recorded rejected intent.
- Follow-up turns — a later user turn may be a correction of the
assistant’s behaviour or just the next task. Extraction flags the
candidate; the
Reflectordecides, and is told to skip freely.
Structs§
- Intervention
- One moment in a transcript where the user stepped in.
- LeapRun
- Audit record for one abstraction/consolidation pass. Appended to
runs.jsonl; together with the store’s git history this is the full lineage from any rule back to the reflections that argued for it. - Learner
- Runs one abstraction/consolidation pass for a domain: current learned rules + unprocessed reflections in, a rewritten learned rule set out.
- Learning
Store - Proposal
- A rule change waiting for the user, with the evidence that argues for it.
- Reflector
- Turns interventions into reflections with one model call each.
Mirrors
crate::eval::Judge: bare provider, no tools, no history. - Reflexion
- One learned note, tied to the intervention that produced it.
- Rule
- One rule in a domain’s TOML file.
- Rule
Tally - What the ledger says about one rule, folded from its rows.
- Store
Lock - Holds the store’s writer lock for as long as it lives. See
LearningStore::lock. - Validation
Record - One probe’s measurement, written down instead of printed and discarded.
Enums§
Constants§
- MAX_
ACTIVE_ RULES_ PER_ DOMAIN - Hard cap on active learned rules per domain — the count half of the
budget, where
RULES_CHAR_BUDGETis the size half. The learner frames already say “never exceed 15”; this is the check that does not depend on the model listening. Fifteen because rule adherence falls off well before the drift literature’s ~50-entry cap, and the block rides in every run’s cached prefix. User rules are not counted: they are the user’s own budget to spend. - RULES_
BLOCK_ HEADING - The heading
rules_prompt_blockemits, shared so a validator can strip an old block before injecting a candidate one — a session recorded with rules must not get them twice, or keep stale ones in its baseline arm. - RULES_
CHAR_ BUDGET - Roughly how large a domain’s rendered rules block should be allowed to get, in characters (~4 chars per token). Consolidation exists so learning never grows the system prompt without bound; this is the bound.
Functions§
- budget_
refuses - The budget gate’s arithmetic: a candidate set that ends over the cap may land only by shrinking an already-over set toward it. Growth past the cap — however the learner argued for it — is refused, and the refusal is what forces the next pass to merge or retire before it may add.
- classify_
origin - Classify a reflection’s origin from the taint covering its intervention.
- domain_
rules_ section - One domain’s section of the rules block, from explicit rule sets rather than the store — which is what lets a proposal gate render a candidate set exactly as a run would see it, before anything is written anywhere.
- extract_
interventions - Extract every intervention from a recorded conversation.
- finalize_
rules - Mint identity for a freshly learned rule set, carrying lineage forward.
- locate_
followup - Find the user turn carrying
intervention_textand return the index of that message — the conversation prefix for a counterfactual probe is everything before it. - rule_
tallies - Fold ledger rows into per-rule tallies.
- rules_
hash - Stable content hash of a rendered rules block. FNV-1a written out here because the std hasher is deliberately unstable across Rust releases, and a ledger key that drifts with the toolchain would silently split every tally.
- strip_
rules_ block - Remove a previously injected rules block from a recorded system prompt.
- wrap_
rules_ block - Wrap rendered sections in the heading a run’s system prompt carries.