Skip to main content

Module learning

Module learning 

Source
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 consolidation

The 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 Reflector decides, 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.
LearningStore
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.
RuleTally
What the ledger says about one rule, folded from its rows.
StoreLock
Holds the store’s writer lock for as long as it lives. See LearningStore::lock.
ValidationRecord
One probe’s measurement, written down instead of printed and discarded.

Enums§

Evidence
What the reflector was shown when a reflection was mined.
Origin
Where a reflection’s evidence came from, provenance-wise.
Trigger

Constants§

LEARN_MIN_REFLECTIONS
How many unprocessed reflections a domain needs before mecha learn consolidates — the default behind learn --min, and the floor doctor’s starved-learner check measures against. One constant, two readers, on the MAX_ACTIVE_RULES_PER_DOMAIN lesson: a check that names one number while the gate applies another fails silently, and looks like a healthy loop.
MAX_ACTIVE_RULES_PER_DOMAIN
Hard cap on active learned rules per domain — the count half of the budget, where RULES_CHAR_BUDGET is the size half. This is the check that does not depend on the model listening; [learner_frames] states the same number to the learner, interpolated from here so the two cannot disagree.
PASS_DOMAINS
Domains loaded by a named pass rather than by a general run.
RULES_BLOCK_HEADING
The heading rules_prompt_block emits, 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.
RUN_DOMAINS
The domains whose rules ride in an ordinary agent run’s system prompt.
TRIAGE_DOMAIN
The mail classifier’s own learning domain.

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.
evidence_for
Decide what the reflector may see for one intervention, and how the resulting reflection classifies.
extract_interventions
Extract every intervention from a recorded conversation.
finalize_rules
locate_followup
Find the user turn carrying intervention_text and return the index of that message — the conversation prefix for a counterfactual probe is everything before it.
routed_domains
Every domain something actually loads. What “unrouted” must be measured against — a domain is routed if a run carries it or a pass reads 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.
run_domains_including
The domains a run exercising domain would carry: RUN_DOMAINS, plus domain itself when it is not one of them.
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.