LoreKitLoreKit docs

Self-improving workflows

Two ways to use LoreKit: reactive capture, where hooks turn a failure into a lesson, and self-improving workflows, where a skill reads its own lessons at the start of every run and writes a retrospective at the end. Covers the loop, what makes a good lesson, how to tell the loop works, the guards that stop it reinforcing its own mistakes, and how to record decisions.

LoreKit makes retrieval deterministic and leaves reasoning to the model. It decides which lessons reach an agent, and when; the agent decides what they mean. This guide builds on that split: a skill that reads what it learned last time, does its work, and writes down what it learned this time.

What LoreKit does, and what it leaves to the model

LoreKit doesThe model does
Stores lessons under a scope — branch::, repo::, project::, or global — and returns the most specific copy of a keyDecides whether a delivered lesson applies to the task in front of it
Delivers lessons through lifecycle hooks and error matching, and through memory.list / memory.searchWeighs a lesson against the user's intent and against other lessons
Counts recurrence: writing the same scope + key again increments seen_countDecides when a recurring lesson should become a rule, and writes the rule
Measures usage: how often each lesson is delivered, and how often an agent chooses to open itNotices when two lessons disagree, or when a lesson is wrong
Archives lessons by age and usage through retention policies, and stops delivering a lesson once its TTL runs outWrites the lesson in the first place

Some things LoreKit does not do, by design. It keeps no belief graph and no typed links between memories. It attaches no confidence score to a lesson and propagates nothing from one lesson to another. It does not detect contradictions, and when something a lesson depends on changes, nothing invalidates the lesson. Memory stays small, legible, and in the hot path, with no structure precomputed at write time.

Two mechanisms look close to these and are not:

  • Ranking is not confidence. The session-start injection orders candidates by a fixed, reproducible formula of recency, recurrence, match against the current query, and recorded outcome. That orders delivery. Nothing is stored on the lesson, and nothing flows between lessons.
  • Citations are not links. A write can name the lessons it applied in cited. LoreKit counts those citations so you can see which lessons get used. A citation carries no relationship type, and LoreKit infers nothing from it.

Two usage patterns

Reactive capture (the default)

npx @lorekit/cli install wires this for you. Lifecycle hooks deliver lessons at session start, on a prompt that matches one, and when a tool call fails: the hook searches your lore with the tool name and the error text, and injects up to three matching lessons before the agent retries. At the end of a turn that hit friction — an errored tool call, or the same command repeated in a loop — the hook nudges the agent to write a lesson, and the lorekit-memory skill writes it. A clean session stays silent.

Per-prompt and error-matched recall run on Claude Code. Cursor reads lessons through a rule and an end-of-turn hook.

Reactive capture starts from a failure. Until something goes wrong, nothing is written.

Self-improving workflows

A skill, agent, or workflow owns a loop of its own. It reads its scoped lessons at the start of every run and writes a retrospective lesson at the end. Improvement does not need a failure first: a near-miss, a guess that paid off, and a lesson that was applied and worked are all worth recording.

Reactive captureSelf-improving workflow
Set up bynpx @lorekit/cli install — hooks plus the lorekit-memory skillYou, inside your skill, with the lorekit-setup skill
ReadsAt session start, on matching prompts, on failing tool callsIts own bucket, at the start of every run
WritesAfter a turn with frictionAt the end of every run, and at the skill's own failure points
BucketTag skill::lorekit-memoryTag loop::<host>-lessons, keys <host>-lessons::<slug>
PromotionInsights flags load-bearing lessonsThe skill suggests a reviewed rule change once a lesson recurs

Both patterns use the same store and the same memory.* tools, so one project can run both.

The loop

Read at the start → do the work → write a retrospective → promote when a lesson recurs.

Give the loop its own bucket first, so its lessons stay apart from every other loop's on the same scopes: the tag loop::<host>-lessons and the key namespace <host>-lessons::<slug>, where <host> is the skill's name. Same scope + key overwrites in place, which is what makes recurrence countable.

Read at the start of every run

Read narrow to broad, filter by the bucket tag, and cap what you inject. The lorekit-setup recipe cards default to five lessons per scope.

memory.list { scope: "repo::acme/app", tags: ["loop::deploy-lessons"], limit: 5 }
memory.list { scope: "global",         tags: ["loop::deploy-lessons"], limit: 5 }

Apply each lesson whose Applies when line matches the run as a consideration, not a command. When a lesson conflicts with what the user asked for, the user wins — say so. When a repo:: lesson and a global lesson disagree, the repo:: lesson wins.

If the memory.* tools are not connected, log one line and carry on. The loop must never block the run.

Do the work

Run the skill as normal. Note which lessons changed what you did — the retrospective names them.

Write a retrospective at the end

