# Lore Memory Workflows Use Lore for knowledge that should survive the current session. The hook autosave is a safety net; explicit saves are still the right tool for decisions, gotchas, durable facts, and tracked follow-up work. ## What To Save - **Decisions**: use `lore-decision action='create'` for architectural choices, rationale, alternatives, consequences, and review dates. Pass `affects: [...]` to auto-create `decided_by` facts. - **Memories**: use `lore-memory action='save'` for non-obvious discoveries, debugging insights, gotchas, and workarounds that are not formal decisions. - **Facts**: use `lore-fact action='create'` for relationships between system components, such as `uses`, `depends_on`, or `is_a`. Lore rejects fact creation without resolvable provenance: pass an existing `sourceMemoryId`, or pass `agent` and `session` matching an earlier memory saved in the same process. - **Tasks**: use `lore-task action='create'` for tracked work such as open PRs, blocked dependencies, and follow-up investigations. Close tasks as soon as they are done or cancelled. When a fact becomes stale, invalidate it with `lore-fact action='invalidate'`. That action closes the fact's validity window and updates the fact-side confidence fields; it does not mutate the supporting memory. For recurring corpus hygiene — orphan facts, overdue governance, duplicate clusters, topic sprawl, ownerless rows, scope anomalies, operational expiry gaps, and log-shaped summaries — run [`lore debt scan`](./memory-debt.md) periodically. The scanner is read-only by default; `docs/memory-debt.md` describes the categories, scoring, recommended monthly cadence, and the opt-in `lore debt create-tasks` surface. Memory `synopsis` values are scan hooks, not mini-bodies. `lore-memory` save/update rejects synopses over `memory.synopsisMaxChars`; the default is 150 characters. Vaults that want longer wake-up/recall lines can set: ```yaml memory: synopsisMaxChars: 220 ``` The configurable cap cannot exceed 500 characters, which remains the structural storage ceiling for the Synopsis property. ## Memory Confidence Columns Some vaults still have legacy Memories database columns named `Confidence` and `Confidence Score`. They are read for schema compatibility only; newly generated Memories schemas do not create them. Memory write tools do not accept confidence inputs, memory reads do not rank or render by those columns, and operators should not write them directly. Fact confidence is separate and remains active on the Facts database. ## Conflict Verdicts `lore-memory action='compare'` accepts six verdicts for a pair of memories. `memoryIdA` and `memoryIdB` are unordered labels. For asymmetric verdicts, pass `affectedMemoryId` to name the contradicted or superseded memory. For symmetric verdicts, omit it. | Verdict | Direction | Meaning | | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `conflicts_with` | Asymmetric | A and B make incompatible factual claims in the same scope. `affectedMemoryId` names the contradicted memory. | | `supersedes` | Asymmetric | Decision-kind affected targets only. The other memory is the later, more accurate decision. `affectedMemoryId` names the superseded decision. | | `scoped` | Symmetric | A and B differ, but the difference is explained by project, time, environment, or other scope. | | `related` | Symmetric | A and B share a subject but make non-overlapping claims. | | `compatible` | Symmetric | A and B make near-identical claims. Consider updating one memory if one should become canonical. | | `not_conflict` | Symmetric | A and B are about unrelated subjects. | For non-decision memories that need to be replaced by a new synthesis, do not use `supersedes`. Either update/archive the old memory manually, or promote the synthesis into a formal decision and archive the old non-decision memory. When `lore conflicts scan` surfaces candidate pairs, judge each pair with this vocabulary and call `lore-memory action='compare'` once per pair. The non-actionable verdicts also record that the pair has already been judged, so future scans skip it. ## Topic Keys Use `topicKey` when saving a non-procedure, non-state memory about a recurring topic: a governance decision, runbook, incident, postmortem, or policy that may evolve. The `lore-memory action='save'` path upserts on `Topic Key` plus identical project relation set. A matching memory receives a revision block instead of a new row. Subject-canonical state memories also use Topic Key internally, but callers should not pass `topicKey` directly for them. Use `subject` with `replace: true` so Lore derives the canonical `state/` key consistently. Use stable kebab-case paths grouped by kind: - `decision/jwt-auth-model` - `runbook/database-migration` - `incident/login-redirect-502` - `postmortem/payment-gateway-timeout` - `policy/data-retention` - `state/auth` - `procedure/cache-miss-investigation` Procedure topic keys use the `procedure/` family, but pass them to `lore-procedure action='propose'`, not `lore-memory action='save'`. The procedure propose path uses `topicKey` for proposal idempotency and conflict detection: it reuses an existing proposed row for the same project set and conflicts with accepted or deprecated rows instead of appending revision blocks. If unsure for a non-state durable memory, call `lore-memory action='suggest-topic-key'` with the title and kind. Do not use `topicKey` on `kind: 'note'`, `kind: 'task'`, `kind: 'state'`, or `kind: 'operational'`; state uses the subject-canonical save shape below, and operational rows should expire instead of forming an upsert chain. For current-state summaries whose subject evolves through events, prefer the subject-canonical save shape: ```json { "action": "save", "title": "Auth current state", "content": "External operators use per-user PATs; internal engineers use ntn.", "subject": "auth", "replace": true } ``` Lore stores this as `Kind = state` with topic key `state/auth`. The next save for the same subject appends a revision to that row instead of creating a second wake-up entry. Use `lore-memory action='history' subject='auth'` to read the full revision chain. When a non-procedure `lore-memory` upsert chain grows beyond roughly 5 KB or 5 revisions, the save response surfaces a promotion advisory. For decision chains, promote the synthesis into a fresh formal decision. For non-decision chains, split into narrower topic keys or archive the broad chain and start a more specific one. To re-key a misnamed first save, pass `topicKey` to `lore-memory action='update'` for non-procedure topic-key rows. Re-keying appends a `## Re-keyed (YYYY-MM-DD)` audit block but does not bump `Revision Count`. Combining `topicKey` with `kind` in a single update is rejected. ## Passive Learning Extraction The Stop-triggered background autosave reviews the session transcript and saves a session synopsis. It can also identify atomic learnings and save each as its own memory. Foreground agents should still save important decisions and discoveries explicitly. Do not add a visible `## Key Learnings:` section to user-facing responses, and do not double-save a discovery you already wrote explicitly. Operators can disable extraction with `LORE_DISABLE_LEARNING_EXTRACTION=1` or `hooks.learningExtraction: false` in `.lore.yaml`. `hooks.memoryCaptureMode: durable` is the default engineering-memory policy. `hooks.memoryCaptureMode: conversational` is an explicit opt-in for broad chat recall: user-stated preferences, personal or work context, reminders, commitments, and other future-useful conversational facts the durable filter would skip. Conversational mode routes auto-captured memory saves as proposed by default so broad recall candidates are reviewed before entering default recall. Disabling learning extraction suppresses conversational capture and falls back to durable synopsis-only autosave. For shared-vault deployments, set `hooks.proposeAutosaveLearnings: true` in `.lore.yaml` to route every auto-extracted learning through the proposed-memory review inbox (`Status = proposed`) instead of writing it directly into accepted recall. Default is `false` so existing installs see byte-identical autosave behavior. When enabled, the rows are filtered out of default `lore-query action='recall'` / `lore-context action='wake-up'` until a reviewer approves or rejects them via `lore inbox` (CLI: `lore inbox list`, `lore inbox approve `, `lore inbox reject `, `lore inbox archive `) or `lore-memory action='approve' / 'reject'` (MCP). Both surfaces share the same `MemoryService.recordReview` service path and append a `## Reviewed (YYYY-MM-DD)` audit block with the reviewer + timestamp. Both terminal verdicts drop the row out of the proposed-memory inbox: `approve` makes it eligible for default recall, `reject` keeps it off default recall (the `reviewTerminalStatusExclusionFilters` default-exclude on `MemoryService.list` / `search` covers both `proposed` and `rejected`), so neither verdict pollutes shared recall with noisy auto-extractions. The inbox depth surfaces in `lore status`'s Proposed memories line and the wake-up Proposed Memories section. Has no effect when `hooks.learningExtraction` is `false` — there is no learning save to gate. Atomic learning saves are deduplicated more strictly than ordinary memory saves. In background-agent runs, a `source: "autosave_learning"`, `kind: "note"` save with a session id checks existing autosave-learning notes before creating a row. Project-scoped autosaves reuse same-project-set matches across sessions, including auto-resolved catch-all projects. Projectless autosaves reuse matching unscoped autosave-learning rows across the vault. The dedup gate uses strict structural checks first and, when semantic search is available, a paraphrase candidate lane with lexical support. If Lore cannot read the duplicate candidate set, the autosave learning save fails before creating a possible duplicate. To force a separate row during recovery or migration, set `LORE_DISABLE_AUTOSAVE_LEARNING_DEDUP=1` for that autosave run. ## Procedures (Reusable Procedural Memories) Procedures are reviewed, fleet-wide operating knowledge promoted from resolved episodes — closed tasks, resolved incidents, postmortems, and reviewed notes. Adapted from LangMem's episodic / semantic / procedural taxonomy: episodes stay inspectable history, while `kind: "procedure"` memories carry the "when this situation appears, this sequence worked" guidance an agent reaches for. Procedures always require human or authorized-agent review before they become fleet-wide; raw session summaries never become procedures silently. The lifecycle is three steps: 1. **Mine candidates.** Run `lore procedures scan` (CLI) or `lore-procedure action='scan-candidates'` (MCP). Both are read-only. Lore walks the project's resolved incidents / postmortems / runbooks plus closed tasks, clusters them by entity, and surfaces ranked candidate groups (cluster key, supporting source ids, score). A cluster requires at least two supporting memories before it surfaces. 2. **Propose.** Distill the resolution shape into a procedure via `lore procedures propose --title ... --entity ... --activation ... --step ... --source --source ` (CLI) or `lore-procedure action='propose'` (MCP). The propose path creates a `kind: "procedure", Status: proposed` memory with structured activation conditions, ordered steps (each must be non-blank after trim — a stepless or whitespace-only step is rejected at the schema boundary), optional known failure modes, and `## Sources` pointing back to the supporting episodes. At least two `sourceMemoryIds` are required (mirrors the scan's `PROCEDURE_MIN_SOURCES = 2` threshold) so the propose path can never bypass the auditable evidence trail. Pass `supersedesIds` when replacing an older procedure or runbook. Body sections are pinned so the wake-up surface can label rows distinctly. 3. **Approve.** Review and approve via the existing inbox surface (`lore inbox approve ` CLI or `lore-memory action='approve'` MCP). Approved procedures flip to `Status: accepted` and surface in default recall / wake-up, labeled `procedure` in the meta line. Reject via `lore inbox reject ` if the candidate isn't worth shipping; both approve and reject append a `## Reviewed (YYYY-MM-DD)` audit block recording the reviewer. Procedures can be deprecated via `lore procedures deprecate ` (CLI) or `lore-procedure action='deprecate'` (MCP). Deprecate rejects `Status: proposed` rows — those must leave the inbox via `lore inbox reject` so the `## Reviewed (YYYY-MM-DD)` audit block lands with the reviewer identity. Deprecate is idempotent on already-deprecated rows. **Supersession** is a two-step workflow: at propose time, pass `supersedesIds: []` so the new row's `Supersedes` relation records the chain. After approval, run `lore procedures deprecate --reason "Superseded by "` to flip the predecessor out of accepted recall. The propose response surfaces ready-to-paste deprecate commands when `supersedesIds` is set, so the workflow is explicit and operator-visible. The `lore-memory action='compare'` path with `verdict: 'supersedes'` is **decision-only**: the compare handler rejects non-decision kinds, so it cannot replace a procedure. Activation conditions are also replicated to the memory's `Keywords` field so hybrid search picks up procedures whose activation entity matches the user's current query. ## Operational Memories And Expiry Use `kind: "operational"` only for temporary coordination state: PR poll state, closeout banners, build receipts, migration breadcrumbs, or similar execution notes that explain what just happened but should not become durable project knowledge. Operational rows should carry an expiry contract at creation time: - `expiresAt: "YYYY-MM-DD"` is shorthand for `scope: { lifetime: "expires", expiresAt: "YYYY-MM-DD" }`. Default wake-up and recall exclude the row once `Expires At < today`. - `expiresOn: "pr-closed:owner/repo#123"` or `expiresOn: "task-closed:"` records an event-bound expiry marker. The marker does not hide the row by itself; `lore debt scan` audits operational rows whose linked closure event has happened but whose row has not been archived or date-expired. Task markers resolve through Lore task state; PR markers resolve through GitHub's pull-request API and use `GITHUB_TOKEN` or `GH_TOKEN` for private-repo access. Wake-up excludes expired memories by default. MCP callers that are auditing a specific issue can pass `lore-context action='wake-up' includeExpired: true` to include expired memories while still preserving narrow-scope isolation. This is narrower than the service-internal `includeOutOfScope` escape hatch. Migration path for stale operational rows: ```text lore memory update --expires-at YYYY-MM-DD lore memory update --expires-on pr-closed:owner/repo#123 lore-memory action='update' memoryId='' expiresAt='YYYY-MM-DD' lore-memory action='update' memoryId='' expiresOn='pr-closed:owner/repo#123' ``` Passing `expiresAt: null` or `expiresOn: null` through MCP clears those fields; the CLI clear sentinel is an empty string. ## Digest And Synopsis Quality Digest bodies and `Synopsis` values are scan surfaces, not session-history surfaces. They should state durable signal: decisions, constraints, gotchas, open loops, or reusable operating knowledge. Avoid chronological prose such as "first we checked...", "then we edited...", or "finally we opened a PR." `lore debt scan` audits accepted `source: "digest"` rows and non-empty synopses for log-shaped prose. Rewrite flagged rows into distilled signal, or archive them if they only describe activity. ## Task Hygiene - Create tasks for work that needs cross-session tracking. - Close tasks with `lore-task action='close'` as soon as work completes. - Use `done` for completed work and `cancelled` for abandoned or superseded work. - When saving a memory that resolves tracked work, check wake-up context for related active tasks and close any resolved ones. ### Task Review And Expiry Policy Task review is an operator triage signal, not an automatic lifecycle transition. `Review By` remains the task's review/due date and is the only field used to classify active tasks as overdue. The shared `Lifetime` / `Expires At` columns continue to govern retrieval scope only: use `expires` + `Expires At` for temporary memories or facts whose knowledge should disappear from default recall after a date, and use `until-task-closed` as a declarative label for task-scoped rows whose useful reach ends when the task is closed. Do not treat `Expires At` as a task due date, and do not add a task-specific expiry field. Existing tasks keep their current `Review By`, `Lifetime`, and `Expires At` values. New tasks default to no `Review By` unless the caller passes a due date; callers may still pass the usual scope bundle, including `lifetime: "until-task-closed"` for per-session tracked work. Reuse/update paths may extend `Review By` when the incoming request carries a later due date, but a routine task edit does not automatically push the date forward. Phase 1 behavior is warning-only: - `lore tasks list` / `lore-task action='list'` continue to group active tasks into Overdue and Active sections from `Review By`. - `lore status` / `lore-context action='status'` continue to count overdue active tasks and stale untouched active tasks. - `lore debt scan` continues to report overdue tasks under `overdue_governance`. - `lore tasks reconcile` / `lore-task action='reconcile'` continue to surface evidence-backed candidate closures, but they do not close overdue tasks just because the review date has passed. There is no phase-1 auto-cancel, triage-bucket escalation, bulk close, or extend-on-touch behavior. A deliberate overdue-expiry sweep can be added only after the close-reason and explicit-ID bulk-close surfaces exist. That future sweep should be opt-in, should operate on task rows rather than per-project membership state, and should call the same task close path a human would use so `Done At`, terminal state, closure reason, partial-failure reporting, and idempotency stay consistent across CLI and MCP. A second run over the same vault must skip tasks already in `done` or `cancelled` and must not append a second closure note. If a future deliberate sweep auto-cancels an overdue task, the closure reason must be written through the task close-reason surface in this shape: ```md Cancelled by task expiry sweep: Review By was days overdue. No operator extended or closed the task before the sweep. ``` ## Migrating From Unscoped Writes Older vaults may contain memories or facts whose `Project` relation is empty. Those rows usually mean "repo-wide" context, but during operator migrations an omitted project scope is easy to confuse with "run this for my current project." Project-capable migrations therefore require an explicit scope decision. Pass `--project ` to target one project; the discovery query includes rows in that project plus unscoped rows so shared repo context can still be repaired. Pass `--allow-unscoped` only when you intentionally want the migration to scan the whole vault. Pass `--include-archived` with `--project` when repairing data for a retired project. This applies to `--fix-fact-encoding`, `--fix-memory-encoding`, `--build-entities`, `--normalize-agents`, `--backfill-fact-sources`, `--backfill-synopses`, `--backfill-autosave-learning-source`, and `--build-fact-confidence-scores`. Recommended flow: 1. List candidates: `lore status projects -a` 2. Preview: `lore migrate --fix-memory-encoding --project "Widget" --dry-run` 3. Apply: `lore migrate --fix-memory-encoding --project "Widget" --yes` If a project name is missing, archived, or inaccessible, Lore aborts before the plan or write phase. Transient Notion lookup failures such as 429s and 5xxs are reported as retryable project-resolution errors; retry them rather than switching to `--allow-unscoped`. ## MCP Tool Checklist - Session start: `lore-context action='wake-up'` - After `/clear` or a topic pivot: `lore-context action='wake-up'` with `userQuery` set to the new task prompt. - Formal decision: `lore-decision action='create'` - Superseding a decision: `lore-decision action='create'` with `supersedesIds`, or `lore-decision action='supersede'` - Governing context before editing an entity: `lore-decision action='context'` - General knowledge: `lore-memory action='save'` - Durable relationship: `lore-fact action='create'` - Tension between memories: `lore-memory action='compare'` - Session end: rely on the Stop autosave hook; no manual call required ## Scheduled Digest Synthesis The Stop hook can spawn a detached weekly digest synthesizer for the current project. Digest scheduling is debounced by filesystem marker and never blocks the user's next turn. Digest saves are idempotent by project/date: a repeated save with the same `Digest — YYYY-MM-DD — ` title updates the existing digest row instead of creating another one. - Manual invocation: `lore digest --project ` - Preview: `lore digest --dry-run` - Wider low-volume window: `lore digest --since YYYY-MM-DD` - Disable: `hooks.autoDigest: false` in `.lore.yaml`, or `LORE_AUTO_DIGEST=false` - Reset debounce marker: `rm $TMPDIR/lore-hook-state/digest.*.last`