@lorekit/cli 1.63.0 → 1.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -51,16 +51,23 @@ memory that calls those primitives on a host's behalf. Both run on the same
|
|
|
51
51
|
LoreKit store — over the `memory.*` MCP tools for agents, over the `lorekit` CLI
|
|
52
52
|
or REST for jobs.
|
|
53
53
|
|
|
54
|
-
## The
|
|
54
|
+
## The rungs of a lessons loop (in one screen)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| **Slow (procedural)** | A human-reviewed edit that hardens a recurring lesson into a host rule | **Yes** |
|
|
56
|
+
The runtime loop below is two tiers, fast and slow; a third, rarer rung sits past
|
|
57
|
+
promotion, for the minority of recurring lessons that can be turned into a
|
|
58
|
+
mechanically-checked rule instead of text a reader has to notice.
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
the
|
|
60
|
+
| Rung | Mechanism | Changes behavior? | Advisory or enforced? |
|
|
61
|
+
| ---- | --------- | ----------------- | ---------------------- |
|
|
62
|
+
| **Fast (episodic)** | LoreKit lessons in a per-host bucket, read at the start of a run, written on failure | **No** — advisory input only | Advisory |
|
|
63
|
+
| **Slow (procedural)** | A human-reviewed edit that hardens a recurring lesson into a host rule | **Yes** | Advisory (works only if the next reader notices it) |
|
|
64
|
+
| **Compiled invariant** | A declarative `obligations-map.mjs` entry a CI gate checks against a changed-file set — see [rules/compiled-invariants.md](./rules/compiled-invariants.md) | **Yes** | Enforced once `gating`; most lessons never qualify |
|
|
65
|
+
|
|
66
|
+
A recurrence gate connects the first two: a lesson that recurs (`seen_count >= 3`)
|
|
67
|
+
or is marked `status=structural` becomes promotion-eligible. Entrenchment guards
|
|
68
|
+
keep the fast tier from reinforcing its own wrong conclusions. The third rung has
|
|
69
|
+
its own, stricter gate — the compilability test — and most promotion-eligible
|
|
70
|
+
lessons stop at the second rung because they fail it.
|
|
64
71
|
|
|
65
72
|
## Pick the shape first
|
|
66
73
|
|
|
@@ -71,6 +78,7 @@ before reading further:
|
|
|
71
78
|
| ------------ | ----- | ---- |
|
|
72
79
|
| A **model-driven** skill, agent, or workflow that fails in recurring, classifiable ways | Prose **lessons** — advisory, recurrence-gated, promotable into rules | [rules/self-improvement-loops.md](./rules/self-improvement-loops.md) |
|
|
73
80
|
| A **deterministic job** — a GitHub Actions workflow, a cron script, a release pipeline — that needs last-run state | JSON **state records** — authoritative, parsed, one key per fact | [rules/ci-state-records.md](./rules/ci-state-records.md) |
|
|
81
|
+
| A recurring lesson whose failure mode is judgement-free and checkable against an independent source of truth | A **compiled invariant** — a declarative entry a CI gate enforces mechanically, never advisory once `gating` | [rules/compiled-invariants.md](./rules/compiled-invariants.md) |
|
|
74
82
|
|
|
75
83
|
A host can want both, in separate buckets: the state record carries *what is true
|
|
76
84
|
right now*, the lesson carries *what we learned about it*.
|
|
@@ -90,11 +90,17 @@ Three concrete limits make this a hard rule rather than a style preference:
|
|
|
90
90
|
- **5 000** active memories per user by default, enforced by a DB trigger.
|
|
91
91
|
- **120 requests/min** per user across every LoreKit surface.
|
|
92
92
|
|
|
93
|
-
And one
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
And one mechanism that matters more than any of the three limits above for
|
|
94
|
+
keeping state records out of the way: the SessionStart digest is built by
|
|
95
|
+
`fetchLessons`, which keeps only entries where `isGeneralLesson(kind)`
|
|
96
|
+
is true — `kind == null || kind === 'lesson'`. A record written with `--kind bus`
|
|
97
|
+
or `--kind signal` is excluded from the digest outright, no matter how recent it
|
|
98
|
+
is; it never competes with lessons for the read cap in the first place. The
|
|
99
|
+
exclusion is by `kind`, not by volume — writing a state record does not, by
|
|
100
|
+
itself, protect it. A record left with `kind` unset (`null`) still passes
|
|
101
|
+
`isGeneralLesson` and *does* count toward the digest like any other lesson,
|
|
102
|
+
which is exactly why the Conventions section below requires `--kind` explicitly
|
|
103
|
+
rather than leaving it to infer.
|
|
98
104
|
|
|
99
105
|
---
|
|
100
106
|
|
|
@@ -107,9 +113,17 @@ lesson bucket:
|
|
|
107
113
|
Deliberately **not** `loop::…`; that prefix is the lessons grammar.
|
|
108
114
|
- **Key:** `ci-state::<slug>` — e.g. `ci-state::flaky-tests`. One slug per fact.
|
|
109
115
|
- **Taxonomy:** pass `--kind bus --host ci` explicitly. `kind`/`host` are only
|
|
110
|
-
inferred from `loop::` tags, so a `ci::` tag leaves them NULL unless you say so
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
inferred from `loop::` tags, so a `ci::` tag leaves them NULL unless you say so
|
|
117
|
+
— and a NULL `kind` still passes `isGeneralLesson`, so an un-kinded state
|
|
118
|
+
record renders as a raw JSON blob inline in every session's SessionStart
|
|
119
|
+
digest, right alongside actual lessons. Setting `--kind` is what keeps it out
|
|
120
|
+
of the digest at all, not just what makes it easy to find. It also buys
|
|
121
|
+
`lorekit list --kind bus --host ci` — one command that shows every state
|
|
122
|
+
record and nothing else — but that's secondary to keeping the digest clean.
|
|
123
|
+
The `kind` vocabulary is **closed** — `lesson`, `bus`, `signal` — adding a
|
|
124
|
+
new one is a schema change (`MemoryKindSchema` in `@lorekit/schemas`), so if
|
|
125
|
+
none of the three fits your record, pick whichever is closest rather than
|
|
126
|
+
inventing a fourth.
|
|
113
127
|
|
|
114
128
|
Scope, by what the fact is about:
|
|
115
129
|
|
|
@@ -401,9 +415,11 @@ do:
|
|
|
401
415
|
6. **Version every record.** An unrecognised `v` means fall back to the first-run
|
|
402
416
|
path and log it — never parse a shape you do not recognise.
|
|
403
417
|
7. **Remember agents read this scope.** State records land in `repo::` alongside
|
|
404
|
-
lessons and
|
|
405
|
-
|
|
406
|
-
|
|
418
|
+
lessons, and a correctly-kinded one (`--kind bus`/`--kind signal`) is excluded
|
|
419
|
+
from SessionStart's automatic digest — an agent finds it by explicitly listing
|
|
420
|
+
or reading the scope (see below), not by it appearing unprompted. It still
|
|
421
|
+
means the value should read sensibly to a human or agent skimming it, and stay
|
|
422
|
+
small.
|
|
407
423
|
|
|
408
424
|
---
|
|
409
425
|
|
|
@@ -413,8 +429,10 @@ Job-to-job persistence alone does not justify LoreKit over `actions/cache`. What
|
|
|
413
429
|
does is that **both sides of the loop read the same store**:
|
|
414
430
|
|
|
415
431
|
- CI writes `ci-state::flaky-tests` deterministically on every run of `main`.
|
|
416
|
-
- An agent asked to "fix the flaky tests" reads it
|
|
417
|
-
|
|
432
|
+
- An agent asked to "fix the flaky tests" reads it with `lorekit list --kind bus
|
|
433
|
+
--host ci` (or a direct `memory_read` of the key) — the same repo scope, no
|
|
434
|
+
extra wiring, just an explicit lookup instead of the automatic digest — and
|
|
435
|
+
starts from the real list instead of re-deriving it.
|
|
418
436
|
- The agent's own findings go back as a **lesson** in the `loop::` bucket, in the
|
|
419
437
|
same repo scope, where the next human and the next agent both see it.
|
|
420
438
|
- The dashboard shows both, with each state record linked back to the exact
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Compiled invariants (the third rung)
|
|
2
|
+
|
|
3
|
+
A lesson that keeps recurring can go two places. [self-improvement-loops.md](./self-improvement-loops.md)
|
|
4
|
+
covers the first: promote it into a **prose rule** — a human-reviewed edit to the
|
|
5
|
+
host's own source or docs, still read by whoever (human or model) happens to look.
|
|
6
|
+
This file covers the second, harder promotion: turning a recurrence into a
|
|
7
|
+
**compiled invariant** — a declarative, checked assertion that a CI gate or a CLI
|
|
8
|
+
command enforces mechanically, whether or not anyone reads the rule that day.
|
|
9
|
+
|
|
10
|
+
## The three rungs
|
|
11
|
+
|
|
12
|
+
1. **Lesson** (fast, episodic). Written by a model at the end of a run, read at
|
|
13
|
+
the start of the next one. Advisory only — it biases a future run, it does not
|
|
14
|
+
block one.
|
|
15
|
+
2. **Prose rule** (slow, procedural). A human-reviewed edit that hardens a
|
|
16
|
+
recurring lesson into the host's own source or docs. Still advisory in the
|
|
17
|
+
sense that nothing *enforces* it — it works only if the next reader (human or
|
|
18
|
+
model) actually reads it.
|
|
19
|
+
3. **Compiled invariant** (this file). A declarative entry — in this repo,
|
|
20
|
+
`packages/cli/src/shared/obligations-map.mjs` — that `lorekit obligations`
|
|
21
|
+
checks against a changed-file set. Not advisory: a `gating` entry with an
|
|
22
|
+
independent `guard` fails `--strict`.
|
|
23
|
+
|
|
24
|
+
Rungs 1 and 2 are **probabilistic** — they depend on someone noticing, reading,
|
|
25
|
+
and acting on the text at the right moment. Rung 3 is **deterministic** — the
|
|
26
|
+
check runs whether or not anyone was thinking about the rule that day. That is
|
|
27
|
+
the entire reason to climb this far: it is strictly more reliable, and strictly
|
|
28
|
+
more expensive to build and maintain, which is why most lessons never get here.
|
|
29
|
+
|
|
30
|
+
## The compilability test
|
|
31
|
+
|
|
32
|
+
A lesson is a compile candidate only if **all four** hold:
|
|
33
|
+
|
|
34
|
+
1. **Trigger detectable without judgement.** The condition that should fire the
|
|
35
|
+
check can be recognized from a file path, a diff shape, or another mechanical
|
|
36
|
+
signal — not "whether this edit is the kind that matters," which needs a
|
|
37
|
+
reader's judgement. This is exactly what the existing "trigger-context must be
|
|
38
|
+
concrete" requirement in the lessons loop exists for: a lesson with a vague
|
|
39
|
+
trigger-context was never going to compile, so demanding concreteness at
|
|
40
|
+
write time is what keeps the door open later.
|
|
41
|
+
2. **Assertion statable as must-remain-true.** The rule has to be expressible as
|
|
42
|
+
an invariant ("if A changed, B must also be in the changed set"), not a
|
|
43
|
+
preference or a judgement call ("consider simplifying this").
|
|
44
|
+
3. **Expected value comes from somewhere OTHER than the memory itself.** The
|
|
45
|
+
check needs an independent source of truth to compare against — a second
|
|
46
|
+
file, a generator's output, an existing test. A lesson that only says "X
|
|
47
|
+
should be true" with no external referent has nothing to check X against.
|
|
48
|
+
4. **An enforcement point exists.** Something already runs at the right time —
|
|
49
|
+
CI, a pre-commit hook, a CLI command a human or agent actually invokes — for
|
|
50
|
+
the check to plug into. A perfectly compilable rule with nowhere to run is
|
|
51
|
+
still stuck at rung 2.
|
|
52
|
+
|
|
53
|
+
Most memories fail this test, and that is the expected, normal outcome — not a
|
|
54
|
+
sign the lesson was low-quality. A lesson can be a perfectly good, permanently
|
|
55
|
+
useful rung-2 prose rule and never qualify for rung 3, because it fails on
|
|
56
|
+
judgement (a design taste) or on having no independent value to check against (a
|
|
57
|
+
process reminder). Compilability is a property of the failure mode, not of how
|
|
58
|
+
important or how recurrent the lesson is.
|
|
59
|
+
|
|
60
|
+
## The state ladder
|
|
61
|
+
|
|
62
|
+
A compiled entry does not start out enforcing anything. It moves through:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
candidate → advisory → gating → retired
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **candidate** — surfaced by `lorekit invariants candidates` (below), not yet a
|
|
69
|
+
map entry. Exists only in a scan's output, never in the repo.
|
|
70
|
+
- **advisory** — a hand-written `obligations-map.mjs` entry (`state: 'advisory'`).
|
|
71
|
+
Reported by `lorekit obligations` on every hit, gates nothing under `--strict`
|
|
72
|
+
(only `--strict-all` counts it, per its own note: "restores the previous
|
|
73
|
+
behaviour of gating on every unmet obligation regardless of state"). This is
|
|
74
|
+
where every new entry starts.
|
|
75
|
+
- **gating** — fails `--strict`. An entry may only be `gating` if it declares an
|
|
76
|
+
independent `guard`: an existing CI spec or script that *already* asserts the
|
|
77
|
+
partnership on its own, so the map entry is surfacing a real check earlier,
|
|
78
|
+
not inventing a new source of truth. An entry with no `guard` stays advisory by
|
|
79
|
+
construction — it would otherwise assert nothing but its own author's belief.
|
|
80
|
+
- **retired** — not checked; kept in the map for provenance (why the entry
|
|
81
|
+
existed, when it stopped applying).
|
|
82
|
+
|
|
83
|
+
Two more rules keep the ladder honest once an entry reaches `gating`:
|
|
84
|
+
|
|
85
|
+
- **Demote on false positives.** Nothing in `lorekit obligations` runs a `guard`
|
|
86
|
+
or writes `state` back — this is a manual policy, not automation. A `gating`
|
|
87
|
+
entry that fires on an edit it should not have (its `guard` disagrees, or a
|
|
88
|
+
human overrides it) is a signal the entry is too broad or the wrong shape —
|
|
89
|
+
a human should demote it back to `advisory` rather than letting the
|
|
90
|
+
disagreement stand.
|
|
91
|
+
- **A review date.** Every entry carries `reviewBy`; an entry with no independent
|
|
92
|
+
guard gets a shorter horizon than one that does, because it is more likely to
|
|
93
|
+
have been an over-generalization from a single incident.
|
|
94
|
+
|
|
95
|
+
## The cluster layer
|
|
96
|
+
|
|
97
|
+
An `obligations-map.mjs` entry does not name a bare lesson key — it names a
|
|
98
|
+
**recurrence class**, declared in `packages/cli/src/shared/recurrence-clusters.mjs`.
|
|
99
|
+
Read that file's header docblock for the full rationale; the short version is
|
|
100
|
+
that naming the class (rather than one lesson) lets several unmet obligations
|
|
101
|
+
that share a root cause report as one problem, and gives the compile pipeline a
|
|
102
|
+
join point between "a candidates scan over the memory store" and "a human-written
|
|
103
|
+
map entry." `RECURRENCE_CLUSTERS` is a short, deliberately curated list — adding
|
|
104
|
+
one is an assertion that several distinct memories are really the same class, not
|
|
105
|
+
a bucket you reach for per-entry.
|
|
106
|
+
|
|
107
|
+
## `lorekit invariants candidates`
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
lorekit invariants candidates [--scope <scope>] [--min-seen-count N] [--json]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A read-only survey over the memory store. It reuses `dedupe`'s Jaccard clustering
|
|
114
|
+
to find near-duplicate lessons, then ranks the resulting clusters by
|
|
115
|
+
`(summed seen_count × distinct scopes)`, descending. A cluster is worth
|
|
116
|
+
reporting when the summed `seen_count` across its members is at least
|
|
117
|
+
`--min-seen-count` (default 3), or any member's `meta` comment already declares a
|
|
118
|
+
non-`active` status. For each candidate it prints every memory the merge would
|
|
119
|
+
collapse — deliberately the default view, not hidden behind `--verbose`, because
|
|
120
|
+
that list *is* the point of the command: a human decides from it whether the
|
|
121
|
+
cluster deserves a hand-written entry.
|
|
122
|
+
|
|
123
|
+
It also reports whether a cluster already resolves to a named recurrence class
|
|
124
|
+
via the same join `dedupe` uses — so a scan can tell you "this cluster of five
|
|
125
|
+
near-duplicate lessons is already the `copies-a-claim` class" instead of leaving
|
|
126
|
+
you to notice by hand.
|
|
127
|
+
|
|
128
|
+
Two things it deliberately does **not** do, on purpose:
|
|
129
|
+
|
|
130
|
+
- **It does not classify trigger-contexts.** A lesson's raw `trigger-context`
|
|
131
|
+
string, when present, is printed verbatim — never interpreted into a
|
|
132
|
+
glob/command/error-shape. Turning "parses into a detectable trigger" from a
|
|
133
|
+
string into an actual predicate is the human step the compile pipeline
|
|
134
|
+
protects; automating it would be exactly the "auto-compile" this pipeline
|
|
135
|
+
refuses to do.
|
|
136
|
+
- **It does not check `compiled_to`.** No such field exists yet — no schema, no
|
|
137
|
+
server support. A candidate that has already been compiled into an
|
|
138
|
+
`obligations-map.mjs` entry can still surface again on a later scan. This is a
|
|
139
|
+
known, named gap, not a silent one: don't assume a candidate you see today
|
|
140
|
+
hasn't already been acted on, and don't build automation on the assumption
|
|
141
|
+
that a scan's output is the full unresolved set.
|
|
142
|
+
|
|
143
|
+
It never auto-compiles and never gates anything — it produces a report; a human
|
|
144
|
+
reads it and hand-writes the `obligations-map.mjs` entry.
|
|
145
|
+
|
|
146
|
+
## Where declarations live
|
|
147
|
+
|
|
148
|
+
Compiled invariants are declared **in the repo** — `obligations-map.mjs` is
|
|
149
|
+
reviewable, versioned, ordinary source, subject to the same PR review as any
|
|
150
|
+
other code change. They are never stored as memory-store records. The memory
|
|
151
|
+
store is the *input* to the compile pipeline (it is what `lorekit dedupe` and
|
|
152
|
+
`lorekit invariants candidates` scan) but is never on the critical path of a
|
|
153
|
+
gating check — `lorekit obligations` reads only the changed-file set and the
|
|
154
|
+
map, never the store, so a store outage cannot silently disable a gate.
|
|
155
|
+
|
|
156
|
+
## The dedupe prerequisite
|
|
157
|
+
|
|
158
|
+
This is the most load-bearing sequencing fact in the pipeline: **cluster before
|
|
159
|
+
you count.** The compile pipeline is `cluster → groom-merge → compile candidate →
|
|
160
|
+
invariant`, in that order, and the clustering step is not optional scaffolding —
|
|
161
|
+
it is the thing that makes the `seen_count` threshold meaningful at all.
|
|
162
|
+
|
|
163
|
+
The concrete case that proves it: the ~45 lesson variants behind the
|
|
164
|
+
`copies-a-claim`/`sibling-set` recurrence were not one lesson written 45 times —
|
|
165
|
+
they were **~45 distinct keys**, each written once, each carrying its own
|
|
166
|
+
`seen_count` of 1. A raw scan for `seen_count >= 3` over the ungroomed store would
|
|
167
|
+
have found *nothing*, because no single key ever crossed the threshold on its
|
|
168
|
+
own. Only after `lorekit dedupe`'s Jaccard clustering merged the near-duplicates
|
|
169
|
+
into groups did the pipeline have anything to sum a `seen_count` over. Skipping
|
|
170
|
+
the merge step, or running the candidates scan against an ungroomed store, does
|
|
171
|
+
not just produce a worse result — it produces a false negative that looks like
|
|
172
|
+
"nothing recurs here."
|
|
173
|
+
|
|
174
|
+
## `compiled_to` is a named gap
|
|
175
|
+
|
|
176
|
+
There is deliberately no `compiled_to` field on a memory record today — no
|
|
177
|
+
schema for it, no server support. Once a lesson has been compiled into an
|
|
178
|
+
`obligations-map.mjs` entry, nothing marks the source memory as resolved, and
|
|
179
|
+
nothing stops it from surfacing again in a later `lorekit invariants candidates`
|
|
180
|
+
scan or a later `dedupe` pass. This is a known gap, not a claim that the loop
|
|
181
|
+
closes end-to-end today — don't describe the pipeline as though a compiled
|
|
182
|
+
candidate is automatically suppressed from future scans, and don't build
|
|
183
|
+
automation that assumes it is.
|
|
@@ -267,6 +267,16 @@ Promotion is a normal, human-reviewed edit — LoreKit does not apply it. After
|
|
|
267
267
|
successful promotion, write an UPDATE setting `status=promoted` so the lesson
|
|
268
268
|
stops re-suggesting and stands as an audit trail of why the rule exists.
|
|
269
269
|
|
|
270
|
+
A recurring lesson can be promoted a second time, past the prose rule above,
|
|
271
|
+
into a **compiled invariant** — a declarative, mechanically-checked assertion
|
|
272
|
+
instead of text a reader has to notice and act on. Most lessons never qualify
|
|
273
|
+
(the failure mode has to be judgement-free and checkable against an
|
|
274
|
+
independent source of truth); when one does, see
|
|
275
|
+
[compiled-invariants.md](./compiled-invariants.md) for the compilability test,
|
|
276
|
+
the `candidate → advisory → gating → retired` ladder, and
|
|
277
|
+
`lorekit invariants candidates`, the scan that surfaces compile candidates from
|
|
278
|
+
the store.
|
|
279
|
+
|
|
270
280
|
---
|
|
271
281
|
|
|
272
282
|
## Entrenchment guards (do not skip these)
|