Ask the 30-second question: would a future run do better if it knew this? If not, write nothing. If so, search for a near-duplicate first, so a recurrence updates the existing lesson instead of adding a second one:

memory.search { q: "<key words of the lesson>", scopes: ["repo::acme/app", "global"], limit: 10 }

Then write it, with every fact the store models in its own field:

memory.write {
  scope:    "repo::acme/app",
  key:      "deploy-lessons::<slug>",
  value:    "<the markdown lesson — see What a good lesson is>",
  tags:     ["loop::deploy-lessons", "source::paid-off"],
  trigger:  "paid-off",
  ttl_days: 90,
  cited:    ["<scope::key of each lesson you applied>"]
}

Writing the same scope and key again is an update: the store increments seen_count by one, and passing ttl_days again restarts the expiry. Never write a count or a date into the lesson body — the columns are the only copies that stay true. If a lesson you applied at the start worked, write the update anyway. A lesson that keeps working is recurrence evidence too.

cited names the lessons that shaped this run. It is the only way LoreKit learns that a lesson was used rather than only shown — see Letting your agent say which lessons it used.

Promote when a lesson recurs

LoreKit counts; your skill decides. A lesson is promotion-eligible when its seen_count reaches 3, or when it carries the status::structural tag because it describes a design gap rather than a one-off. The skill then suggests the promotion in one line, and never acts on its own:

Lesson "<title>" recurred 3 times. Promote it to a permanent guard in <host>?

Promotion is a normal, reviewed edit — LoreKit does not apply it. A global lesson hardens the skill's own source; a repo:: lesson hardens the repo's own rules or docs. Once the edit lands, write an update that adds the status::promoted tag, so the lesson stops re-suggesting itself and records why the rule exists.

On the dashboard, Insights labels lessons that are delivered widely and chosen often Load-bearing — the candidates to promote.

What a good lesson is

A good lesson is an observation, scoped as narrowly as it can be, and short.

  • An observation, not a rigid rule. Write what happened and what worked. The lorekit-memory skill's own contrast: "Running pnpm nx test mcp-core without supabase start fails with a connection refused error; start Supabase first" ages well, while "ALWAYS start Supabase" does not. The next run weighs an observation against its own situation; a commandment gets applied where it doesn't fit.
  • The narrowest scope that fits. For a loop lesson that could go either way, lorekit-setup defaults to global: its Applies when line still has to match before it fires anywhere.
  • Short. One lesson per record, led by the takeaway, at most about 1,500 characters. No transcript excerpts, session IDs, timestamps, or "in this run I…" framing — the next run has none of that context. Never a secret or personal data: drop the lesson instead.
The lesson is about…Scope
A principle that holds in any repoglobal
This repository's code or toolingrepo::{owner}/{repo}
A monorepo-wide conventionproject::{name}
A throwaway detail of one branchbranch::{owner}/{repo}::{branch}

The lorekit-setup skill writes loop lessons to this shape:

# <one-line takeaway — what to do, not what the lesson is about>

**Applies when:** <concrete signal — file glob, task type, tool name, error shape>

**What happened:** <the concrete observable from the run>
**Why:** <root cause, if known; "unknown" is allowed>
**Do this instead:** <prescriptive, actionable, testable instruction>
**Promotion target:** <the rule or step this would harden if promoted, or "none">

Applies when must be concrete, so the read step can match it against a run. Do this instead is advice the next run weighs, never a command it obeys.

Keep everything the store already models out of the body: recurrence is seen_count, expiry is ttl_days, status is a status::<value> tag, and the trigger, host, and origin have fields of their own. lorekit lint flags a body that hides metadata in an HTML comment, front matter, or a key=value header.

When to write

Write when a run produces something a future run would benefit from knowing. These are the moments the lorekit-memory skill writes on, with the trigger value to record:

Momenttrigger
A stuck loop — the same step failing again and againstuck-loop
A repeated command failurecommand-failure
A surprising gotchagotcha
A near-missnear-miss
A wrong assumption that cost timeassumption-wrong
A guess that paid off and should be reusedpaid-off
A lesson written by handmanual

A self-improving workflow adds two moments of its own:

  • The end of every run. Ask the 30-second question even when the run went well. The aw workflow does this in its last phase, so friction is captured on clean runs too, and records it with trigger: "end-of-run" — the field takes any string.
  • When feedback on the run's output arrives. A review comment the author fixed, or one they rejected, is evidence about the reviewer's own judgement. The pr-review example below records both.

Skip a smooth run with nothing durable to say. An empty retrospective is not written.

How to know it works

LoreKit counts two things for every lesson:

  • Delivered (read_count) — every time the lesson is handed to a reader, including bulk memory.list and memory.search results and a person opening it in the dashboard.
  • Chosen (opened_count) — the deliveries where an agent deliberately fetched that one lesson, with memory.read or lorekit read / lorekit show.

