Hard expiry
Every session hard-expires after 15 minutes.
Concepts
lore has one kind of thing, a note, and four ways for a note to reach a file: the code cites it, the note anchors the code, it is a CONTEXT.md above the file, or it is linked from a note that does one of those.
A note is a Markdown or Org file under the notes directory, docs/ by default, plus every CONTEXT.md in the repo. Frontmatter is optional. When present, it gives the note a type and fields the schema checks:
---
type: Decision
status: accepted
summary: One line shown in context output. # default: a section or the first paragraph
aliases: [login] # other names that link here
anchors: [pkg/auth/**, "symbol:SessionManager"]
---
The default schema has three types, each with a home directory, so a note in docs/specs/ is a Spec without saying so:
| Type | What it holds | Statuses |
|---|---|---|
| Spec | What something should do, for whom, and what it leaves out. Edited in place. | draft accepted retired |
| Decision | A choice and its reasons. Numbered, and never edited once accepted. | proposed accepted superseded |
| Effort | A plan against one or more specs, with a task checklist. | planned active done |
The types are yours to change in .lore/schema.yml.
A note’s name is its type, lowercased, then its filename: spec/due-dates, effort/due-dates, decision/0002-rotating-refresh-tokens. A numbered note also answers to its number within its type, decision/0002. lore writes every link and message with these names.
So a spec and the effort that builds it can share a filename; the type tells them apart. A bare filename still works while only one note has it, so lore show auth is fine, but lore show due-dates asks which:
lore show due-dates
lore show: 'due-dates' matches 2 notes:
effort/due-dates docs/efforts/due-dates.md
spec/due-dates docs/specs/due-dates.md
When lore new gives a second note a filename that bare links already use, or a number they name a note by, as in [[0003]], it lists those links and the name each one meant, so you can qualify them before lore check complains.
Notes link to each other with double brackets, by name: [[decision/0001]] finds 0001-hard-expiry.md. A filename, path or alias works too while it names one note. A link inside a frontmatter field, such as spec: "[[spec/auth]]", is an edge lore context follows: a file governed by this decision is shown its spec too.
---
type: Decision
status: accepted
supersedes: ["[[decision/0001-hard-expiry]]"]
spec: "[[spec/auth]]"
---
# Rotating refresh tokens
## Context
Hard expiry (see [[decision/0001]]) signed people out mid-task,
and [[spec/auth]] says a session survives a browser restart.
## Decision
Refresh tokens rotate on every use; access tokens last 15 minutes.
Every session hard-expires after 15 minutes.
Who can sign in, how, and for how long.
Field links are edges lore follows; the same links in the body are references a reader follows. lore check reports any link that matches no note, or more than one.
Code points at a note with a comment containing lore: and the note’s name, such as // see lore:decision/0012. lore finds them with ripgrep, in any language.
const ACCESS_TTL = 15 * 60;
// see lore:decision/0002 -- a refresh token is good for one use.
export function refresh(token: string) {
A citation is the strongest match lore knows: it ranks first in lore context. If the decision it names is superseded, lore check says so, and the code gets looked at again.
Where a citation is code pointing at a note, an anchor is a note claiming code. The anchors field takes three forms:
An anchor that matches nothing is a lore check problem, so a note cannot quietly lose the code it governs in a rename.
A CONTEXT.md describes the directory it sits in: what lives there, and the rules for it. Every CONTEXT.md above a file applies to it, nearest first, so pkg/auth/session.ts gets pkg/auth/CONTEXT.md, then pkg/CONTEXT.md, then the root one.
lore context lists notes most relevant first, and says why each one matched:
CONTEXT.md files, nearest first.Output stops at a budget, 4000 tokens unless you pass --budget, so an agent is never handed the whole notes directory.
Decisions are never edited or deleted once accepted. A new decision lists the old one in supersedes, and the old one becomes superseded. lore new --supersedes does both, and moves the old note’s anchors to the new one.
lore new Decision "Rotating refresh tokens" --supersedes 0001
created docs/decisions/0002-rotating-refresh-tokens.md
marked docs/decisions/0001-hard-expiry.md superseded
lore context always shows the decision in force, with one line of what it replaced. lore show walks the whole chain:
lore show decision/0002
Rotating refresh tokens (Decision, accepted)
docs/decisions/0002-rotating-refresh-tokens.md
Refresh tokens rotate on every use; access tokens last 15 minutes.
History (newest first):
▸ decision/0002-rotating-refresh-tokens (accepted)
decision/0001-hard-expiry (superseded)
Links to:
supersedes: decision/0001-hard-expiry → docs/decisions/0001-hard-expiry.md
spec: spec/auth → docs/specs/auth.md
decision/0001 → docs/decisions/0001-hard-expiry.md
spec/auth → docs/specs/auth.md
Cited by code:
pkg/auth/session.ts:4
An effort links the specs it was planned against and tracks its tasks as a checklist. lore freeze writes a hash of each spec’s body into the effort’s frozen field. If a spec changes while the effort is not done, lore check reports it and lore next sends you back to planning, because the tasks may be stale.
---
type: Effort
status: active
specs: ["[[spec/due-dates]]"]
frozen:
docs/specs/due-dates.md: 34dd79dc3cec
---
Once an effort is done, its frozen versions are history. A spec that changes after all its efforts shipped shows up in lore next as new work to plan.
Notes can be Org files, and both kinds sit in one repo: a Markdown effort can plan against an Org spec. Fields are the #+key: value lines at the top, links are [[spec/auth]], [[spec/auth][label]] or [[file:prd.org][the PRD]], and checklists use [X].
#+title: Search
#+status: accepted
#+anchors: [src/search/**]
Find tasks by words in their title. Builds on [[spec/due-dates][due dates]] for filtering.
* Behaviour
- [X] Match whole words, case-insensitively
- [ ] Filter by due date
lore show search
Search (Spec, accepted)
docs/specs/search.org
Find tasks by words in their title. Builds on [[spec/due-dates][due dates]] for filtering.
Checklist: 1 of 2 done
Links to:
spec/due-dates → docs/specs/due-dates.md
Anchors:
src/search/** → 1 file
A value in brackets or quotes is read as YAML, so #+anchors: [a, b] is a list; anything else is text. Set format: org on a type and lore new writes .org notes from .lore/templates/<Type>.org.
lore keeps no database and no cache. Every command reads the working tree, so it can never disagree with the files in front of you, on any branch. On a 21,000-file repository, lore context answers in about a tenth of a second.