Documentation
¶
Overview ¶
Package memory is the assistant's persistent memory.
The vault holds what you write down; this holds what the assistant *learns* about you across conversations — your preferences, the people you work with, standing context and priorities — the things a good human assistant just knows and a stateless one forgets the moment a session ends. Memories are embedded and recalled semantically, so the relevant ones surface into a later conversation without you repeating yourself. That continuity is what turns a chatbot into a second brain.
Index ¶
- Constants
- Variables
- func Accept(db *sql.DB, id int64) error
- func AcceptAuto(db *sql.DB, id int64, at time.Time) error
- func AcceptedSince(db *sql.DB, since int64, project string) (byHand, auto int, err error)
- func Consolidate(db *sql.DB, rt *router.Router) (merged int, superseded int, err error)
- func ContestedNow(db *sql.DB, id int64, text string) (int64, bool)
- func Count(db *sql.DB) (int, error)
- func DismissedTexts(db *sql.DB) (map[string]bool, error)
- func EffectiveSalience(m Memory, now int64) float64
- func EscapeField(s string) string
- func Evidence(query string, qVec []float32, cands []Candidate) []bool
- func Exclude(db *sql.DB, id int64) error
- func Export(db *sql.DB, dir string) error
- func ExportKind(db *sql.DB, dir string, kind Kind) error
- func Faded(db *sql.DB, now int64) (int, error)
- func Fingerprint(text string) string
- func Forget(db *sql.DB, id int64) error
- func ForgetBySource(db *sql.DB, source string) (int, error)
- func Fuse(query string, qVec []float32, cands []Candidate) []float64
- func HasProject(db *sql.DB, project string) (bool, error)
- func HybridRank(query string, qVec []float32, cands []Candidate, k int) []string
- func Import(db *sql.DB, p *provider.Provider, embedModel, dir string) (imported, rescued int, err error)
- func ImportLog(db *sql.DB, dir string) (int, error)
- func ImportPending(db *sql.DB, dir string) (int, int, error)
- func Init(db *sql.DB) error
- func LastReview(db *sql.DB) (int64, error)
- func Learn(db *sql.DB, rt *router.Router, exchange, source string) (int, error)
- func LearnedBetween(db *sql.DB, since, until int64) (int, error)
- func MarkDrift(mems []Memory, dir string)
- func PendingCount(db *sql.DB) (int, error)
- func Pin(db *sql.DB, id int64) error
- func ProjectOf(db *sql.DB, id int64) (string, error)
- func ReconcilePending(db *sql.DB) (restored, rejected int, err error)
- func Reconfirm(db *sql.DB, id int64, commit string) error
- func Reject(db *sql.DB, id int64) error
- func Render(mems []Memory) string
- func SeenTexts(db *sql.DB) (map[string]bool, error)
- func SetVault(db *sql.DB, dir string)
- func UnattributedCount(db *sql.DB) (int, error)
- func UndoAuto(db *sql.DB, id int64) error
- func UnescapeField(s string) string
- func Unflushed(db *sql.DB) (int, error)
- func UnflushedIDs(db *sql.DB) map[int64]bool
- func UnflushedPending(db *sql.DB) (int, error)
- func Unpin(db *sql.DB, id int64) error
- type BenchResult
- type Candidate
- type Correction
- type DiffEntry
- type DiffResult
- type Edits
- type Fetched
- type GraphEdge
- type GraphNode
- type HealthReport
- type Kind
- type LogEntry
- type MemGraph
- type Memory
- func All(db *sql.DB) ([]Memory, error)
- func AllInProject(db *sql.DB, project string) ([]Memory, error)
- func AutoAccepted(db *sql.DB, since int64) ([]Memory, error)
- func Fading(mems []Memory, now int64) []Memory
- func Pending(db *sql.DB) ([]Memory, error)
- func Pinned(db *sql.DB, project string) ([]Memory, error)
- func Recall(db *sql.DB, p *provider.Provider, embedModel, query string, k int) ([]Memory, error)
- func RecallInProject(db *sql.DB, p *provider.Provider, embedModel, query, project string, k int) ([]Memory, error)
- func RecallProcedures(db *sql.DB, p *provider.Provider, embedModel, query string, k int) ([]Memory, error)
- func Surface(db *sql.DB, kinds []Kind, n int) ([]Memory, error)
- type PipelineCase
- type PipelineReport
- type QAResult
- type Receipt
Constants ¶
const ( EvCreated = "created" // a new memory was stored EvReinforced = "reinforced" // a re-statement corroborated an existing memory EvSuperseded = "superseded" // a newer memory replaced this one (ref_id is the newer) EvMerged = "merged" // a duplicate was folded into this one (ref_id is the dropped) EvForgotten = "forgotten" // the memory was deleted EvUpdated = "updated" // an attribute (confidence, project, salience) changed EvQuarantined = "quarantined" // a machine proposed this and it is waiting for review EvAccepted = "accepted" // a quarantined memory was reviewed and released into active memory EvRejected = "rejected" // a quarantined memory was reviewed and discarded // EvAutoAccepted is the review switch accepting a memory with nobody // watching, kept apart from EvAccepted so "since your last review" means // since a person last looked, not since the switch last ran. EvAutoAccepted = "auto-accepted" // EvUnaccepted is `logos review --undo-auto` putting one back in the queue. EvUnaccepted = "returned-to-review" )
Event names for the log. Kept as plain strings so the log stays readable and new event types never require a migration.
const ( MinCosine = 0.45 CosineGap = 0.15 )
Relevance floor for recall. Fuse ranks, and a rank says nothing about whether anything matched: the top candidate always scores 1.0, so every query returned up to its limit and reinforced all of it (#44). Measured with nomic-embed-text on ten short memories and ten queries: unrelated pairs sit at a median cosine of 0.36 with a maximum of 0.48, a clear answer at 0.73-0.87, and the one vague query ("which package manager") put its answer at 0.48 — level with the noise. So no absolute threshold separates them alone. MinCosine drops what no query should reach (nonsense tops out at 0.39); CosineGap drops the band of noise behind a strong answer, and leaves a vague query's flat field whole rather than guess at it. The nomic search_query/search_document prefixes were measured too and widened the overlap, so they are not sent.
const ( // must carry before its words are evidence on their own. LexicalShare = 0.7 // VectorOnlyGap is CosineGap for a memory sharing no word with the query. // 0.12, not 0.10, which measured 2 points more precise on the 32 facts but // sits exactly on a paraphrase 0.10 below the answer, the case // TestAMemoryFarBelowTheBestMatchIsLeftOut keeps. VectorOnlyGap = 0.12 )
Measured on a scratch vault of 432 memories about one project (its own 400 commit subjects plus 32 labelled facts) and 18 labelled queries: any shared word admitted a memory, so a project's common vocabulary — "written", "claude code", "release" — let most of the vault through, and the 0.15 gap admitted the rest of a flat field. Weighting the shared words, and holding a vector-only match closer, took precision from 27% to 33% with the same recall there, and from 31% to 39% on the 32 facts alone, where three answers that had got in on a coincidental word ("tag" in "-tags") were lost. Swept 0.3-0.9 and 0.05-0.15: lower LexicalShare or a wider gap gave the noise back, a higher share or narrower gap started dropping answers. Ranking was not the lever: score fusion, qwen3-embedding:0.6b, and a 4B chat model as judge were all measured and none ranked the answers higher.
const ( PinNone = 0 // ranked normally PinAlways = 1 // always included in a context pack, budget permitting — see spendMemories PinNever = 2 // excluded from recall entirely; still on record, still listed by All )
Pin states. PinNone is the default: ranked normally, neither forced in nor held out. Deliberately two states rather than a bool, because "always show me this" and "never show me this" are different user intents, not opposites of the same one — a memory can need overriding in either direction, and a single flag can only say one of them.
const DedupThreshold = 0.87
DedupThreshold is how close a new memory must be to an existing one to be treated as the same fact. High, so genuinely distinct facts about the same subject are kept; only near-restatements collapse.
The threshold alone was not enough — records that share a sentence frame and differ only in a number sit above it. It is now one of two conditions, the other being sameFact.
const Dir = "memories"
Dir is where memories live inside the vault.
const FadingDays = 60
FadingDays is how long a memory can go unrecalled before `logos show` asks whether it is still wanted — two-thirds of a half-life, while it still ranks well enough to be worth deciding about rather than already out of sight.
const HalfLifeDays = 90.0
HalfLifeDays is how long an untouched memory takes to lose half its salience. Long, because a preference stated once is meant to persist — but not forever if it is never relevant again.
const LogFile = "log.md"
LogFile is the timeline's name inside the memory directory. Not one of the kinds, for the reason PendingFile is not: Import iterates `kinds`, so this file is invisible to the path that decides what counts as known.
const MemSimilarityThreshold = 0.60
MemSimilarityThreshold gates "similar" edges. High enough that the graph shows genuine clusters, not a cobweb.
const OutcomeNoop = "noop"
OutcomeNoop means nothing was stored — there was nothing to store.
const PendingFile = "pending.md"
PendingFile is the review queue's name inside the memory directory. Not one of the kinds, on purpose: Import iterates `kinds`, so this file is invisible to the path that decides what counts as known.
const Schema = `` /* 4108-byte string literal not displayed */
const SourceCheckpoints = "checkpoints"
SourceCheckpoints marks a memory promoted from a decision that held across checkpoints (see ops.PromoteDecisions). Resume lists it with the decisions, not the memories, because that is where its sources were.
const ( // StaleAgeDays: a memory older than this, never recalled, and faded in // effective salience is stale — still true, perhaps, but no longer earning // its keep. StaleAgeDays = 60 )
Variables ¶
var ErrOnlySecret = errors.New("nothing to remember once the credential or <private> span in it was kept out — neither is stored as a memory")
ErrOnlySecret is Store refusing a memory that was nothing but a credential, or a span the person marked <private>. Its own value so a bulk caller can count these as refusals rather than stopping on the first one, or reporting them as failures to write.
var Ks = []int{1, 3, 5, 10}
Ks are the recall@k thresholds reported in one pass.
Functions ¶
func Accept ¶
Accept releases a quarantined memory into active memory: it becomes recallable and gets written to the vault, same as anything Store creates directly. Rejects an id that is not actually pending, rather than silently no-op-ing on a typo or a double-accept.
func AcceptAuto ¶ added in v0.5.2
AcceptAuto is Accept done by the review switch at the given time. The memory is marked as auto-accepted in its vault line, which is what lets a person list and undo what the switch did — after a rebuild as well as before one.
func AcceptedSince ¶ added in v0.5.3
AcceptedSince counts the review queue's accepts at or after since, a Unix time, by a person and by the review switch apart: an accept nobody watched is not the same evidence as one somebody read. project "" counts every scope; a project counts its worktrees' scopes ("brain/feature") with it, as the usage ledger totals them. By prefix with the slash, not LIKE, so a project named with _ or % matches only itself. Read from the log, which ImportLog rebuilds from memories/log.md, so the count survives deleting the index.
func Consolidate ¶
Consolidate merges near-duplicate memories and supersedes ones a newer memory updates, so the store holds each fact once and holds its current version.
For each highly-similar pair, the model classifies the relation:
- duplicate → keep the more-salient, fold in salience, supersede the other
- update → the newer memory replaces the older; the older is superseded (this is what the knowledge-update case needs: "I moved to Boston" supersedes "I live in NYC")
- distinct → leave both
Only pairs above the similarity gate are shown to the model, so the number of calls stays proportional to genuine overlap, not to the store size squared.
func ContestedNow ¶ added in v0.5.2
ContestedNow is contestedMemory for a memory already stored: whether its text contradicts another memory, by text alone. The review switch asks it at the moment it would accept, not only when the proposal arrived, because a memory pinned or stored since can be the one it now disputes.
func DismissedTexts ¶ added in v0.5.3
DismissedTexts is every text the timeline records a person rejecting from the review queue or forgetting. Narrower than SeenTexts on purpose: a text met only as the reinforcement of a memory worded differently was not thrown out, and a caller that reports these as the user's rejections would say so falsely. Restored from log.md, like the rest of the timeline.
func EffectiveSalience ¶
EffectiveSalience is a memory's stored salience decayed by how long since it was last useful, then lifted by how often it has proven useful. This is what ranking should trust, not the raw stored number: memory that earns its keep through recall stays prominent; memory that never helps recedes.
func EscapeField ¶ added in v0.4.8
EscapeField and UnescapeField are the same encoding for code outside this package that rewrites these files — a project rename — so it matches the name as written rather than as typed.
func Evidence ¶ added in v0.5.0
Evidence reports, per candidate, whether anything ties it to the query: enough of the query's distinctive words, a few of them backed by a vector close to the best match, or a vector alone that is closer still. Without evidence a candidate is not an answer, however it ranks among the others.
func Exclude ¶
Exclude marks a memory never-include: it is dropped from recall entirely, but stays on record rather than being deleted. For the case Forget is too blunt for — a memory that is not wrong, just not something that should surface (something sensitive, something the user would rather manage by hand) — where the honest action is "stop showing me this", not "erase it".
func ExportKind ¶
ExportKind writes one kind's memories to memories/<kind>.md.
Superseded memories are left out. They are history, not knowledge — the memory_log already records that they existed and what replaced them, and carrying them here would put values the user has already corrected back into a file the user reads.
Quarantined memories are left out too, and for a stronger reason: they are not yet knowledge at all. A machine proposed them and nobody has said yes. Writing them to the file the user reads as "what the assistant knows" would make quarantine theatre — reviewable in principle, already believed in practice. They start appearing here the moment Accept clears the flag.
func Faded ¶ added in v0.4.8
Faded counts the memories that disuse has pushed well below their stored salience. It only counts: decay is applied where salience is read, by EffectiveSalience, and nowhere else.
It used to write the decayed value back as the stored salience. Every run then re-applied all the decay since last_used to a number that already had it, so `logos memory consolidate` run twice in a row halved an unused fact twice, and every ranking site decayed the result a third time. The write also never reached the kind file, so a rebuild quietly undid it, and ranking depended on whether the cache had survived.
func Fingerprint ¶ added in v0.5.3
Fingerprint is the key Store deduplicates on, for a caller that has to say beforehand what a Store will skip: a preview that counted by exact text promised writes the Store then folded into an existing row.
func ForgetBySource ¶
ForgetBySource removes every memory learned a particular way and reports how many went. It returns the count rather than nothing so a caller can tell "I removed forty" from "there was nothing there".
This is what makes a bulk seeding reversible. `logos bootstrap` writes several memories at once from git history, and a user who dislikes the result should not have to forget them one id at a time — nor should they have to guess which ids were the machine's and which were theirs. Source is the record of how a fact arrived, so it is the honest handle for undoing a whole arrival.
Each row goes through Forget, so the timeline records every removal individually and the vault files are flushed. A bulk DELETE would be faster and would leave the vault holding notes the database no longer knows about.
func Fuse ¶
Fuse returns a reciprocal-rank-fusion score per candidate (aligned to cands), combining vector similarity and BM25 lexical rankings, normalised to 0..1 by the top score so callers can blend it with other signals like salience.
func HasProject ¶ added in v0.4.8
HasProject reports whether any memory — including a superseded or quarantined one — is filed under this exact project name. It is how a caller tells "this project has recorded nothing about that" apart from "there is no such project", which otherwise produce the same empty result and so the same wrong conclusion for the agent reading it.
func HybridRank ¶
HybridRank fuses vector and BM25 rankings, returning candidate IDs best-first.
func Import ¶
func Import(db *sql.DB, p *provider.Provider, embedModel, dir string) (imported, rescued int, err error)
Import rebuilds the store from the vault. This is what makes `rm -rf .logos` survivable.
The files win. Where a memory exists in both places the text, confidence and provenance are taken from markdown, because the whole point is that a person can open the file, correct a fact, and have the correction stick. Memories present in the cache but absent from the file are deleted — that is how deleting a line forgets something.
Vectors are not stored in the file; anything without one is re-embedded here, which is the same work `logos index` already does for notes.
func ImportLog ¶
ImportLog restores the timeline from the vault, and is what makes deleting the cache survivable for the history as well as for the memories themselves.
Runs after Import, so a memory the log refers to is already back in the table. Events are matched on what they are rather than on a row id — a log id is local to the database that issued it and means nothing after a wipe — so re-running this is a no-op rather than a second copy of every line.
Returns how many events it put back.
func ImportPending ¶
ImportPending restores the review queue from the vault, and is what makes deleting the cache survivable for proposals as well as for memories.
Ordering matters: this runs after Import, so an id the queue claims is one Import has already had its chance to restore as an active memory. If a proposal was accepted between the last flush and now, the row exists and is no longer quarantined — re-quarantining it would un-accept a decision the user already made, so an existing row is left exactly as it is.
A damaged file is refused rather than acted on, for the reason Import gives: a truncated queue read as authoritative would silently reject every proposal the missing tail held.
Returns how many proposals it put back into the cache, and how many it rescued the other way — out of a cache that was their only copy and into the vault. They are separate numbers because they are opposite events, and a caller that reported a rescue as a restore would tell the user their queue had been recovered on a run where it was merely, finally, written down.
func LastReview ¶ added in v0.5.2
LastReview is when a person last accepted or rejected a proposal, or 0. It reads the memory timeline, which log.md restores after a rebuild, so "since your last review" survives deleting the index like the marks it counts.
func Learn ¶
Learn extracts durable memories from a conversation excerpt and stores the ones worth keeping. Returns how many were newly stored. Runs at T1 — a narrow classification, not reasoning.
func LearnedBetween ¶
LearnedBetween counts memories first created in [since, until] (unix seconds), read from the append-only log so the count is stable even after some of those memories were later superseded or forgotten. Used for growth over time.
func MarkDrift ¶ added in v0.5.3
MarkDrift sets Drift on each memory recorded at a commit of the repository at dir whose named files have changed since. The same measurement, and the same "may", as a ruling's (deadend.MarkDrift): a memory written in another repository, or before commits were recorded, is left alone.
func PendingCount ¶
PendingCount is the number `logos doctor` surfaces (see internal/health). The whole point of quarantine is that nothing sits there unseen, and a count nobody ever looks at is exactly that — so this has to be cheap enough to run on every health check, which is why it is a COUNT rather than len(Pending()).
func Pin ¶
Pin marks a memory always-include: a context pack carries it regardless of relevance score. For the handful of things that are always worth having on hand — the standing instruction, the fact that governs everything else — and that a ranking model might otherwise rate as unremarkable on any given query.
func ProjectOf ¶ added in v0.5.3
ProjectOf is the project memory id is filed under, "" for a global one.
func ReconcilePending ¶ added in v0.5.1
ReconcilePending adopts hand edits to the review queue file now, and reports how many proposals the file put back and how many it rejected.
Exported for `logos review`. Only writes used to reconcile, so the review offered a proposal the user had already rejected by deleting its line — and a write that did adopt the deletion said nothing about it.
func Reconfirm ¶ added in v0.5.5
Reconfirm records that a memory still holds at commit: `logos review --stale` after a person looked at the files that moved since it was written. Its drift is measured from there on, so the warning they answered does not come back for the same commits. Written through to the memory's file like setPin, because a commit kept only in the index would be lost with it and every answered warning would return.
func Reject ¶
Reject discards a quarantined memory outright. Unlike Forget, this only works on something still pending: rejecting an already-active memory is not what this call means, and forcing the caller through Forget for that keeps the two operations from being silently interchangeable.
func Render ¶
Context renders the recalled memories as a compact block for injection into a prompt — what the assistant "already knows" about the user, so it does not ask again and can act on preferences unprompted.
func SeenTexts ¶ added in v0.5.2
SeenTexts is every text the timeline records a memory being stored, proposed or folded into another with — what Logos has met before, whatever became of it since. A fold too: a re-worded import is logged under its own words only as the reinforcement of the memory it restated, and forgetting that memory overrules it as much as forgetting one it created. The import switch skips these, so a memory the user forgot or rejected is not brought back by the next import. The timeline is restored from log.md, so this survives deleting the index.
func SetVault ¶
SetVault tells one store where its durable copy belongs. An empty dir unbinds it, which disables writing through — what tests that only care about the cache want, and what index.Close does so a closed handle leaves nothing behind.
func UnattributedCount ¶
UnattributedCount reports how many log lines carry no project at all. These are pre-migration events for memories that were forgotten before the backfill could reach them; they belong to some project, and there is no longer any way to say which. A project timeline that quietly omits them is accurate about what it shows and incomplete about the past, and a caller that says so is more trustworthy than one that does not.
func UndoAuto ¶ added in v0.5.2
UndoAuto puts a memory the review switch accepted back in the review queue. Back in the queue rather than rejected: the switch's mistake is to have decided without the user, and the undo returns the decision to them instead of making a second one on their behalf.
func UnescapeField ¶ added in v0.4.8
func Unflushed ¶ added in v0.4.8
Unflushed counts the memories the cache holds that the vault does not, which is what `logos doctor` needs in order to say so. Zero is the normal answer.
func UnflushedIDs ¶ added in v0.4.8
UnflushedIDs is the set of memories the cache holds and the vault does not, for a reader that has already loaded some memories and needs to say which of them are not durable.
A set rather than a column on Memory because every recall path scans its own explicit column list, and threading one more field through all of them would touch far more code than the question is worth. One query answers it for a whole page of results, and the normal answer is an empty map.
func UnflushedPending ¶ added in v0.5.2
UnflushedPending counts the proposals in the review queue the cache holds and memories/pending.md does not — a separate number from Unflushed because the file at risk is a different one, and so is the noun doctor names it by.
Types ¶
type BenchResult ¶
type BenchResult struct {
Category string
N int
HitsAt map[int]int // k -> number of questions whose evidence was in the top k
}
BenchResult is the outcome for one category (or overall): hits at each k.
func RunLongMemEval ¶
func RunLongMemEval(p *provider.Provider, embedModel, path string, limit int, hybrid bool, progress func(done, total int)) ([]BenchResult, error)
RunLongMemEval evaluates retrieval recall at several k thresholds (Ks) over up to `limit` instances of a LongMemEval file, in one embedding pass.
func (BenchResult) RecallAt ¶
func (r BenchResult) RecallAt(k int) float64
RecallAt returns recall@k for this result.
type Correction ¶ added in v0.5.3
type Correction struct {
ID int64 `json:"id"`
Replaced int64 `json:"replaced"`
// Existing is set when the corrected text was already an active memory, so
// the old one was retired in its favour instead of a twin being written.
// "Corrected — #12 is new" would be false, and the caller says so.
Existing bool `json:"existing,omitempty"`
Redactions []secret.Redaction `json:"redactions,omitempty"`
}
A Correction says what Correct did: which memory now holds the truth, and which one it replaced.
func Correct ¶ added in v0.5.3
func Correct(db *sql.DB, p *provider.Provider, embedModel string, id int64, m *Memory) (Correction, error)
Correct replaces memory id with m in one step: m is stored, and the old memory is marked superseded by it.
Forget-then-remember was the only way to do this before, and it is two mistakes. Forgetting erases what was believed, so the history can no longer say "this used to be 8080"; and remembering the new fact on its own runs it through the contested check, which finds the very memory being corrected and queues the correction for review. A person who said "that is wrong, it is now X" has already reviewed it. So the new memory skips semantic dedup and the contested check, and the old one is kept on record, out of recall.
The link survives deleting the index without a vault format change: the superseded memory leaves memories/<kind>.md, and the superseded event in memories/log.md — the one ImportLog replays — names its replacement.
type DiffEntry ¶
type DiffEntry struct {
MemID int64 `json:"mem_id"`
Event string `json:"event"`
Text string `json:"text"` // snapshot from the log at the time of the change
TS int64 `json:"ts"`
// The project the memory belonged to, "" for a global fact. Carried so a
// reader can tell whose work an entry describes: a flat list of every
// project's changes reads as a list of changes to the one in front of you.
Project string `json:"project,omitempty"`
}
DiffEntry is one change to one memory within the window.
type DiffResult ¶
type DiffResult struct {
Subject string `json:"subject,omitempty"`
Since int64 `json:"since"`
Until int64 `json:"until"`
Added []DiffEntry `json:"added"` // newly learned facts
Removed []DiffEntry `json:"removed"` // superseded or forgotten
Corroborated []DiffEntry `json:"corroborated"` // re-stated, merged, or updated — belief firmed
}
DiffResult is what changed between two moments.
func Diff ¶
Diff computes what changed in the store between since and until (unix seconds, inclusive). If subject is non-empty, it narrows to changes whose snapshot text mentions it, case-insensitively — "what changed about Sarah?". Matching on the snapshot (not the live memories table) is deliberate: it still catches facts that were forgotten or superseded inside the window.
func (DiffResult) Empty ¶
func (d DiffResult) Empty() bool
type Edits ¶ added in v0.5.3
type Edits struct {
Added, Forgotten []int64
// Held is a line left out of memory, each with why and what to do: its
// text belongs to a memory awaiting review or superseded, so it can be
// neither that memory nor a new one.
Held []string
// Migrated counts memories moved from an old one-file-per-kind vault to
// their own file under memories/<kind>/, this run. Zero on every run after
// the first for a given kind — migration is idempotent, keyed off which
// per-memory files already exist, not off a flag that could get stuck if
// a run is interrupted.
Migrated int
}
Edits is what an import changed because of a hand edit: lines with no id comment that became memories, and memories whose line is gone. Said by `logos index`, because the two together are what a line retyped without its comment does — the old memory is forgotten with its project, confidence and pin, and a bare one takes its place — and the summary line counts neither.
type Fetched ¶ added in v0.5.3
Fetched is one id asked for by number, and what became of it. An id is something an agent copied out of an earlier answer, so the memory may have been replaced, queued or excluded since; each of those is a different thing to tell the agent, and none of them is the text served as current.
type GraphEdge ¶
type GraphEdge struct {
Src string `json:"src"`
Dst string `json:"dst"`
Rel string `json:"rel"` // mentions | similar | supersedes
Weight float64 `json:"weight"`
}
GraphEdge connects two nodes.
type GraphNode ¶
type GraphNode struct {
ID string `json:"id"` // "m<id>" for a memory, the note slug for a note
Type string `json:"type"` // "memory" | "note"
Label string `json:"label"`
Kind string `json:"kind"`
Confidence float64 `json:"confidence,omitempty"`
Salience float64 `json:"salience,omitempty"`
Project string `json:"project,omitempty"`
Superseded bool `json:"superseded,omitempty"`
Degree int `json:"degree"`
}
GraphNode is a memory or a note in the memory graph.
type HealthReport ¶
type HealthReport struct {
Total int `json:"total"`
Duplicates int `json:"duplicates"` // memories that are part of a near-duplicate pair
DuplicatePairs [][2]int64 `json:"duplicate_pairs"` // a sample, for `consolidate` to act on
Stale int `json:"stale"`
StaleIDs []int64 `json:"stale_ids"` // a sample
Orphans int `json:"orphans"` // linked to no note (informational)
LowConfidence int `json:"low_confidence"`
Score float64 `json:"score"` // 0..1, fraction with no defect flag
}
HealthReport is the computed diagnostic.
type Kind ¶
type Kind string
Kind categorises a memory so it can be weighted and surfaced appropriately.
const ( // Preference: how the user likes things done ("prefers short emails", // "no meetings before 10am"). The heart of the "emotional intelligence" a // secretary is expected to have. Preference Kind = "preference" // Person: a relationship fact ("Sarah is the CFO", "Alex is sensitive // about deadlines"). Person Kind = "person" // Fact: a durable fact about the user or their work. Fact Kind = "fact" // Context: standing situational context ("launching in Q4", "hiring two // engineers") — what lets the assistant anticipate. Context Kind = "context" // Procedure: how to do something in this codebase, with a trap — what // goes wrong if you don't. The mirror of a recorded dead end: negative // knowledge stops an agent repeating a mistake, this stops it repeating // the discovery that the mistake was avoidable. Never surfaced by // ordinary recall or in a context pack (see recallScoped, Pinned) — its // only reader is before_you_try, via RecallProcedures. See // internal/procedure for the record shape and retrieval ranking. Procedure Kind = "procedure" )
type LogEntry ¶
type LogEntry struct {
ID int64 `json:"id"`
TS int64 `json:"ts"`
MemID int64 `json:"mem_id"`
Event string `json:"event"`
Detail string `json:"detail"`
RefID int64 `json:"ref_id"`
// Project is the memory's project as it stood when the event happened,
// which is not always its project now — a memory can be re-scoped, and the
// timeline of a project should show what was true at the time rather than
// retroactively rewriting itself.
Project string `json:"project,omitempty"`
}
LogEntry is one line of the timeline.
func History ¶
History returns the timeline for a single memory, oldest first — its whole life, from first learned to now.
func ParseLog ¶ added in v0.5.3
ParseLog is parseLog for a reader outside this package: `logos watch` reads the timeline's lines as they are appended, from the vault, so it shows what happened even to a memory stored by another process's index.
func Timeline ¶
Timeline returns the most recent log entries, newest first, up to limit (limit <= 0 means all).
func TimelineInProject ¶
TimelineInProject is Timeline narrowed to one project: what changed in what the assistant knows about *this* work, newest first.
It is a separate function rather than a filter the caller applies afterwards because the limit has to be applied after the narrowing, not before. Asking for "the last 20 events in kestrel" and getting two, because the other eighteen of the global last-20 belonged to other projects, is the kind of wrong that looks like an empty project rather than a bad query.
Unattributed lines — written before the log carried a project, by a memory since forgotten — match nothing and are simply absent. UnattributedCount reports how many there are so a caller can say the timeline is partial instead of implying it is complete.
type MemGraph ¶
MemGraph is the whole thing.
func BuildGraph ¶
BuildGraph assembles the memory graph. withSimilarity adds the (view-time, never-stored) similarity edges; they are the densest, so they are opt-in.
type Memory ¶
type Memory struct {
ID int64 `json:"id"`
// Uid is a content-derived id — sha256(kind + normalized text + the
// repository's normalized remote), truncated to uidLen hex characters —
// computed once, by computeUID, the first time this memory is stored, and
// never recomputed after. ID is an autoincrement counter, local to one
// vault's SQLite cache and reused once a row is deleted; Uid is what lets
// the same fact, written independently on two machines, be recognised as
// the same memory once 0.6.0 merges vaults across users. A hand edit to
// text, confidence or salience must not change it — see upsert, which
// never writes this column on an UPDATE.
Uid string `json:"uid,omitempty"`
Text string `json:"text"`
Kind Kind `json:"kind"`
Salience float64 `json:"salience"` // 0..1, how much it matters
// Confidence is how sure we are the fact is true and current, as distinct
// from salience (how much it matters). A preference you stated by hand is
// near-certain; one the model inferred from a passing remark is not.
// Corroboration raises it; being superseded or going stale lowers it.
Confidence float64 `json:"confidence"` // 0..1
// Project scopes a memory to one piece of work, so an assistant helping with
// ÉlyséeBot recalls ÉlyséeBot's context and not the whole life. Empty means
// global — it applies everywhere.
Project string `json:"project,omitempty"`
Source string `json:"source"` // where it was learned (conversation, manual, mcp)
// Agent names which coding agent learned this — "claude-code", "cursor",
// "codex" — when the MCP host said so at handshake. Empty means either the
// fact predates this field or arrived somewhere that isn't one agent among
// several (the CLI, a dream pass). Never asked of the model: see
// internal/mcpserver's clientInfoFromInitialize, which is the only writer.
Agent string `json:"agent,omitempty"`
Created int64 `json:"created"`
LastUsed int64 `json:"last_used"`
Uses int `json:"uses"`
SupersededBy int64 `json:"superseded_by,omitempty"` // id of the memory that replaced this one
// Supersedes is the opposite direction from SupersededBy, and the one that
// has to survive a wipe: SupersededBy is this row's own id-based pointer,
// set on the OLD memory by carryLocked, and never written to the vault —
// it dies with the index, which is fine, because the superseded row's file
// is deleted too (see removeMemoryFile) and nothing recreates either on
// reimport. Supersedes is the NEW memory's forward link, written here as
// the old memory's content-derived Uid rather than its numeric id, so a
// reader of this memory's own per-memory file can say what it replaced
// without the database at all. Set once by Correct, at creation, exactly
// like Uid itself.
Supersedes string `json:"supersedes,omitempty"`
// Quarantined means a machine proposed this and nobody has looked at it yet.
// A quarantined memory is a real row — same table, same shape — it is just
// invisible to Recall/All until a human accepts it. See quarantine.go.
Quarantined bool `json:"quarantined,omitempty"`
// ReviewIfContested queues this memory only when it contradicts something
// already stored, instead of queueing it unconditionally. Set on the way
// in; Quarantined is the answer on the way out. See contestedMemory.
ReviewIfContested bool `json:"-"`
// Turn cites the transcript turn a queued proposal was made in, as TurnRef
// writes it. Contests is the memory it contradicted when it queued. Both
// are what the review switch decides by (see internal/ops/autoreview.go),
// and both are written into the queue file, because a decision the index
// alone remembered would be forgotten by the rebuild that follows.
Turn string `json:"turn,omitempty"`
Contests int64 `json:"contests,omitempty"`
// AutoAccepted is when the review switch accepted this memory, or 0 when a
// person did or nobody had to. Written into the memory's vault line, so
// `logos review --undo-auto` still finds it after the index is deleted.
AutoAccepted int64 `json:"auto_accepted,omitempty"`
Score float64 `json:"score"` // set on recall: relevance to the query
// Pin overrides ranking entirely: PinAlways forces inclusion in a context
// pack regardless of relevance score, PinNever removes a memory from
// recall while leaving it on record. See PinAlways and PinNever.
Pin int `json:"pin,omitempty"`
// Commit is the repository's HEAD where the memory was written, when it
// was written inside the repository its project is. A procedure's trap and
// a decision's reason are about the code as it was then; recall measures
// the files the text names against this, the way a checkpoint's rulings
// are measured against the checkpoint's commit. Empty for every memory
// written before it existed and for one written outside a repository —
// neither is ever flagged.
Commit string `json:"commit,omitempty"`
// Remote is the repository's normalized origin URL (see
// gitstate.NormalizeRemote), set by ops.Anchor from Dir before Store is
// called. It exists only to seed Uid at creation — not persisted to the
// vault or the index, and never read back once Uid is set. A memory
// written outside a repository, or one whose caller never resolved a
// Dir, leaves this "", which computeUID treats as the explicit no-remote
// case rather than a repository that happens to have an empty identity.
Remote string `json:"-"`
// Drift is set on the way out, by MarkDrift, when files the memory names
// have changed since Commit. A reason to say so, never to drop it.
Drift *gitstate.Drift `json:"drift,omitempty"`
// contains filtered or unexported fields
}
Memory is one remembered thing.
func All ¶
All returns every active memory, most salient first — the fallback and the CLI view. Superseded memories are excluded (they are history, not truth).
func AllInProject ¶
AllInProject is All narrowed to one project's memories plus the global ones, which is what a project-scoped view wants: the work's own facts, and the standing truths that apply to every project. An empty project means no narrowing at all, so this degrades to All.
func AutoAccepted ¶ added in v0.5.2
AutoAccepted lists the memories the review switch accepted at or after since, newest first, that still carry the mark: one a person has since forgotten, or whose mark they deleted by hand, is no longer the switch's.
func Fading ¶ added in v0.5.5
Fading picks out the memories no recall has touched in FadingDays (never recalled counts from when it was written), the longest untouched first.
Decay already ranked these down, and said so to nobody: a memory that drifted out of every context pack looked, from the outside, like one Logos had lost. Listing them gives the person the decision decay was making for them. A pinned memory is left out — the pin already answered the question.
So are the two kinds that reach an agent without a recall, so their clock says nothing about whether they are read: a procedure is shown by before_you_try, and preferences are surfaced into every context pack. Listing those beside "let one go" would invite deleting what is used every session.
func Pending ¶
Pending returns quarantined memories awaiting review, oldest first — a backlog should be worked in the order it arrived, not have the newest arrival jump the queue.
It carries Agent, which the review queue needs more than active memory does: the question in front of a reviewer is whether to believe a proposal, and which agent proposed it is part of the answer.
func Pinned ¶
Pinned returns every active PinAlways memory in scope for a project — its own plus the global ones, the same scoping AllInProject uses. This is the list contextpack must include regardless of relevance score; ranking never gets a vote on whether a pinned memory appears; only the budget does, and only in the sense of how much of it fits — see spendMemories.
kind != Procedure is not optional here the way it is a design choice elsewhere: recallScoped's exclusion only stops a procedure from being ranked into the pack, but Pinned is contextpack's separate, unconditional path around ranking entirely (see recallScoped's own comment on why no special case lives there for PinAlways). Without this line, pinning a procedure would be the one way to put instruction-shaped vault content in front of a model with no boundary line in sight — the opposite of "procedures never enter the pack."
func Recall ¶
Recall returns the memories most relevant to a query, by embedding similarity. The used memories have their reinforcement bumped, so what proves useful stays fresh — memory that is exercised persists, memory that never helps fades in the ranking.
func RecallInProject ¶
func RecallInProject(db *sql.DB, p *provider.Provider, embedModel, query, project string, k int) ([]Memory, error)
RecallInProject is Recall narrowed to a project's memory (plus global memory).
func RecallProcedures ¶
func RecallProcedures(db *sql.DB, p *provider.Provider, embedModel, query string, k int) ([]Memory, error)
RecallProcedures returns the vault's procedure-kind memories bearing on a query, unscoped by project — deliberately, mirroring deadend.Collect, which searches every project on purpose because the procedure that would help was quite possibly recorded on a different project than the one being asked about. internal/procedure.Check takes it from here to rank and flag cross-project hits; this function's only job is "every stored procedure", the way Collect's is "every recorded dead end".
type PipelineCase ¶
type PipelineCase struct {
Name string
Exchange string // the conversation to learn from
Probe string // a later question that should recall the fact
Expect []string // any of these substrings appearing in a recalled memory counts as a hit
}
PipelineCase is one conversation and what should be learnable from it.
type PipelineReport ¶
type PipelineReport struct {
Cases int
Learned int // memories stored
RecallHits int // probes that recalled the expected fact
Duplicates int // re-learning the same case stored nothing new (good)
LearnTime time.Duration // total extraction time
RecallTime time.Duration // total recall time
MemoryCount int // final store size
Details []string // per-case notes
}
PipelineReport is the outcome of a pipeline run.
func RunPipeline ¶
func RunPipeline(db *sql.DB, rt *router.Router, cases []PipelineCase) (PipelineReport, error)
RunPipeline learns from each case, then probes recall, over a fresh store. It also re-learns every case once to confirm dedup holds (no growth).
func (PipelineReport) Accuracy ¶
func (r PipelineReport) Accuracy() float64
type QAResult ¶ added in v0.5.1
QAResult is end-to-end accuracy for one LongMemEval category: a generated answer, graded against the gold answer, after retrieval — as opposed to BenchResult, which only asks whether the right session was surfaced. A system can have perfect recall and a wrong answer; this is the number that is actually comparable to another memory system's published LongMemEval score, because every competitor reports QA accuracy, not recall@k.
func RunLongMemEvalQA ¶ added in v0.5.1
func RunLongMemEvalQA(p *provider.Provider, embedModel, genModel, judgeModel, path string, limit int, hybrid bool, depth int, progress func(done, total int)) ([]QAResult, error)
RunLongMemEvalQA runs the full pipeline LongMemEval actually measures: retrieve, generate an answer from what was retrieved, grade it against the gold answer. genModel answers the question from retrieved context alone; judgeModel (often the same model) grades the generated answer against the gold one. Both run through the local router, per this project's no-cloud-call rule — there is no hosted grading step hiding in here.
type Receipt ¶
type Receipt struct {
Outcome string `json:"outcome"` // EvCreated, EvReinforced, EvQuarantined, or OutcomeNoop
ID int64 `json:"id"`
// Ref is the memory that was reinforced, when the store folded into an
// existing one instead of adding a twin.
Ref int64 `json:"ref,omitempty"`
// StillQueued is set when the memory reinforced is itself waiting in
// quarantine. Dedup matches queued rows too, and "already knew that" about
// a fact recall will not return is a receipt contradicted one call later.
StillQueued bool `json:"still_queued,omitempty"`
// Contested names the memory this one contradicts, when that is why it
// queued. "Queued for review" with no reason is the receipt that trains a
// user to stop reading receipts; the one fact that makes the queue worth
// opening is which existing memory is now in dispute.
Contested int64 `json:"contested,omitempty"`
ContestedText string `json:"contested_text,omitempty"`
// Redactions is what Store masked out of the text before keeping it.
Redactions []secret.Redaction `json:"redactions,omitempty"`
// QueueRestored and QueueRejected are what a queued store adopted from a
// hand edit to the review queue file before inserting: lines the user added,
// and lines they deleted, which rejects those proposals. The store does it
// on the way to rewriting the file, and a caller that cannot see it reports
// "queued" on a call that also discarded something in the user's name.
QueueRestored int `json:"queue_restored,omitempty"`
QueueRejected int `json:"queue_rejected,omitempty"`
}
A Receipt says what actually happened to a memory that was stored.
"Saved" is not one outcome. The same call can create a memory, corroborate one that already existed, land in quarantine awaiting review, or do nothing — and a caller that cannot tell those apart will report all four as success. That matters most across MCP, where an agent tells the user it remembered something: it should be able to say whether it learned a new fact, confirmed an old one, or just queued something for the user to look at.
func Store ¶
Store embeds a memory and saves it, deduplicated on its normalised text so the same fact learned twice does not accumulate. The receipt distinguishes a new memory from a corroborated one.