Chosen divided by delivered is the lesson's pull-through: how often something went and got it on purpose. A lesson delivered often and chosen rarely costs context without earning it.

Where you see it:

  • Insights places every lesson on a grid of delivered × chosen: Load-bearing (promote it to a rule), Specialist (broaden its scope), Noise tax (prune it first), and Dormant (archive it). A lesson gets a verdict once it is at least 7 days old and has been delivered at least 10 times; until then it is Too new to judge.
  • The Lore Explorer detail sheet shows Recurrence, Delivered, Chosen, and Cited side by side.
  • The Explorer list sorts by Most chosen, and its Chosen filter finds lessons chosen N times or fewer.
  • The CLI previews the lessons nothing has ever chosen:
lorekit groom --scope repo::acme/app --max-opened-count 0

Pull-through measures selection, not influence. A lesson injected at session start is already in the agent's context, and can change what it does without ever being fetched again. That is what cited is for — and why a 0 in Cited means "nobody said so", not "unused". Full detail: Which lessons are earning their place?

For proof that a loop improved something, lorekit-setup asks for one check: after a lesson is promoted to a rule, its failure should stop recurring. Watch the source lesson's seen_count go flat. A new write that matches the promoted lesson's Applies when line means the rule did not hold. A flat count proves something only while the loop is still firing, and a loop you seeded up front has no clean baseline to compare against.

Guards

The main risk of a learning loop is self-reinforcing error: the loop wrongly concludes "approach X always fails", stores it, avoids X forever, and never gathers the evidence that would overturn the belief. LoreKit does not reason about lessons, so these guards live in your skill. They come from lorekit-setup:

  1. Lessons are advisory, never auto-applied. A lesson biases a run. It never disables a gate, skips a step, or changes a limit. The only path from a lesson to changed behaviour is a human-reviewed edit.
  2. Recurrence gates promotion, not a single run. A seen_count of 3 or more, or an explicit status::structural tag.
  3. Every lesson expires. Write ttl_days: 90 and pass it again on every recurrence, so a stale belief decays instead of entrenching. The store enforces the TTL; an expiry written in the prose does nothing.
  4. Contradiction is surfaced, not silently overwritten. The search before every write finds the prior lesson, and a genuine reversal is a reviewed decision. LoreKit does not detect the contradiction: the search puts both lessons in front of the model, and the model has to notice.
  5. The privacy pre-flight is never skipped. A lesson that contains a secret, a token, or personal data is dropped, not written. The bar is higher for repo::, which is team-visible.

lorekit-setup's wiring checklist adds four more:

  • Cap the injection. Read a few lessons per scope into a run — five by default. An uncapped loop crowds out the run it was meant to help.
  • Quarantine, don't delete. Tag a suspect lesson status::quarantine and shorten its TTL. Your read step skips quarantined lessons, so the lesson stops biasing runs but survives long enough for a person to inspect it.
  • Protect what a person has vetted. lorekit pin <scope::key> (or memory.protect) keeps every retention policy and manual groom --run from archiving it.
  • Don't loop an audit step. An adversarial or fresh-eyes pass must not be biased by earlier runs, so it should not read a lessons bucket at all.

Lifecycle

lorekit-setup walks a loop through six steps. npx @lorekit/cli install adds the skill; run /lorekit-setup <skill-name> to start.

StepWhat it answers
FindWhere a loop pays off. Start from recurring pain — lorekit dedupe, lorekit invariants candidates, git fix-and-revert chains — not from a guess
WireAdd the read and write steps from a recipe card: code-changing agent, reviewer, multi-step orchestrator, or CI job
SeedDeliver value on run one with a handful of hand-checked lessons, or leave the loop cold to prove its lift — never both
ProveShow that a real failure stopped recurring after its lesson was promoted
MaintainKeep the loop firing with a canary lesson, check that writes match the read, quarantine bad lessons, and groom the bucket
ScaleTurn one person's loop into a team practice: rediscovery across authors, promotion as review, onboarding from the store

The full skill, with its recipe cards: lorekit-setup.

Worked examples

Both examples come from mthines/agent-skills, and every lesson shown is a real one. The key is the stored key. The body is a short version written from what the skill's own docs record — trimmed to the title, Applies when, What happened, and Do this instead — so the stored wording may differ. Each links to its source.

aw — autonomous development

