@lorekit/cli 1.68.0 → 1.70.0
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/README.md +29 -8
- package/package.json +1 -1
- package/skill/lorekit-groom/rules/grooming-pass.md +34 -0
- package/skill/lorekit-memory/SKILL.md +20 -2
- package/skill/lorekit-memory/references/scope-resolution.md +23 -1
- package/skill/lorekit-setup/SKILL.md +153 -92
- package/skill/lorekit-setup/rules/ci-state-records.md +1 -1
- package/skill/lorekit-setup/rules/cold-start-seeding.md +102 -0
- package/skill/lorekit-setup/rules/compiled-invariants.md +9 -8
- package/skill/lorekit-setup/rules/loop-health.md +117 -0
- package/skill/lorekit-setup/rules/proving-improvement.md +111 -0
- package/skill/lorekit-setup/rules/self-improvement-loops.md +196 -41
- package/skill/lorekit-setup/rules/team-and-portfolio.md +107 -0
- package/skill/lorekit-setup/templates/README.md +26 -0
- package/skill/lorekit-setup/templates/ci-job.md +90 -0
- package/skill/lorekit-setup/templates/code-changing-agent.md +89 -0
- package/skill/lorekit-setup/templates/multi-step-orchestrator.md +76 -0
- package/skill/lorekit-setup/templates/reviewer-reconcile-host.md +86 -0
- package/src/commands/invariants.mjs +17 -10
- package/src/commands/lint.mjs +4 -2
- package/src/commands/show.mjs +244 -3
- package/src/shared/candidates-pure.mjs +94 -5
- package/src/shared/lessons-view.mjs +71 -0
- package/src/shared/mirror-pairs.mjs +9 -0
- package/src/shared/scope-precedence.mjs +82 -0
- package/src/store/local.mjs +95 -1
- package/src/store/remote.mjs +60 -3
- package/src/surfaces.generated.mjs +14 -17
package/README.md
CHANGED
|
@@ -277,6 +277,25 @@ missing. It exits **non-zero** when the key is found in no readable store, so it
|
|
|
277
277
|
fits scripts. `--json` emits the full normalized record(s) and which store each
|
|
278
278
|
came from. Both a scope and a key are required (else a usage error).
|
|
279
279
|
|
|
280
|
+
#### Batch mode: several references in one call
|
|
281
|
+
|
|
282
|
+
Pass **two or more** references and `show` fetches all of them in one pass
|
|
283
|
+
instead of one invocation per lesson:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
lorekit show global::prefer-guard-clauses repo::acme/widget::build-flags --json
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Each positional must itself parse as a complete `<scope>::<key>` reference —
|
|
290
|
+
that is what tells this apart from the existing `show <scope> <key>`
|
|
291
|
+
two-positional form, whose first positional is a **bare** scope with no `::`.
|
|
292
|
+
Results are reported in the same order the references were given, and
|
|
293
|
+
`--json` emits `{ "results": [{ "scope", "key", "offline", "remote", "diverged" }, …] }`
|
|
294
|
+
instead of the single-reference top-level shape. The remote half of a batch is
|
|
295
|
+
ONE round trip (`POST /memories/read`), not one request per reference; the
|
|
296
|
+
offline half still resolves each reference through the two-tier store, so a
|
|
297
|
+
project-tier lesson still shadows a same-key home-tier one.
|
|
298
|
+
|
|
280
299
|
### Addressing a memory: `<scope::key>`
|
|
281
300
|
|
|
282
301
|
`show`, `write` and `link` all take a memory the same way, and the single-token
|
|
@@ -523,10 +542,11 @@ lorekit invariants candidates --scope repo::owner/repo
|
|
|
523
542
|
```
|
|
524
543
|
|
|
525
544
|
A cluster is a candidate when the **summed `seen_count`** across its members
|
|
526
|
-
is at least `--min-seen-count` (default `3`), or any member
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
545
|
+
is at least `--min-seen-count` (default `3`), or any member already declares a
|
|
546
|
+
non-`"active"` status. Status is read from a `status::<value>` **tag** — the
|
|
547
|
+
canonical home, per the `lorekit-setup` skill's `self-improvement-loops.md` —
|
|
548
|
+
falling back to a legacy `<!-- meta: … status=… -->` body comment for lessons
|
|
549
|
+
written before tags were the convention; the tag wins when both are present.
|
|
530
550
|
Candidates are ranked by (summed `seen_count` × distinct scopes), descending.
|
|
531
551
|
**For each candidate it prints every memory the merge would collapse** —
|
|
532
552
|
that list is the whole point of the command.
|
|
@@ -536,10 +556,11 @@ whose members already resolve to a named recurrence class is flagged as a
|
|
|
536
556
|
stronger case ("this should join an existing invariant") than a merely
|
|
537
557
|
similar one ("this might be a new class").
|
|
538
558
|
|
|
539
|
-
It deliberately does **not** classify a
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
559
|
+
It deliberately does **not** classify a member's applicability signal — its
|
|
560
|
+
visible `**Applies when:**` body line, or a legacy `trigger-context` meta
|
|
561
|
+
field — into a glob/command/error-shape. That judgment is the human step the
|
|
562
|
+
compile pipeline's "never auto-compile, never auto-gate" rule protects, so the
|
|
563
|
+
raw string is printed, never interpreted — and it does **not** know about
|
|
543
564
|
`compiled_to` (no such field exists yet, so an already-compiled candidate can
|
|
544
565
|
still surface here — a known, named gap, not a silent omission).
|
|
545
566
|
|
package/package.json
CHANGED
|
@@ -60,6 +60,14 @@ Findings are structural, not semantic — each names its rule:
|
|
|
60
60
|
entry has no `kind`, so it renders as a raw JSON blob in every SessionStart
|
|
61
61
|
digest instead of being excluded by `isGeneralLesson`. Set `--kind bus` or
|
|
62
62
|
`--kind signal` on the record.
|
|
63
|
+
- **hidden-metadata** — the body carries an HTML comment, a leading front-matter
|
|
64
|
+
block, or a `key=value` header before the `#` title. This is the **repudiated
|
|
65
|
+
legacy `<!-- meta: seen_count=… status=… trigger-context=… -->` convention** the
|
|
66
|
+
`lorekit-setup` skill once prescribed and now forbids: an HTML comment renders to
|
|
67
|
+
nothing (so a human sees a lesson starting mid-sentence) while a baked-in
|
|
68
|
+
`seen_count` / `status` / `trigger` silently disagrees with the store's own
|
|
69
|
+
column / tag / field. **This is grooming's job to clean, not just report** — see
|
|
70
|
+
the cleanup step below.
|
|
63
71
|
|
|
64
72
|
These are the cheapest wins and the least controversial, so clear them first.
|
|
65
73
|
For each: either **fix it in place** (rewrite a too-short value into a real
|
|
@@ -68,6 +76,32 @@ updates in place) or, if the lesson is genuinely empty of meaning, **queue it fo
|
|
|
68
76
|
removal** in the plan. `lint` exits non-zero while findings remain, which also
|
|
69
77
|
makes it a clean CI gate — a passing `lint` is your Phase 6 proof.
|
|
70
78
|
|
|
79
|
+
### Cleaning a `hidden-metadata` finding (fold, then strip)
|
|
80
|
+
|
|
81
|
+
A hidden-metadata lesson is **fixed in place**, never deleted for it (the prose is
|
|
82
|
+
usually a real lesson wearing a bad header). Recover the facts into their proper
|
|
83
|
+
homes, then strip the block:
|
|
84
|
+
|
|
85
|
+
1. **Read the whole record** (`lorekit show <scope::key> --json`) so you see the raw
|
|
86
|
+
body, including the `<!--` block the SessionStart digest hides.
|
|
87
|
+
2. **Fold each buried fact into its first-class home**, never back into prose:
|
|
88
|
+
- `seen_count=N` → **drop it entirely.** The column is the only true copy; a
|
|
89
|
+
baked number is stale. Do not try to "restore" it — the store's counter is
|
|
90
|
+
authoritative.
|
|
91
|
+
- `status=structural` / `status=promoted` → a `status::<value>` **tag** on the write.
|
|
92
|
+
(`status=active` is the default — drop it.)
|
|
93
|
+
- `trigger-context="…"` → the visible **`Applies when:`** line at the top of the
|
|
94
|
+
body, and/or the `trigger` **field** for the category.
|
|
95
|
+
- `expires=…` / `ttl=…` → a `ttl_days` on the write.
|
|
96
|
+
3. **Rewrite the body as pure markdown** to the lesson shape (takeaway title →
|
|
97
|
+
`Applies when` → bold-labelled paragraphs), with the `<!--`/front-matter/header
|
|
98
|
+
gone, via a `memory.write` to the same `scope`+`key` (updates in place, carrying
|
|
99
|
+
the recovered tags/fields).
|
|
100
|
+
4. **Re-lint** the record to confirm `hidden-metadata` no longer fires.
|
|
101
|
+
|
|
102
|
+
Do this whenever a pass surfaces the finding — that is how the store stops
|
|
103
|
+
re-teaching the pattern to the next agent that reads a neighbouring lesson.
|
|
104
|
+
|
|
71
105
|
## Phase 3 — Dedupe (read-only)
|
|
72
106
|
|
|
73
107
|
```bash
|
|
@@ -120,9 +120,9 @@ Full resolution rules: [references/scope-resolution.md](./references/scope-resol
|
|
|
120
120
|
|
|
121
121
|
| Tool | Use | Token |
|
|
122
122
|
|------|-----|-------|
|
|
123
|
-
| `memory.list` | List lessons for one scope (newest first, tag filter) | read |
|
|
123
|
+
| `memory.list` | List lessons for one scope (newest first, tag filter). `scope` is optional — omit it to list across every scope you can see | read |
|
|
124
124
|
| `memory.search` | Full-text search across scopes (supports `repo::owner/*`) | read |
|
|
125
|
-
| `memory.read` | Read one lesson by scope
|
|
125
|
+
| `memory.read` | Read one lesson by key (`scope` optional — see below) — or several at once via `refs: ["scope::key", …]` (one round trip, up to 32) | read |
|
|
126
126
|
| `memory.write` | Store or update a lesson (same scope+key updates in place) | read+write |
|
|
127
127
|
|
|
128
128
|
Write tools need write permission (`lk_rw_*` or `lk_wo_*`); read tools need read
|
|
@@ -130,6 +130,24 @@ permission (`lk_rw_*` or `lk_ro_*`). A read-only token cannot write and a
|
|
|
130
130
|
write-only token cannot read — if a call fails with an authorization error,
|
|
131
131
|
report it and move on; do not retry.
|
|
132
132
|
|
|
133
|
+
Holding only a bare key, with no scope? Call `memory.read { key }` without a
|
|
134
|
+
`scope`. It resolves across every scope you can see, preferring the most
|
|
135
|
+
specific — `project`, then `branch`, then `repo`, then `global` — and the
|
|
136
|
+
response's `scope` field names the one that answered. If the key also exists
|
|
137
|
+
elsewhere, `other_scopes` lists those: that is your cue to pass an explicit
|
|
138
|
+
`scope` next time, because only you know which one you meant. An omitted scope
|
|
139
|
+
does NOT mean `global`. The same applies to `memory.list` and
|
|
140
|
+
`memory.list_archived`, which simply list across everything instead of
|
|
141
|
+
resolving to one winner.
|
|
142
|
+
|
|
143
|
+
Already know the exact `scope::key` refs you need — from a prior `memory.list`
|
|
144
|
+
or `memory.search`, or from citations another lesson names — and need more than
|
|
145
|
+
one? Pass them all as `memory.read`'s `refs` array instead of one call per
|
|
146
|
+
lesson. Each ref resolves independently: an unresolved one lands in the
|
|
147
|
+
response's `missing` list rather than failing the whole call, and matching is
|
|
148
|
+
against the **verbatim stored scope** (no case-folding), unlike a single
|
|
149
|
+
`scope`/`key` read.
|
|
150
|
+
|
|
133
151
|
Every lesson this skill writes carries the tag `skill::lorekit-memory` plus a
|
|
134
152
|
`source::<trigger>` tag (for example `source::stuck-loop`) so lessons are
|
|
135
153
|
easy to find and audit later.
|
|
@@ -37,6 +37,27 @@ Query most specific first, then merge; more specific scopes win on key collision
|
|
|
37
37
|
branch::{owner}/{repo}::{branch} → repo::{owner}/{repo} → project::{name} → global
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Reading without a scope
|
|
41
|
+
|
|
42
|
+
`memory.read`, `memory.list` and `memory.list_archived` all take an **optional**
|
|
43
|
+
`scope`. Omitting it does not mean `global` — it means "every scope I can see".
|
|
44
|
+
|
|
45
|
+
`memory.read { key }` resolves the key across all of them and returns ONE
|
|
46
|
+
lesson, preferring the most specific scope type:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
project::{name} → branch::{owner}/{repo}::{branch} → repo::{owner}/{repo} → global
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
(Same precedence as the read order above, ranked by scope TYPE rather than by
|
|
53
|
+
your own scopes — a hosted call has no working directory to derive them from.)
|
|
54
|
+
Ties within a type break by most-recently-updated. The response's `scope` names
|
|
55
|
+
the scope that answered; `other_scopes`, when present, lists the ones it
|
|
56
|
+
shadowed — pass an explicit `scope` next time if that is not the one you meant.
|
|
57
|
+
|
|
58
|
+
`memory.list` / `memory.list_archived` simply widen instead: every entry carries
|
|
59
|
+
its own `scope`, and nothing is resolved away.
|
|
60
|
+
|
|
40
61
|
## Write scope (retrospective)
|
|
41
62
|
|
|
42
63
|
Use the narrowest scope that correctly describes where the lesson applies:
|
|
@@ -54,7 +75,8 @@ Use the narrowest scope that correctly describes where the lesson applies:
|
|
|
54
75
|
```
|
|
55
76
|
|
|
56
77
|
Wildcards work **only** in `memory.search` — not in `memory.list`,
|
|
57
|
-
`memory.read`, or `memory.write`.
|
|
78
|
+
`memory.read`, or `memory.write`. On the two read tools, omitting `scope`
|
|
79
|
+
entirely (above) is the way to reach past a single scope.
|
|
58
80
|
|
|
59
81
|
## Validation rules
|
|
60
82
|
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lorekit-setup
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
a
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
mistakes", "self-improving memory", "memory in CI", "GitHub Actions state",
|
|
22
|
-
"remember the last CI run", "/lorekit-setup".
|
|
4
|
+
Turns a skill, workflow, agent, or CI job into one that gets better across runs
|
|
5
|
+
and can PROVE it. Walks a six-step lifecycle — Find where a loop pays off, Wire
|
|
6
|
+
it from a ready-made recipe card, Seed it so it delivers on run one, Prove it
|
|
7
|
+
improved something, Maintain it so it does not rot, and Scale it to a team — with
|
|
8
|
+
a 15-minute quickstart that ends in a loop firing once, live. Under the hood it
|
|
9
|
+
is a LoreKit lessons loop: a fast episodic tier of advisory lessons read at the
|
|
10
|
+
start of every run and written on failure; a slow procedural tier that promotes a
|
|
11
|
+
recurring lesson into a permanent rule; and, for the rare judgement-free case, a
|
|
12
|
+
third rung that compiles a lesson into a mechanically-checked CI invariant. Also
|
|
13
|
+
covers the non-LLM case (durable JSON state records for a deterministic job) and
|
|
14
|
+
the guards that stop a learning loop from reinforcing its own mistakes. Runtime
|
|
15
|
+
reading and writing of lessons is the lorekit-memory skill; this is the authoring
|
|
16
|
+
counterpart. Use when giving a host durable cross-run memory or wiring a lessons
|
|
17
|
+
loop. Triggers on "set up memory for my skill", "add a self-improvement loop",
|
|
18
|
+
"give my workflow memory", "make this learn from its mistakes", "where should I
|
|
19
|
+
add a loop", "prove my loop is working", "self-improving memory", "memory in CI",
|
|
20
|
+
"GitHub Actions state", "remember the last CI run", "/lorekit-setup".
|
|
23
21
|
user-invocable: true
|
|
24
22
|
argument-hint: '[host-name]'
|
|
25
23
|
license: MIT
|
|
26
24
|
metadata:
|
|
27
25
|
author: mthines
|
|
28
|
-
version: '
|
|
26
|
+
version: '2.0.0'
|
|
29
27
|
workflow_type: memory-loop-authoring
|
|
30
28
|
tags:
|
|
31
29
|
- lorekit
|
|
@@ -41,23 +39,87 @@ metadata:
|
|
|
41
39
|
|
|
42
40
|
# LoreKit Setup
|
|
43
41
|
|
|
44
|
-
Give a host durable cross-run memory
|
|
45
|
-
**self-improvement loop**: it reads its own
|
|
46
|
-
every run and hardens the proven ones into
|
|
47
|
-
more it runs. For a deterministic host — a
|
|
48
|
-
reads what was true at the end of its last
|
|
42
|
+
Give a host durable cross-run memory, and make its improvement **visible**. For a
|
|
43
|
+
model-driven host that means a **self-improvement loop**: it reads its own
|
|
44
|
+
accumulated lessons at the start of every run and hardens the proven ones into
|
|
45
|
+
permanent rules, so it gets better the more it runs. For a deterministic host — a
|
|
46
|
+
CI job — it means **state records**: it reads what was true at the end of its last
|
|
47
|
+
run instead of rediscovering it.
|
|
48
|
+
|
|
49
|
+
This is the **authoring** counterpart to `lorekit-memory`. `lorekit-memory` does the
|
|
50
|
+
runtime read/write of individual lessons; `lorekit-setup` wires the durable memory
|
|
51
|
+
that calls those primitives on a host's behalf. Both run on the same LoreKit store —
|
|
52
|
+
over the `memory.*` MCP tools for agents, over the `lorekit` CLI or REST for jobs.
|
|
53
|
+
|
|
54
|
+
> A loop that nobody can see helping is indistinguishable from no loop. This skill
|
|
55
|
+
> treats "the host measurably got better" as the deliverable — not "a loop is
|
|
56
|
+
> wired." Every step below is judged against that.
|
|
57
|
+
|
|
58
|
+
## The lifecycle (start here)
|
|
59
|
+
|
|
60
|
+
A working loop is six steps. Do them in order; each links to the rule that covers it.
|
|
61
|
+
|
|
62
|
+
| # | Step | What it answers | Read |
|
|
63
|
+
| - | ---- | --------------- | ---- |
|
|
64
|
+
| 1 | **Find** | *Where* does a loop actually pay off? (Do not guess — the data already knows.) | [rules/self-improvement-loops.md § Find where a loop pays off](./rules/self-improvement-loops.md#find-where-a-loop-pays-off) |
|
|
65
|
+
| 2 | **Wire** | Add the read/write steps with the least effort — copy a recipe card. | [templates/](./templates/) + [rules/self-improvement-loops.md](./rules/self-improvement-loops.md) |
|
|
66
|
+
| 3 | **Seed** | Deliver value on run **one**, before any lesson has been earned. | [rules/cold-start-seeding.md](./rules/cold-start-seeding.md) |
|
|
67
|
+
| 4 | **Prove** | Show the loop reduced a real failure — not just that lessons exist. | [rules/proving-improvement.md](./rules/proving-improvement.md) |
|
|
68
|
+
| 5 | **Maintain** | Keep it firing, keep the bucket clean, roll back a bad lesson. | [rules/loop-health.md](./rules/loop-health.md) |
|
|
69
|
+
| 6 | **Scale** | Turn one person's loop into a team practice that compounds. | [rules/team-and-portfolio.md](./rules/team-and-portfolio.md) |
|
|
70
|
+
|
|
71
|
+
If invoked with a `host-name`, set up memory for that host and walk this lifecycle.
|
|
72
|
+
Otherwise ask which skill / workflow / agent / job it is for, pick the shape from
|
|
73
|
+
[Pick the shape](#pick-the-shape), then walk it.
|
|
74
|
+
|
|
75
|
+
## Quickstart (15 minutes, one loop firing once)
|
|
76
|
+
|
|
77
|
+
The shortest path from "I have a host" to "I watched the loop close." Do it against a
|
|
78
|
+
real host with `memory.*` connected.
|
|
79
|
+
|
|
80
|
+
1. **Name the bucket.** From the host's name `<host>`: tag `loop::<host>-lessons`,
|
|
81
|
+
key namespace `<host>-lessons::<slug>`. One bucket per host.
|
|
82
|
+
2. **Copy a recipe card.** Pick the archetype in [templates/](./templates/) that
|
|
83
|
+
matches your host (code-changing agent, reviewer/reconcile host, multi-step
|
|
84
|
+
orchestrator, CI job) and paste its read step at the host's start and its write
|
|
85
|
+
step at the host's existing failure points. The card already carries the
|
|
86
|
+
injection cap, the TTL, and the entrenchment defaults — do not hand-roll them.
|
|
87
|
+
3. **Seed one real lesson** (optional but recommended) so run one is not empty —
|
|
88
|
+
[rules/cold-start-seeding.md](./rules/cold-start-seeding.md). Keep it to a
|
|
89
|
+
*handful*, hand-checked.
|
|
90
|
+
4. **Fire it once, on purpose.** Trigger the failure the loop is meant to catch.
|
|
91
|
+
Confirm a lesson was written to the right scope with the right tag:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
memory.list { scope: "<the scope you expect>", tags: ["loop::<host>-lessons"], limit: 10 }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
5. **Run again and watch it read back.** Start a second run over the same
|
|
98
|
+
situation. Confirm the lesson surfaces in the read step and biases the run.
|
|
99
|
+
That is the loop closing — the whole point, demonstrated once.
|
|
100
|
+
6. **Write down the success metric** you will use to prove it keeps helping —
|
|
101
|
+
[rules/proving-improvement.md](./rules/proving-improvement.md). The required one
|
|
102
|
+
is the *immunity re-challenge*: after you promote a lesson to a rule, the failure
|
|
103
|
+
signature should stop recurring.
|
|
104
|
+
|
|
105
|
+
That is a live, working loop. Everything below is depth on each step.
|
|
106
|
+
|
|
107
|
+
## Pick the shape
|
|
108
|
+
|
|
109
|
+
Two kinds of host want memory, and they want a different record. Decide which first:
|
|
49
110
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
111
|
+
| The host is… | Wants | Read |
|
|
112
|
+
| ------------ | ----- | ---- |
|
|
113
|
+
| 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) |
|
|
114
|
+
| 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) |
|
|
115
|
+
| 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) |
|
|
116
|
+
|
|
117
|
+
A host can want both, in separate buckets: the state record carries *what is true
|
|
118
|
+
right now*, the lesson carries *what we learned about it*.
|
|
55
119
|
|
|
56
120
|
## The rungs of a lessons loop (in one screen)
|
|
57
121
|
|
|
58
|
-
The runtime loop
|
|
59
|
-
promotion, for the minority of recurring lessons that can be turned into a
|
|
60
|
-
mechanically-checked rule instead of text a reader has to notice.
|
|
122
|
+
The runtime loop is two tiers, fast and slow; a third, rarer rung sits past promotion.
|
|
61
123
|
|
|
62
124
|
| Rung | Mechanism | Changes behavior? | Advisory or enforced? |
|
|
63
125
|
| ---- | --------- | ----------------- | ---------------------- |
|
|
@@ -65,66 +127,65 @@ mechanically-checked rule instead of text a reader has to notice.
|
|
|
65
127
|
| **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) |
|
|
66
128
|
| **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 |
|
|
67
129
|
|
|
68
|
-
A recurrence gate connects the first two: a lesson that recurs (`seen_count >= 3`)
|
|
69
|
-
|
|
70
|
-
keep the fast tier from reinforcing its own wrong conclusions.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
A recurrence gate connects the first two: a lesson that recurs (`seen_count >= 3`) or
|
|
131
|
+
carries the `status::structural` tag becomes promotion-eligible. Entrenchment guards
|
|
132
|
+
keep the fast tier from reinforcing its own wrong conclusions.
|
|
133
|
+
|
|
134
|
+
## Non-negotiables (do not "optimize these away")
|
|
135
|
+
|
|
136
|
+
Five co-requirements make the difference between a loop that helps and one that
|
|
137
|
+
quietly rots or crowds out the run it was meant to help. Every recipe card bakes
|
|
138
|
+
them in; if you wire by hand, wire these too:
|
|
139
|
+
|
|
140
|
+
1. **A lesson body is markdown for humans — nothing hidden.** No HTML comment, no
|
|
141
|
+
front-matter, no JSON blob, no `key=value` header. Every fact the store already
|
|
142
|
+
models goes in its own field, never restated in prose — see
|
|
143
|
+
[the body contract](#a-lesson-body-is-markdown-for-humans--nothing-hidden) below.
|
|
144
|
+
2. **Every loop declares a per-host injection cap.** A loop reads *at most* N lessons
|
|
145
|
+
into a run (the cards default to a small N). Many cheap loops with no cap tax
|
|
146
|
+
every session's context until agents ignore injected lore entirely.
|
|
147
|
+
3. **Every lesson expires.** `ttl_days` on the write, refreshed on recurrence, so a
|
|
148
|
+
stale belief decays instead of entrenching. Decay is automatic, never staffed.
|
|
149
|
+
4. **Lessons are advisory, never auto-applied.** The only path from a lesson to
|
|
150
|
+
changed behavior is the human-reviewed slow tier.
|
|
151
|
+
5. **Prove it, or it did not happen.** A loop earns its keep only when a real failure
|
|
152
|
+
stops recurring — [rules/proving-improvement.md](./rules/proving-improvement.md).
|
|
153
|
+
|
|
154
|
+
### A lesson body is markdown for humans — nothing hidden
|
|
155
|
+
|
|
156
|
+
The `value` is **markdown a person reads**, and it carries no hidden payload. Every
|
|
157
|
+
fact the store already models goes in its own write field:
|
|
158
|
+
|
|
159
|
+
| Fact | Field, not prose |
|
|
160
|
+
| ---- | ---------------- |
|
|
161
|
+
| Recurrence | `seen_count` — the store increments it on every overwrite |
|
|
162
|
+
| Expiry | `ttl_days` (`clear_ttl` to make permanent) |
|
|
163
|
+
| Status (`structural` / `promoted`) | a `status::<value>` tag |
|
|
164
|
+
| Owning host, bucket kind | `host`, `kind` |
|
|
165
|
+
| Repo / branch / commit / PR | the `origin_*` fields |
|
|
166
|
+
| What triggered the write | `trigger` |
|
|
167
|
+
|
|
168
|
+
A hidden block does not just look untidy — it is *wrong*. A `seen_count=1` baked into
|
|
169
|
+
prose is stale the first time the lesson recurs, while the column that governs
|
|
170
|
+
promotion moves without it; an HTML comment renders to nothing, so a human is shown a
|
|
171
|
+
lesson that begins mid-sentence. The full shape, the field-by-field rationale, and the
|
|
172
|
+
writing rules are [the lesson record](./rules/self-improvement-loops.md#the-lesson-record).
|
|
173
|
+
This contract is now checkable — `lorekit lint` flags a body that violates it, and the
|
|
174
|
+
`lorekit-groom` skill cleans legacy offenders.
|
|
75
175
|
|
|
76
|
-
|
|
77
|
-
before reading further:
|
|
78
|
-
|
|
79
|
-
| The host is… | Wants | Read |
|
|
80
|
-
| ------------ | ----- | ---- |
|
|
81
|
-
| 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) |
|
|
82
|
-
| 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) |
|
|
83
|
-
| 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) |
|
|
84
|
-
|
|
85
|
-
A host can want both, in separate buckets: the state record carries *what is true
|
|
86
|
-
right now*, the lesson carries *what we learned about it*.
|
|
87
|
-
|
|
88
|
-
## Set up a loop (model-driven hosts)
|
|
176
|
+
## The shared codebase-knowledge layer (automatic cross-loop synergy)
|
|
89
177
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
178
|
+
A per-host lessons bucket is private to one host. There is also **one shared bucket
|
|
179
|
+
every code-touching host reads and, under a contract, writes**: `codebase-knowledge` —
|
|
180
|
+
a repo-scoped, structurally-keyed record (`knowledge::<symbol>@<path>` facts,
|
|
181
|
+
`hotspot::<path>` counters) of what the codebase has taught every LoreKit loop that
|
|
182
|
+
touched it. Because the name is fixed and the key is structural, a loop wired by one
|
|
183
|
+
person compounds with a loop wired by another. The full specification is
|
|
184
|
+
[rules/self-improvement-loops.md § Shared codebase-knowledge](./rules/self-improvement-loops.md#shared-codebase-knowledge-the-standard-cross-loop-layer).
|
|
95
185
|
|
|
96
|
-
##
|
|
186
|
+
## Connectivity
|
|
97
187
|
|
|
98
|
-
A
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
codebase has taught every LoreKit loop that touched it. Because the name is fixed
|
|
103
|
-
and the key is structural, a loop wired by one person compounds with a loop wired
|
|
104
|
-
by another: a host about to change code reads the history for exactly the files it
|
|
105
|
-
will touch, and a host that verifies a structural fact contributes it back. That
|
|
106
|
-
is the synergy that appears for a user who wired a single skill and nothing else.
|
|
107
|
-
|
|
108
|
-
Wire it whenever a host changes code (read at its plan/apply seam) or verifies a
|
|
109
|
-
durable structural fact (write under the contract). The full specification — the
|
|
110
|
-
bucket table, the automatic read side, and the seven-bullet multi-writer write
|
|
111
|
-
contract — is [rules/self-improvement-loops.md § Shared codebase-knowledge](./rules/self-improvement-loops.md#shared-codebase-knowledge-the-standard-cross-loop-layer).
|
|
112
|
-
|
|
113
|
-
## Set up CI state (deterministic hosts)
|
|
114
|
-
|
|
115
|
-
Follow [rules/ci-state-records.md](./rules/ci-state-records.md). It covers: when
|
|
116
|
-
LoreKit beats `actions/cache` (and when it does not), the `ci::<job>-state` bucket
|
|
117
|
-
convention, the versioned JSON envelope, the read/write steps with the `lorekit`
|
|
118
|
-
CLI or REST, a full GitHub Actions example, the guards (bounded cardinality, no
|
|
119
|
-
secrets, explicit expiry, never on the critical path, last-write-wins), and a
|
|
120
|
-
wiring checklist.
|
|
121
|
-
|
|
122
|
-
If invoked with a `host-name`, set up memory for that host; otherwise ask which
|
|
123
|
-
skill / workflow / agent / job it is for, pick the shape from the table above,
|
|
124
|
-
then walk that rule file's setup.
|
|
125
|
-
|
|
126
|
-
A lessons loop's runtime tier needs LoreKit's `memory.*` tools connected; if they
|
|
127
|
-
are not, the host's loop is a silent no-op (the slow tier — a normal source edit —
|
|
128
|
-
still works). A CI job needs a `lk_*` token in its environment instead, and
|
|
129
|
-
degrades to its first-run path when the store is unreachable. Designing either
|
|
130
|
-
needs no connection.
|
|
188
|
+
A lessons loop's runtime tier needs LoreKit's `memory.*` tools connected; if they are
|
|
189
|
+
not, the host's loop is a silent no-op (the slow tier — a normal source edit — still
|
|
190
|
+
works). A CI job needs a `lk_*` token in its environment instead, and degrades to its
|
|
191
|
+
first-run path when the store is unreachable. Designing either needs no connection.
|
|
@@ -58,7 +58,7 @@ this rule should say so out loud rather than sell the store.
|
|
|
58
58
|
| | Lesson (`self-improvement-loops.md`) | CI state record (this file) |
|
|
59
59
|
| --- | --- | --- |
|
|
60
60
|
| Author | a model, at the end of a run | a script, deterministically |
|
|
61
|
-
| Value | prose
|
|
61
|
+
| Value | markdown prose only — metadata lives in the store's own fields | JSON (an object, not a bare scalar) |
|
|
62
62
|
| How a reader uses it | **advisory** — a consideration that can be overridden | **authoritative** — parsed and branched on |
|
|
63
63
|
| Recurrence / promotion | yes (`seen_count`, human-gated hardening) | n/a — nothing is inferred, so nothing needs gating |
|
|
64
64
|
| Entrenchment risk | high — the reason those guards exist | none; the risks are cardinality and secrets instead |
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Cold-start seeding — value on run one
|
|
2
|
+
|
|
3
|
+
The honest cold start is a lie. A loop does not really begin empty — the lessons already
|
|
4
|
+
exist, scattered across git history, PR review threads, CI guard scripts, and the
|
|
5
|
+
`obligations-map`. They were just never written down as reusable memory. Seeding
|
|
6
|
+
harvests a *handful* of them so the loop's very first run reads real institutional
|
|
7
|
+
knowledge instead of nothing.
|
|
8
|
+
|
|
9
|
+
This is optional, and it trades one thing for another — read
|
|
10
|
+
[the measurement trade-off](#the-hard-rule-never-seed-a-loop-you-will-measure) before
|
|
11
|
+
you reach for it.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [When to seed (and when not to)](#when-to-seed-and-when-not-to)
|
|
16
|
+
- [The cap: a handful, hand-checked](#the-cap-a-handful-hand-checked)
|
|
17
|
+
- [The hard rule: never seed a loop you will measure](#the-hard-rule-never-seed-a-loop-you-will-measure)
|
|
18
|
+
- [Where the lessons already are](#where-the-lessons-already-are)
|
|
19
|
+
- [The flow](#the-flow)
|
|
20
|
+
- [Guards](#guards)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## When to seed (and when not to)
|
|
25
|
+
|
|
26
|
+
Seed when the host's value is **invisible for its first week** — lessons only accrue on
|
|
27
|
+
repeated runs, so a freshly-wired loop delivers config, not a win, and people abandon
|
|
28
|
+
it before it pays off. A few real seeded lessons make run one useful.
|
|
29
|
+
|
|
30
|
+
Do **not** seed when you intend to *prove* the loop's lift (below), or when you cannot
|
|
31
|
+
find genuinely recurring pain to seed from — a fabricated lesson is worse than an empty
|
|
32
|
+
bucket.
|
|
33
|
+
|
|
34
|
+
## The cap: a handful, hand-checked
|
|
35
|
+
|
|
36
|
+
**Seed a handful (roughly ≤ 5), each one hand-checked. Never bulk-mine.** This is the
|
|
37
|
+
single most important rule here, and it runs against the instinct to "import everything."
|
|
38
|
+
|
|
39
|
+
Bulk seeding floods the bucket with generic, machine-drafted lessons that never get
|
|
40
|
+
opened. That tanks the exact pull-through ratio [proving-improvement.md](./proving-improvement.md)
|
|
41
|
+
reads, triggers the bucket-bloat [loop-health.md](./loop-health.md#keep-the-bucket-from-bloating)
|
|
42
|
+
fights, and buries the few seeds that were actually good — on day one. A small,
|
|
43
|
+
curated set is worth more than a large, automatic one.
|
|
44
|
+
|
|
45
|
+
## The hard rule: never seed a loop you will measure
|
|
46
|
+
|
|
47
|
+
Seeding writes lessons before the loop has run, so it **erases the clean baseline** the
|
|
48
|
+
immunity re-challenge needs. A seeded loop's improvement is unattributable — you cannot
|
|
49
|
+
tell the loop's own learning from the head start you handed it.
|
|
50
|
+
|
|
51
|
+
So it is one or the other, per loop:
|
|
52
|
+
|
|
53
|
+
| You want… | Then… |
|
|
54
|
+
| --------- | ----- |
|
|
55
|
+
| Value on run one | Seed it (a handful, curated). Accept that its lift is not cleanly provable. |
|
|
56
|
+
| Provable lift | Leave it cold. The first weeks are quieter, but the immunity check reads a real baseline. |
|
|
57
|
+
|
|
58
|
+
## Where the lessons already are
|
|
59
|
+
|
|
60
|
+
Mine these — they are recurring pain the team already paid for:
|
|
61
|
+
|
|
62
|
+
- **Git fix/revert chains** — the same pattern fixed the same way 3+ times, or a
|
|
63
|
+
revert-then-refix. `git log`, `git log --grep=revert`, `git log -p` over a hot file.
|
|
64
|
+
- **Recurring PR review comments** — the same review note left across many PRs.
|
|
65
|
+
- **Existing CI guard scripts** — a guard that exists *is* a lesson someone learned;
|
|
66
|
+
seed a `codebase-knowledge` fact pointing at it.
|
|
67
|
+
- **The `obligations-map`** — its path-keyed partnerships are compiled lessons; the
|
|
68
|
+
ones not yet gating are candidates. `lorekit invariants candidates` surfaces clusters.
|
|
69
|
+
- **`lorekit dedupe`** — near-duplicate existing lessons that should be one seed.
|
|
70
|
+
|
|
71
|
+
## The flow
|
|
72
|
+
|
|
73
|
+
1. **Mine** one or two of the sources above for genuinely recurring pain.
|
|
74
|
+
2. **Draft** ≤ 5 candidate lessons to the normal body shape
|
|
75
|
+
([the lesson record](./self-improvement-loops.md#the-lesson-record)) — a takeaway
|
|
76
|
+
title, a concrete **Applies when**, a prescriptive **Do this instead**.
|
|
77
|
+
3. **A human approves** the survivors. This is not skippable; seeding is the one place
|
|
78
|
+
a bad lesson enters with no recurrence evidence behind it.
|
|
79
|
+
4. **Write** each with provenance and a `source::seed` tag, so seeds are auditable and
|
|
80
|
+
distinguishable from earned lessons:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
memory.write {
|
|
84
|
+
scope: "<global | repo::<owner>/<repo>>",
|
|
85
|
+
key: "<host>-lessons::<slug>",
|
|
86
|
+
value: "<markdown lesson body — no hidden blocks>",
|
|
87
|
+
tags: ["loop::<host>-lessons", "source::seed"],
|
|
88
|
+
trigger: "manual",
|
|
89
|
+
origin_commit: "<the commit this was learned from, if any>",
|
|
90
|
+
ttl_days: 90
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Guards
|
|
95
|
+
|
|
96
|
+
- **The body contract still applies** — seeds are markdown for humans, no hidden blocks.
|
|
97
|
+
- **Tag seeds `source::seed`** so [loop-health](./loop-health.md) and grooming can tell a
|
|
98
|
+
head start from an earned lesson.
|
|
99
|
+
- **Privacy pre-flight is not skipped** — a seed mined from history can carry a secret or
|
|
100
|
+
a name; drop it, do not write it. The bar is stricter for `repo::` (team-visible).
|
|
101
|
+
- **Re-check the cap after mining.** If your candidate list is 40 long, you are
|
|
102
|
+
bulk-seeding — cut to the handful that actually recur.
|
|
@@ -34,9 +34,9 @@ A lesson is a compile candidate only if **all four** hold:
|
|
|
34
34
|
1. **Trigger detectable without judgement.** The condition that should fire the
|
|
35
35
|
check can be recognized from a file path, a diff shape, or another mechanical
|
|
36
36
|
signal — not "whether this edit is the kind that matters," which needs a
|
|
37
|
-
reader's judgement. This is exactly what the existing "
|
|
38
|
-
concrete" requirement in the lessons loop exists for: a lesson with a vague
|
|
39
|
-
|
|
37
|
+
reader's judgement. This is exactly what the existing "**Applies when** must
|
|
38
|
+
be concrete" requirement in the lessons loop exists for: a lesson with a vague
|
|
39
|
+
applicability signal was never going to compile, so demanding concreteness at
|
|
40
40
|
write time is what keeps the door open later.
|
|
41
41
|
2. **Assertion statable as must-remain-true.** The rule has to be expressible as
|
|
42
42
|
an invariant ("if A changed, B must also be in the changed set"), not a
|
|
@@ -114,8 +114,9 @@ A read-only survey over the memory store. It reuses `dedupe`'s Jaccard clusterin
|
|
|
114
114
|
to find near-duplicate lessons, then ranks the resulting clusters by
|
|
115
115
|
`(summed seen_count × distinct scopes)`, descending. A cluster is worth
|
|
116
116
|
reporting when the summed `seen_count` across its members is at least
|
|
117
|
-
`--min-seen-count` (default 3), or any member
|
|
118
|
-
|
|
117
|
+
`--min-seen-count` (default 3), or any member already declares a non-`active`
|
|
118
|
+
status — read from a `status::<value>` tag, falling back to a legacy `meta`
|
|
119
|
+
comment for lessons written before tags were the convention. For each candidate it prints every memory the merge would
|
|
119
120
|
collapse — deliberately the default view, not hidden behind `--verbose`, because
|
|
120
121
|
that list *is* the point of the command: a human decides from it whether the
|
|
121
122
|
cluster deserves a hand-written entry.
|
|
@@ -127,9 +128,9 @@ you to notice by hand.
|
|
|
127
128
|
|
|
128
129
|
Two things it deliberately does **not** do, on purpose:
|
|
129
130
|
|
|
130
|
-
- **It does not classify
|
|
131
|
-
|
|
132
|
-
glob/command/error-shape. Turning "parses into a detectable trigger" from a
|
|
131
|
+
- **It does not classify applicability signals.** A lesson's raw signal
|
|
132
|
+
(its **Applies when** line, or a legacy `trigger-context` meta field) is
|
|
133
|
+
printed verbatim — never interpreted into a glob/command/error-shape. Turning "parses into a detectable trigger" from a
|
|
133
134
|
string into an actual predicate is the human step the compile pipeline
|
|
134
135
|
protects; automating it would be exactly the "auto-compile" this pipeline
|
|
135
136
|
refuses to do.
|