Documentation
¶
Overview ¶
Package memory discovers and assembles ari's project memory: the ARI.md a repo carries, the AGENTS.md and CLAUDE.md it honors for compatibility, their @-imports, and the per-file size cap (doc 01 section 7.2, D21). The assembled text is injected as a system-reminder user message, not appended to the system prompt, so block one stays cache-stable (D14); this package only produces the text, the ant wraps it.
Index ¶
- Constants
- Variables
- func Load(opts Options) string
- func NewForget(store ToolStore) tool.Tool
- func NewRecall(store ToolStore, emb Embedder) tool.Tool
- func NewRemember(store ToolStore) tool.Tool
- func ReEmbedStale(ctx context.Context, s *sqlite.Store, e Embedder) (int, error)
- func Remember(ns string, kind sqlite.Kind, body string, importance int, ...) (sqlite.Candidate, error)
- func RenderIndex(rows []Row, cap IndexCap) string
- func RenderPortable(rows []PortableRow) string
- func Vectorize(ctx context.Context, e Embedder, text string) (vec []float32, model string)
- type CommandOutcome
- type Embedder
- type ForgetDisplay
- type Harvester
- type ImportPlan
- type IndexCap
- type NullEmbedder
- type OpenAIEmbedder
- type Options
- type PortableAnchor
- type PortableRow
- type Row
- type ToolStore
Constants ¶
const DefaultPerFileCap = 20_000
DefaultPerFileCap is the soft byte ceiling applied to each source after its imports resolve, so one runaway memory file cannot eat the window. An oversized file is truncated with a visible marker, never dropped silently, and ari doctor warns about it separately (doc 14).
Variables ¶
var DefaultIndexCap = IndexCap{Lines: 100, PerLine: 160}
DefaultIndexCap is the Claude Code auto-memory shape: a hundred lines of roughly a hundred and sixty characters, small enough to always afford in the cached prefix.
Functions ¶
func Load ¶
Load discovers the memory files with the documented precedence, resolves their @-imports, applies the per-file cap, and returns the merged text ready to inject as block two's project-memory section. The lowest-priority source lands first and the nearest, highest-priority source lands last. Empty when nothing is found.
func NewRecall ¶ added in v0.3.0
NewRecall builds the recall tool. The embedder is optional: with none, recall runs FTS-only, which is strong for the identifier-heavy queries a coding memory answers (D10).
func NewRemember ¶ added in v0.3.0
NewRemember builds the remember tool over a memory store.
func ReEmbedStale ¶ added in v0.3.0
ReEmbedStale refreshes every row whose embed_model tag does not match the configured embedder, the lazy migration D17 names: change the endpoint or the model and rows re-embed at the next fold, no flag-day rewrite. A null embedder is a no-op, so a machine with no endpoint never churns, and a row the endpoint still cannot embed is left for the fold after this one. It returns how many rows it refreshed.
func Remember ¶ added in v0.3.0
func Remember(ns string, kind sqlite.Kind, body string, importance int, anchors []sqlite.Anchor, evidence []string, src sqlite.Source) (sqlite.Candidate, error)
Remember builds a deliberate memory candidate from a remember tool call. It requires a body, an importance in 1..10, and at least one anchor so a memory is always tied to something in the repo, and a reflection needs the evidence the store guard will check again. Every violation is refused with a model-facing reason, the first of the three no-evidence enforcement points (D11): here at the tool boundary, then the candidate write, then the fold.
func RenderIndex ¶ added in v0.3.0
RenderIndex builds the pinned index markdown for a namespace from its pinned rows. It is pure and deterministic: same rows in, same bytes out, so the prompt prefix is stable between folds and the cache_control breakpoint holds (D14). The index is rebuilt only by the consolidator at a fold boundary, never on a turn. An empty row set renders the empty string; the prompt assembler owns the "no pins yet" wording.
func RenderPortable ¶ added in v0.3.0
func RenderPortable(rows []PortableRow) string
RenderPortable renders rows to the round-trippable markdown export. Each row becomes one block: a stable id in an HTML comment, the label as a heading, the body as prose, then the anchors, importance, and provenance as a short list. The output is deterministic, so the same rows render the same bytes and a re-export with no edits is a no-op diff.
func Vectorize ¶ added in v0.3.0
Vectorize embeds text for a memory write, degrading to no vector when the endpoint is down so a flaky endpoint never drops a memory: the row lands FTS-only with a NULL model tag, and the next fold's re-embed pass picks it up because NULL does not match the configured model. It returns the vector and the model tag to stamp, both zero when there is no vector.
Types ¶
type CommandOutcome ¶ added in v0.3.0
CommandOutcome is one command execution the harvester watches: the command string and whether it failed.
type Embedder ¶ added in v0.3.0
type Embedder interface {
// Configured reports whether a real endpoint backs this embedder. When
// it is false the store runs FTS-only and no row carries a vector.
Configured() bool
// Model is the tag stamped on every row this embedder vectorizes, the
// hook the lazy re-embed pass keys on when the endpoint or model changes
// (D17, recommendation 14).
Model() string
// Embed returns a vector for text, or an error when a configured endpoint
// is unreachable. The null embedder returns (nil, nil): no vector and no
// error, because having no endpoint is a valid state, not a failure.
Embed(ctx context.Context, text string) ([]float32, error)
}
Embedder turns text into a float32 vector for hybrid recall. Two shapes satisfy it, chosen by config: an OpenAI-compatible endpoint and the null embedder. The product promise is that memory works with no endpoint and works better with one, never that it needs one (D10).
type ForgetDisplay ¶ added in v0.3.0
ForgetDisplay is the typed data the UI renders for a forget: the row that was archived, never sent to the model.
type Harvester ¶ added in v0.3.0
type Harvester struct {
// contains filtered or unexported fields
}
Harvester turns a stream of command outcomes into observation candidates without the ant being asked. It watches for a command that fails and then succeeds, because the fix the ant found in between is knowledge it earned and is worth remembering before the same command bites again (doc 08 section 2.3). It emits candidates only; the loop generates their ids and writes them to the pending table, so a harvested observation is no more recallable than a proposed one until a fold has weighed it.
func NewHarvester ¶ added in v0.3.0
NewHarvester builds a harvester for one ant's namespace, stamping every candidate it emits with the ant's provenance.
func (*Harvester) Observe ¶ added in v0.3.0
func (h *Harvester) Observe(o CommandOutcome) *sqlite.Candidate
Observe records one command outcome and returns an observation candidate when it completes a fail-then-succeed of the same command, else nil. A success with no prior failure is unremarkable, and a repeated failure just keeps the command marked until it eventually succeeds.
type ImportPlan ¶ added in v0.3.0
type ImportPlan struct {
Update []PortableRow // an existing id whose label or body changed
Insert []PortableRow // a block with no matching live id
Archive []string // a live id absent from the edited file
}
ImportPlan is the diff of an edited export against live memory: the rows whose text a human changed, the blocks a human added, and the ids a human removed by deleting their block. It is pure data, computed by Reconcile and applied by the command, so the diff logic is testable without a store.
func Reconcile ¶ added in v0.3.0
func Reconcile(live, edited []PortableRow) ImportPlan
Reconcile diffs the edited rows against the live rows by id. An edited body or label updates the row and (at apply time) marks it read_only, because a human edit is the highest-provenance input and the consolidator must not rewrite it (D11). A block with no id, or with an id that names no live row, becomes a fresh human-authored row so the words are never lost. A live id the file no longer mentions is archived. An unchanged block is left alone.
type IndexCap ¶ added in v0.3.0
type IndexCap struct {
// Lines is the hard ceiling on rendered lines, the Claude Code auto-memory
// shape. Pins past it are summarized in one overflow line, never dropped
// silently.
Lines int
// PerLine is the hard ceiling on a single line's length in bytes; a longer
// line is truncated with an ellipsis rather than allowed to overflow.
PerLine int
}
IndexCap bounds the pinned index so it always fits in the cached prompt prefix and stays cheap enough to afford every turn. The caps force the consolidator to compress rather than accrete: a colony that would overflow them must fold its pins tighter, not spend more of the window (D14, research/memory_swarm.md section 2, recommendation 3).
type NullEmbedder ¶ added in v0.3.0
type NullEmbedder struct{}
NullEmbedder is the no-endpoint path. Recall falls back to BM25 over FTS5, which is strong on its own for the identifier-heavy queries a coding memory answers, which is exactly why D10 weights BM25 high; so memory is useful with nothing configured at all.
func (NullEmbedder) Configured ¶ added in v0.3.0
func (NullEmbedder) Configured() bool
func (NullEmbedder) Model ¶ added in v0.3.0
func (NullEmbedder) Model() string
type OpenAIEmbedder ¶ added in v0.3.0
type OpenAIEmbedder struct {
// contains filtered or unexported fields
}
OpenAIEmbedder calls the /v1/embeddings endpoint, the sibling of the chat shape the provider layer already speaks, which covers Ollama, llama.cpp with --embeddings, LM Studio, and the tailnet box.
func NewOpenAIEmbedder ¶ added in v0.3.0
func NewOpenAIEmbedder(baseURL, apiKey, model string, dim int) *OpenAIEmbedder
NewOpenAIEmbedder builds the endpoint-backed embedder. Local endpoints pass an empty key. A dim of zero disables the dimension check; a positive dim is asserted against every returned vector so a wrong model is caught at the first call rather than as silently bad recall.
func (*OpenAIEmbedder) Configured ¶ added in v0.3.0
func (e *OpenAIEmbedder) Configured() bool
func (*OpenAIEmbedder) Embed ¶ added in v0.3.0
Embed posts one input and returns its vector. Every failure is an error the caller decides how to treat; Vectorize turns it into a degraded FTS-only write, and the re-embed pass leaves the row for the next fold.
func (*OpenAIEmbedder) Model ¶ added in v0.3.0
func (e *OpenAIEmbedder) Model() string
type Options ¶
type Options struct {
// Cwd is the working directory the parent walk starts from.
Cwd string
// Root is the project root (the git root); the parent walk stops here.
Root string
// GlobalDir is the global nest, home of the user-global memory file.
GlobalDir string
// PerFileCap overrides DefaultPerFileCap when nonzero.
PerFileCap int
// contains filtered or unexported fields
}
Options configures discovery and assembly.
type PortableAnchor ¶ added in v0.3.0
PortableAnchor is one anchor in the exported form: a kind, a ref, and the content hash the memory was true at, empty when the anchor carries none.
type PortableRow ¶ added in v0.3.0
type PortableRow struct {
ID string
Kind string
Namespace string
Label string
Body string
Anchors []PortableAnchor
Importance int
From string
}
PortableRow is one memory in the human-editable export form. It carries the stable id in an HTML comment so import can match a row back to the store, the label and body a developer actually reads and edits, and the anchors, importance, and provenance that round-trip through the file. From is the rendered provenance tail; it is written on export and ignored on import, because provenance is the store's to state, not the human's to rewrite.
func ParsePortable ¶ added in v0.3.0
func ParsePortable(md string) ([]PortableRow, error)
ParsePortable reads the export markdown back into rows. A block with an ari:memory comment carries its id; a block a human added with only a heading has an empty id, which import turns into a fresh row. Parsing is lenient by design: a developer edits this by hand, so a missing hash or a dropped from line is not an error, it is just less to round-trip.
type Row ¶ added in v0.3.0
Row is one pinned memory as the index renders it, decoupled from the store's row so the renderer stays a pure function of its inputs. Label is the pin's short handle; Anchors are its file, symbol, or command references, rendered as a trailing hint so the ant knows what a pin is about without a recall.
type ToolStore ¶ added in v0.3.0
type ToolStore interface {
// InsertCandidate queues a proposed memory for the next fold (D12).
InsertCandidate(ctx context.Context, id string, c sqlite.Candidate) error
// Recall runs hybrid recall and bumps access stats (slice 4).
Recall(ctx context.Context, ns, query string, vec []float32, budget int) ([]sqlite.Memory, error)
// MemoryLabel reads a live row's label for the forget consequence.
MemoryLabel(ctx context.Context, ns, id string) (string, bool, error)
// ArchiveMemory retires a row (sets archived_at); it never deletes.
ArchiveMemory(ctx context.Context, ns, id string) (string, bool, error)
}
ToolStore is the slice of the memory store the three memory tools reach. The concrete *sqlite.Store satisfies it; a test drives the tools with a fake. The interface is narrow on purpose: the tools are the ant's hands on its own memory and they do exactly three things, propose, recall, and retire.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package fold holds the consolidator, the one writer of live memory.
|
Package fold holds the consolidator, the one writer of live memory. |
|
Package sqlite is the colony.db substrate: one modernc SQLite file per project, opened in WAL mode, with every write funneled through a single goroutine and reads served from their own pool so recall never waits behind a fold (doc 03 slice 1, D10).
|
Package sqlite is the colony.db substrate: one modernc SQLite file per project, opened in WAL mode, with every write funneled through a single goroutine and reads served from their own pool so recall never waits behind a fold (doc 03 slice 1, D10). |