Lore indexes your patterns and injects them into agent sessions at precisely the moments they are needed. But indexing alone does not guarantee effectiveness. A pattern must be both discoverable (the search engine finds it) and actionable (the agent follows it). This guide explains how to write patterns that achieve both, based on dogfooding evidence.
Dogfooding revealed a consistent failure mode: patterns that describe what exists do not change agent behaviour. Patterns that state what to do — and why — succeed.
Consider two real patterns from a production knowledge base:
This pattern failed to prevent a known mistake:
## Task runner: just
`just` is the task runner for all local and CI operations. Every quality gate has a recipe.
`just ci` runs them all in sequence — if local CI passes, remote CI passes.
Key recipes: `fmt`, `clippy`, `test`, `deny`, `doc`, `ci`, `setup` (git hooks).The pattern accurately describes the tooling, but it never says always run just ci before
committing. An agent read this, understood the tool existed, and then ran individual cargo clippy
and cargo test commands instead — missing the formatting check and breaking CI.
This pattern consistently prevents its target mistake:
# Atlassian MCP createIssueLink has swapped inward/outward semantics
When creating "Blocks" links via MCP, inwardIssue is the BLOCKER and outwardIssue is the BLOCKED
issue — opposite of intuition.
**Why:** Discovered on 2026-03-12 when all dependency links from a plan were created backwards.
**How to apply:** Every time you use `createIssueLink` with type "Blocks", remember to swap: the
blocker goes in `inwardIssue`, the blocked goes in `outwardIssue`. Verify by reading one ticket
after creating links.The difference is not length or formatting. The effective pattern states what to do, explains why the rule exists, and uses the exact terms an agent would encounter when performing the task.
Tell the agent what to do and what never to do. Passive descriptions ("X is the formatter") leave room for interpretation. Direct imperatives ("Always run X before committing") do not.
Weak (descriptive):
justis the task runner for all local and CI operations.
Strong (imperative):
Always run
just cibefore committing. Never substitute individual commands such ascargo testorcargo clippy— they skip formatting checks and other quality gates thatjust ciruns in sequence.
Imperative voice serves a dual purpose: it is both more actionable for agents and more searchable by
the engine. "Always run just ci before committing" contains the verb "run," the tool name, and the
action context "committing" — all terms an agent might use when the pattern should surface.
When a rule exists because something went wrong, say so. A prohibition grounded in a real failure carries more weight than an abstract best practice.
Without incident context:
Use
--body-fileinstead of--bodywith heredocs forgh pr create.
With incident context:
Use
--body-file /tmp/pr-body.mdinstead of--bodywith inline strings or heredocs. Inline and heredoc approaches get blocked by don't-ask mode permission settings.
The second version explains why — the agent now understands the consequence of violating the rule, not just the rule itself.
Not every pattern originates from a failure, and you should not fabricate incident context where none exists. A configuration reference such as "enable clippy pedantic at warn level" does not need a failure story. Use incident grounding when you have it; omit it when you do not.
A pattern that the search engine cannot find is a pattern that does not exist. Lore uses FTS5 full-text search with porter stemming, which means the words in your pattern's title, tags, and body determine whether it surfaces for a given query.
The vocabulary gap in practice:
A pattern about GitHub pull requests contained the phrase "when creating PRs with gh pr create."
When an agent ran gh pr edit --body, the hook extracted terms like "edit" and "body" — neither of
which appeared in the pattern. The pattern scored zero despite being directly relevant.
The fix is straightforward: mention the verbs and nouns an agent might use. If a pattern applies to creating, editing, and updating pull requests, use all three verbs in the body.
Why not semantic search? You might expect semantic search to bridge this gap — embeddings should understand that "edit" and "create" are related concepts. In practice, semantic search helps with broader conceptual matching, but hook-injected queries are typically short (three to five terms extracted from a tool call), and short queries produce weak embedding signals. FTS5 keyword matching carries most of the weight for these precise, tool-driven lookups. Relying on semantic search alone to compensate for missing vocabulary is a gamble; including the terms directly is a certainty.
Understanding the search pipeline helps you write patterns that surface reliably. This section covers the essentials; see Search Mechanics Reference for the full pipeline.
When an agent uses a tool, the hook extracts search terms from its input:
| Signal | Source | Example |
|---|---|---|
| File extension | file_path in Edit/Write |
.rs → language anchor "rust" |
| Filename terms | file_path basename |
validate_email.rs → "validate", "email" |
| Bash command | description or command field |
cargo clippy → language "rust", terms from description |
| Transcript tail | Last user message (up to 200 bytes) | Additional context terms |
These terms are assembled into an FTS5 query. When a language is detected, the query takes the form
rust AND (validate OR email). When no language is detected, terms are joined with OR.
Two filtering rules remove terms before they reach the search engine:
Short terms are discarded. Any term shorter than three characters is dropped. This means "ci"
(two characters) never reaches the search engine, and a pattern about just ci must contain longer
synonyms such as "continuous integration" or "quality gate."
Stop words are filtered. Sixty common English words are removed from queries, including just,
use, new, run, get, set, add, and all. If your pattern's key concept is also a stop
word, ensure the body contains alternative vocabulary.
The full stop word list:
the, and, for, with, from, into, that, this, then, when, will, has, have, was, are, not, but, can, all, its, our, use, new, let, set, get, add, run, see, how, may, per, via, yet, also, just, some, been, were, what, they, each, which, their, there, about, would, could, should, these, those, other, than, them, your, does, here
Lore uses porter stemming, which reduces words to their root form during both indexing and search. "Testing" and "test" share the same stem, so a query for "test" matches a pattern containing "testing." Similarly, "fakes" matches "fake," and "creating" matches "create."
Stemming helps with morphological variants, but it does not help with synonyms. "Edit" does not stem to "create," so a pattern that says "creating" will not match a query containing "edit."
Before finalising a pattern, audit its term coverage. Use lore ingest --file to index the draft
without committing it — the full edit → ingest → search loop runs against your working tree, no WIP
commit required:
-
List the verbs and nouns an agent would use when the pattern should surface. For a pattern about pull request workflows, this might include: create, edit, update, merge, push, branch, PR, pull request, draft, review.
-
Index the draft file directly:
lore ingest --file patterns/my-new-pattern.md
-
Search for your pattern using the terms from step 1:
lore search "edit pull request" --top-k 3 lore search "merge branch workflow" --top-k 3
-
If your pattern does not appear in the results, add the missing terms to the body or tags and re-run
lore ingest --file patterns/my-new-pattern.md. Repeat until every query from step 1 surfaces the pattern. -
Remember that stop words and short terms are invisible. If a key concept such as "CI" is too short, include a longer form such as "continuous integration" or "quality gate pipeline."
Single-file ingest does not touch delta-ingest state, so the next lore ingest (against the whole
repository) still sees real git changes. It also respects .loreignore by default; pass --force
alongside --file to index a file that is otherwise excluded.
Interaction hazard — lore ingest can wipe chunks you just upserted. Single-file ingest is
orthogonal to git state, but walk-based delta ingest is not. If you single-file-ingest a file that
was deleted in git history between the last walk-based ingest and HEAD, the next lore ingest
will observe the deletion in git diff and remove the chunks you just added. Concrete example: you
git rm draft.md and commit, then recreate draft.md in the working tree and run
lore ingest --file draft.md. The single-file ingest lands. Running lore ingest afterwards will
silently undo it. The safe workflow is to finish iterating with lore ingest --file, commit the
file to git, and only then run lore ingest.
Automating this loop with the /coverage-check skill. If you are authoring patterns inside a
Claude Code session with the lore plugin installed, the /coverage-check <pattern-file-path> skill
automates the entire loop above: it derives the candidate query set by simulating the PreToolUse
hook's own query extraction on synthetic tool calls (via the lore extract-queries subcommand),
ingests the draft via lore ingest --file, searches in parallel, scores per-query coverage,
suggests concrete edits to close gaps, and iterates until the surfaced-query set stabilises. Because
the queries are hook output rather than author paraphrase, the report approximates production
discoverability more closely than the manual loop. See
integrations/claude-code/skills/coverage-check/SKILL.md for the full contract.
Tags appear in YAML frontmatter and are indexed as a separate FTS5 column with five times the weight of body text. Use them to boost discoverability for terms that do not appear naturally in the body.
Tags are useful when they add vocabulary the body lacks. If your pattern about error handling also applies to "anyhow" and "result types," adding those as tags ensures the pattern surfaces for queries containing those terms — even if the body text uses different phrasing.
Tags are redundant when they duplicate terms already prominent in the title or body. Adding
tags: [error, handling] to a pattern titled "Error Handling" and containing the phrase "error
handling" in every paragraph adds no search value.
Practical guidance:
- Use three to seven tags per pattern
- Include abbreviations, tool names, and domain terms that agents might search for
- Avoid generic tags such as "best-practice" or "convention" — they match too broadly
- Tags are comma-separated in YAML:
tags: [rust, clippy, linting, code-quality]
A pattern tagged universal opts into lore's always-on injection tier. Its full body is emitted at
every SessionStart (and re-emitted at every PostCompact) under a dedicated
## Pinned conventions section, AND it bypasses the PreToolUse dedup filter so it re-injects on
every relevant tool call instead of being suppressed after the first appearance. The relevance gate
stays intact — the pattern still has to match the agent's tool call to inject.
The header in the SessionStart payload reads ## Pinned conventions rather than
## Universal patterns because that phrasing reads better as agent-facing copy. Both names refer to
the same mechanism: the tags: value is universal; the section header is ## Pinned conventions.
Use universal for patterns where:
- The pattern is a process-level convention (commit messages, push discipline, branch naming, PR etiquette, code review process). These rules need continuous reinforcement throughout a session, not one-shot relevance.
- One reminder is not enough. The motivating example was a
git pushfailure mid-session because dedup had correctly suppressed the workflow pattern after its first appearance hours earlier. - The body is small enough to justify per-call re-injection. A 2 KB universal pattern matched 50 times in a session adds 100 KB of repeated context. Lore emits a per-pattern advisory at ingest time when any single universal body exceeds 1 KB.
Do not use universal for:
- Code-style conventions whose authority is naturally scoped to specific file types or tool calls (Rust naming, TypeScript imports). The PreToolUse hook surfaces these correctly without the always-on cost.
- Reference material the agent reads once at the top of a session and remembers (terminology, architecture overviews). The standard pattern index handles these.
- Long-form documentation. Universal bodies should be short and directive. If your pattern needs more than ~1 KB to express, split it.
Operational notes:
- Keep the count low. Lore emits a stderr advisory when more than three patterns are tagged
universalso authors notice the count growing past intent. - Tag changes take effect at the next
lore ingest. Both adding and removing the tag work the same way — re-ingest the file (delta or single-file) and the next session reflects the new state. - The tag is case-sensitive:
universalis the only form that opts in. Lore emits a near-miss advisory at ingest if it seesUniversal,UNIVERSAL, or any other case-shifted variant in a pattern's tag list. - Universal patterns are exempt from the coverage-check skill's discoverability scoring (they bypass the channel coverage-check measures), but the report marks them so authors do not pointlessly chase low coverage scores on a pattern that always re-injects.
Lore splits each markdown file into chunks by heading and indexes them separately. However, when any chunk in a file matches a search query, lore injects every chunk from that file into the agent's context. This sibling chunk expansion is intentional — it ensures the agent receives the full picture rather than a fragment. But it has a direct consequence for how you organise your pattern files.
Group by domain, not by convenience. Each file should contain sections that belong together. A file about "Git Workflow" with sections on branching, commits, and pull requests is cohesive — when one section matches, the others are almost certainly relevant too. A file that covers "Git Workflow" and "YAML Formatting" in the same document is not — a YAML edit would drag in git conventions, wasting context window space on irrelevant content.
Keep files focused but not fragmented. The goal is not one section per file. A pattern file with a single three-line section provides little value from sibling expansion. Aim for files that cover one coherent domain in enough depth to be useful as a group — typically three to eight sections.
Practical guidance:
- One concern per file: all sections should relate to the same domain or workflow
- Split a file when sections serve different audiences or trigger on different queries
- Merge files when you find yourself duplicating context across several tiny patterns about the same topic
- A good test: if the agent is editing a Rust file and one section matches, would the other sections in this file also be useful? If not, they belong in a separate file
A pattern repository often contains markdown files that are not patterns: a README, a CONTRIBUTING guide, a LICENSE, or documentation about the patterns themselves. If these files end up in the index, they pollute search results — an agent looking for a pattern about error handling does not want a chunk from your README about how to clone the repository.
Place a .loreignore file at the root of your pattern repository to exclude them. The syntax is the
same as .gitignore:
# Repository documentation
README.md
CONTRIBUTING.md
LICENSE
# Tooling and CI
.github/
ci/
# Drafts you do not want indexed yet
drafts/
**/*.draft.md
# Negation: explicitly include a file matched by an earlier pattern
!drafts/important.md
Lore reads .loreignore from the repository root only — nested files in subdirectories are not
supported. Patterns support bare filenames, trailing-slash directories, wildcards, recursive globs
(**/*.draft.md), anchoring (a leading slash anchors a pattern to the repository root), and
negation (!).
When you add or modify .loreignore, run lore ingest afterwards. The next ingest detects the
change and reconciles the database in both directions: files that newly match an exclusion are
removed, and files that are no longer excluded are re-indexed automatically. The same applies when
you delete .loreignore entirely — every file that had been excluded comes back into the index on
the next lore ingest.
Why is
.loreignoreopt-in? Without a.loreignorefile, every markdown file in the repository is indexed, exactly as before. The feature is purely additive — you only encounter it when you choose to.
A pattern that reads like a README section — describing what exists without stating what to do.
Example: "dprint is the single formatter for all file types" tells the agent what dprint is, but
not that it must run dprint check or just fmt before every commit.
Fix: Add the behavioural mandate. State the action, the trigger, and the consequence of omitting it.
A pattern that uses narrow terminology, missing the words agents actually search for.
Example: A pattern about "creating PRs" that never mentions "editing" or "updating." When an agent edits a PR, the hook extracts "edit" — a term the pattern does not contain — and the pattern scores zero.
Fix: Use the vocabulary coverage technique. List the verbs and nouns an agent would use, then verify each appears in the body or tags.
A pattern whose key concept is invisible to search because critical terms are stop words or too short.
Example: A pattern about just ci where "just" is a stop word and "ci" is only two characters.
When an agent runs just ci, the hook extracts zero queryable terms, and no search occurs for that
tool call.
What helps: Include terms that surface the pattern through adjacent queries. "Quality gate,"
"continuous integration," and "pre-commit check" give the search engine vocabulary to find the
pattern when the agent edits a file, runs a related command, or when the user's message provides
context. The pattern will not surface specifically during a just ci invocation — that is a
pipeline limitation — but it will surface during the surrounding workflow.
A single file covering unrelated topics. When any section matches, every section is injected — so a file mixing "Error Handling," "YAML Formatting," and "Docker Configuration" pollutes the agent's context with two irrelevant domains every time one matches.
Example: A file called conventions.md with sections on Rust error handling, Git branch naming,
CI pipeline configuration, and YAML quoting rules. An agent editing a .rs file triggers a match on
the Rust section, and the agent receives all four topics — three of which are noise.
Fix: Split into domain-cohesive files. See File Structure and Grouping above.
A pattern that uses suggestive language ("you might want to consider") rather than directive language ("always," "never").
Example: "Consider using --body-file instead of --body" versus "Use --body-file. Inline
--body with heredocs is blocked by permission settings."
Fix: Replace "consider" and "might want to" with direct imperatives. Agents deprioritise suggestions under time pressure; they follow directives.
When a pattern does not surface as expected, trace the hook pipeline to identify where the breakdown occurs.
Set the environment variable before running a session:
LORE_DEBUG=1 claudeThe debug output writes to stderr with the prefix [lore debug] and shows:
- The extracted query terms and assembled FTS5 query
- The search results with scores and source files
- The deduplication decisions (which chunks were filtered as already injected)
- The final injected content
This tells you whether the problem is an injection gap (the query did not produce terms that match your pattern) or a compliance gap (the pattern was injected but the agent did not follow it). Injection gaps are solved by improving vocabulary coverage. Compliance gaps are solved by strengthening imperative voice and incident grounding.
Run through these checks before committing a new or updated pattern:
- Imperative voice. Does the pattern state what to do and what not to do? Would an agent reading it know exactly how to act?
- Vocabulary coverage. Do the title, body, and tags contain the terms an agent would use
when this pattern should surface? Have you tested with
lore search? - Stop-word avoidance. Are key concepts expressed with terms longer than two characters and not in the stop-word list? If a critical term is a stop word, have you included a longer synonym?
- Incident grounding. If this rule exists because of a past failure, does the pattern explain what happened and why? (Skip this check for patterns without incident history.)
- Tag relevance. Do the tags add vocabulary that the body lacks, rather than duplicating prominent terms?
- Stemming awareness. Are you relying on exact word matches where stemming would help? "Testing" and "test" share a stem, but "edit" and "create" do not.
- Actionable structure. Does every section pass the "so what?" test — can a reader act on it immediately?