pub struct LearningStore { /* private fields */ }Implementations§
Source§impl LearningStore
impl LearningStore
pub fn default_root() -> Result<PathBuf>
Sourcepub fn open(root: impl Into<PathBuf>) -> Result<Self>
pub fn open(root: impl Into<PathBuf>) -> Result<Self>
Open the store, creating the layout (and, best-effort, the git repo) if it is not there yet. Git being absent degrades to plain files — the audit trail is lost, the data is not.
Sourcepub fn open_existing_default() -> Option<Self>
pub fn open_existing_default() -> Option<Self>
Open at the default location only if it already exists — for read paths (prompt assembly) that must not create state as a side effect.
pub fn root(&self) -> &Path
pub fn append_reflexion(&self, r: &Reflexion) -> Result<()>
pub fn reflexions(&self) -> Result<Vec<Reflexion>>
Sourcepub fn mined_sessions(&self) -> Result<HashSet<String>>
pub fn mined_sessions(&self) -> Result<HashSet<String>>
Sessions already mined, so mecha reflect never re-reads one.
pub fn mark_mined(&self, session_id: &str) -> Result<()>
Sourcepub fn mined_outbox(&self) -> Result<HashSet<String>>
pub fn mined_outbox(&self) -> Result<HashSet<String>>
Outbox items already mined for writing reflections — the outbox
counterpart of Self::mined_sessions, so the nightly pass never
re-argues the same edit.
pub fn mark_outbox_mined(&self, item_id: &str) -> Result<()>
Sourcepub fn mined_corrections(&self) -> Result<HashSet<String>>
pub fn mined_corrections(&self) -> Result<HashSet<String>>
Triage corrections already mined for triage reflections — a third
ledger beside sessions and outbox items, for the same reason they are
separate from each other: an id in one must never satisfy another, and
a shared ledger makes that an accident waiting to happen.
Keyed per correction, not per thread. A thread corrected once and then corrected again is two lessons — the second is often the more interesting one, since it says the first correction was not enough.
pub fn mark_correction_mined(&self, key: &str) -> Result<()>
Sourcepub fn distilled_sessions(&self) -> Result<HashSet<String>>
pub fn distilled_sessions(&self) -> Result<HashSet<String>>
Sessions already distilled to the knowledge graph — mecha distill’s
ledger. Kept in this store, not beside the sessions, for the same
reasons the mining ledgers are: the writer lock covers the
read-then-mark race between two detached session_end hooks, and the
git history says when each push happened.
pub fn mark_distilled(&self, session_id: &str) -> Result<()>
Sourcepub fn user_rules(&self, domain: &str) -> Result<Vec<Rule>>
pub fn user_rules(&self, domain: &str) -> Result<Vec<Rule>>
The user’s own rules. This file is never written by any pass: the consolidation prompt is told these rules are immutable, and this is that constraint made structural rather than left to the model.
pub fn learned_rules(&self, domain: &str) -> Result<Vec<Rule>>
Sourcepub fn write_learned_rules(&self, domain: &str, rules: &[Rule]) -> Result<()>
pub fn write_learned_rules(&self, domain: &str, rules: &[Rule]) -> Result<()>
Replace a domain’s learned rules. Only consolidation calls this. Written via a temp sibling and rename: the run-start injection path reads this file with no lock (a read must never wait on a learn pass), so the file on disk has to be complete at every instant — a torn TOML here would fail an unrelated run at startup.
Sourcepub fn rules_prompt_block(&self) -> Result<Option<String>>
pub fn rules_prompt_block(&self) -> Result<Option<String>>
Every domain’s rules, rendered. This is the whole store’s view —
mecha rules, a proposal diff, a validation arm — and deliberately
not what a run’s system prompt gets. Use
Self::rules_prompt_block_for for that.
Sourcepub fn rules_prompt_block_for(&self, domains: &[&str]) -> Result<Option<String>>
pub fn rules_prompt_block_for(&self, domains: &[&str]) -> Result<Option<String>>
The block injected into one run’s system prompt: the user’s rules
first, then enabled learned rules, for the named domains only.
None when there is nothing to say — an empty section would spend
cache-prefix tokens on a heading.
Selection exists because a domain is not universally relevant and the
block rides in every turn’s cached prefix. writing rules describe how
this user’s prose should read; they earn their tokens when the model is
drafting a message and cost them on every run that never drafts
anything. A future triage domain — rules for the mail classifier — is
worse still: that pass is a tool-less, history-less call with one job,
and general conduct rules are noise to it exactly as its rules would be
noise everywhere else.
Named rather than filtered, so a new domain is opt-in. A domain
that appears on disk joins no prompt until something asks for it, which
is the direction that fails safely: the cost of forgetting to add one
is rules that do not fire, and Self::unrouted_domains reports that
at startup. The cost of the other default is every future domain
silently joining every prefix — and with
MAX_ACTIVE_RULES_PER_DOMAIN at 25, three domains would be 75 rules
in front of every request.
Sourcepub fn unrouted_domains(&self, routed: &[&str]) -> Result<Vec<String>>
pub fn unrouted_domains(&self, routed: &[&str]) -> Result<Vec<String>>
Domains that hold active rules but ride in no run’s prompt — the silent half of opt-in selection. Startup warns on these, the routed-name-matches-no-tool precedent: a user rule nobody reads is indistinguishable from a user rule being obeyed, and a typo in a filename is the likely cause.
Sourcepub fn over_budget_domains(&self) -> Result<Vec<(String, usize)>>
pub fn over_budget_domains(&self) -> Result<Vec<(String, usize)>>
Domains whose active learned rules exceed
MAX_ACTIVE_RULES_PER_DOMAIN — the always-loaded block drifting
past the adherence cliff. Startup warns on these (the routed-name
precedent); the learn gate refuses to grow them further.
Sourcepub fn lock(&self) -> Result<StoreLock>
pub fn lock(&self) -> Result<StoreLock>
Take the store’s writer lock, blocking until it is free.
Every pass that writes (reflect, learn) takes this before reading
the state it will act on — the read is where the race lives: two
reflects that both read mined_sessions before either marks would
mine the same session twice, which stopped being hypothetical the
moment reflect started running detached at every session close.
Advisory flock, so it serializes mecha’s own writers without doing
anything to the user’s $EDITOR — the store’s files staying humanly
editable is a requirement, not an accident. The kernel drops the lock
when the fd closes, crash included, so a dead pass can never wedge
the store. Read paths (prompt assembly, validate) do not take it:
a run start must never block on a learn pass, which is why every
rewrite in this module goes through a temp sibling and rename.
Source§impl LearningStore
impl LearningStore
Sourcepub fn write_proposal(&self, p: &Proposal) -> Result<()>
pub fn write_proposal(&self, p: &Proposal) -> Result<()>
Write (or rewrite) one proposal, atomically — mecha proposals list
must never read a half-written file from a nightly pass.
Sourcepub fn proposal(&self, id: &str) -> Result<Proposal>
pub fn proposal(&self, id: &str) -> Result<Proposal>
Find one proposal by id or unique prefix. Ambiguity is an error rather than a guess, same as session lookup.