aw takes a task from plan to a tested draft pull request. Its loop uses the bucket loop::aw-lessons.

  • Reads at the start of planning, and again before implementation and testing: loop::aw-lessons from repo::{owner}/{repo}, then global. Matching lessons are applied as considerations and, in Full mode, listed in the plan under "Lessons applied". Once the plan names the files it will change, aw also reads the repo's shared codebase-knowledge records for those files — read-only.
  • Writes when testing hits its stuck-loop cap, and at the end of every run — CI green, a user-approved stop, or a post-merge bug in the same session — after a retrospective prompt. A lesson that was applied and whose failure did not recur is written back as an update.
  • Promotes at seen_count 3 or status::structural. A global lesson becomes a proposed change to the skill's own source; a repo:: lesson becomes a proposed rule in that repo. Both need a passing confidence gate and the user's approval.

global::aw-lessons::agent0-sandbox-headless-chrome-in-trusted-hosts-mode:

# Download Chrome for Testing directly when the Playwright CDN is blocked

**Applies when:** installing Playwright browsers in an Agent0 sandbox in trusted-hosts mode.

**What happened:** The network allowed `storage.googleapis.com` but blocked the CDN Playwright's installer uses.
**Do this instead:** Download the build Playwright pinned in `browsers.json` from `chrome-for-testing-public`.

The ui-verify skill now does this itself: its browser install falls back to Chrome for Testing (source).

aw-lessons::executor-context-lacks-subagent-dispatch-skips-review-loop:

# Run review-loop from a session that can dispatch sub-agents

**Applies when:** `aw` hands a Full-tier task to the `aw-executor` agent.

**What happened:** The executor could not dispatch sub-agents, so `review-loop` was skipped and the draft PR was marked `NOT REVIEWED`.
**Do this instead:** Dispatch the planner and executor from the top-level session, and re-run `review-loop` there if the executor reports it skipped.

It reached a seen_count of 2, and the workflow's design notes cite it as the reason for that change (source).

pr-review and review-loop — code review

pr-review runs the pr-reviewer agent once; review-loop runs it, for up to five iterations, until every review thread is resolved. The reviewer learns from what happens to its own comments, so it improves without a failure of its own.

  • Reads before it reviews: loop::reviewer-lessons and loop::reviewer-comment-relevance, from repo::{owner}/{repo} and global. Once its impact graph names the changed symbols, it reads the repo's codebase-knowledge records for those symbols and files.
  • Writes during a review, from the reactions and replies to its earlier comments. A 👎 or a "not a bug" reply is evidence toward suppressing that kind of finding in the repo; a 👍, an agreeing reply, or a commit that fixes the flagged line is evidence toward amplifying it. A thread resolved with nothing to corroborate it writes nothing directional. At the end of a run it records what it verified about the symbols and files it traced. A maintainer's /pr-review remember <fact> writes a rule immediately.
  • Promotes after the pull request closes: a finding pattern with 3 or more concordant outcomes becomes a reviewer-lessons entry. A relevance rule turns active only after 3 concordant signals from at least 2 different pull requests — one author's bad afternoon is not a repository preference.

reviewer-lessons::resolved-bot-thread-is-not-evidence-of-a-fix:

# A resolved thread is not evidence of a fix — check HEAD

**Applies when:** deciding whether to drop a finding because an earlier review thread on the same line is resolved.

**What happened:** A bot's thread was marked resolved with no code change. The defect was still live at HEAD, and the finding was dropped.
**Do this instead:** Before dropping the finding, confirm at HEAD that the file still exists and the problem no longer reproduces.

Seen seven times in production, it is now a standing rule in the reviewer: "Resolved is not verified — check HEAD before trusting it" (source).

reviewer-lessons::gate-table-says-pass-while-contract-says-fail:

# Don't print PASS on a verdict line that has warnings

**Applies when:** writing the review report's verdict line when there are warnings but nothing blocking.

**What happened:** A line reading "PASS — no blocking issues" sat beside a FAIL verdict, and readers took it as a contradiction.
**Do this instead:** Write "No blocking issues — N warning(s): …" with no PASS in it.

Also seen seven times in production, it changed the reviewer's report format (source).

Recording decisions

A decision is worth a lesson when a later run might undo it without knowing why it was made. Write it to this shape:

# <the decision, in one line>

**Decision:** <what was chosen, and what was not>
**Rationale:** <the constraint or evidence that decided it>
**Assumptions:** <what has to stay true for the decision to hold>
**Revisit if:** <a concrete signal — a file changing, a version, a limit, a metric>

Store it like any other lesson: the narrowest scope that fits, and a stable key so a revised decision updates in place. If it should not outlive a review, give it a ttl_days — GET /memories?expiring_within_days=N lists what lapses soon, so you can renew it or let it go.

This is a writing convention the model reads, not a LoreKit feature. LoreKit stores the decision and delivers it like any other lesson. It does not parse the four fields, link the decision to the lessons or code it depends on, or invalidate it when an assumption stops being true. Revisit if works only when a later run reads the decision and notices that the condition has been met.

Tip:

More aw read and write snippets are in Use cases. How scopes and tags combine is in Tags & scopes.