Files
2026-07-13 13:00:08 +08:00

244 lines
9.1 KiB
Markdown

# Session History and Synthesis Memory Retrieval
This document describes the lightweight retrieval layer added for session
history and saved memories. It is the implementation note behind the `history`
and `memory` tools, the archive-on-forget behavior, and the fresh human approval
gate for agent-written memory.
## Goals
- Bring useful past-session context back without injecting dynamic history into
the stable system prompt.
- Keep Reasonix cache-first: stable prompt bytes stay stable across turns, while
history and saved facts are fetched on demand.
- Avoid a heavy retrieval dependency. The implementation is pure Go and does not
introduce SQLite, CGO, a vector database, or an embedding model.
- Make memory trustworthy. Agent-initiated memory writes and archives must be
visible to the user and approved every time.
- Preserve traceability. A wrong memory should stop affecting the agent, but the
removed document should remain inspectable.
## Non-Goals
- This is not semantic embedding search. It is lexical retrieval with BM25,
tuned for code, commands, error phrases, filenames, and explicit decisions.
- This does not auto-summarize every session into memory. The `memory` layer is a
synthesis cache: only stable conclusions that the user approves become saved
documents.
- Archived memories are not active knowledge. They exist for audit and recovery,
not for recall.
## Retrieval Core
`internal/retrieval` contains the shared retrieval primitives:
- tokenization for Latin words and CJK runes;
- document-frequency and BM25 scoring;
- compact snippets around query terms;
- `KeepTopRelativeScore`, which keeps the best hit but trims weak trailing hits
below a relative score floor.
The relative score floor is intentionally small (`0.15`) and applied after
sorting. It prevents common-word-only matches from crowding out the useful hit,
while still preserving multiple close matches when they are genuinely relevant.
## Session History Tool
The `history` tool is read-only and lives in `internal/history`.
It supports two operations:
- `search`: rank saved session records by BM25.
- `around`: read a bounded transcript window around a returned hit.
Search input can be scoped:
- `project`: current session directory only.
- `global`: current session directory, user-global session directory, and
compacted-history archive directory.
Search indexes these record kinds by default:
- user text;
- assistant text;
- tool inputs;
- tool errors.
Normal tool output is excluded by default because it can be large and noisy. It
can be requested explicitly with `kind=["tool_output"]`, optionally filtered by
`tool_name`.
`around` enforces path confinement. It only accepts paths under the configured
session or archive roots, so a model cannot use the tool as a general file reader.
When search returns no hits, the tool explicitly tells the agent that zero
results are not proof that an event never happened. It suggests retrying with
rarer terms, widening scope, or including tool output only when needed.
## Saved Memory Recall Tool
The `memory` tool is read-only and lives in `internal/memory/recall.go`.
It supports:
- `search`: BM25 over active memory files.
- `read`: return one full active memory by name.
- `list`: show the active memory index, optionally filtered by type.
Only active memories from the project memory store participate. Archived memory
files are excluded from `search`, `read`, and `list`.
The searchable text combines:
- slug/name;
- title;
- normalized type;
- description;
- body.
This makes short user-facing descriptions useful, while still allowing recall by
the detailed body when the agent knows a rare phrase from the saved fact.
## Memory as Synthesis Cache
Reasonix treats saved memory as a synthesis cache rather than as a raw transcript
cache.
The intended workflow is:
1. The agent searches `history` or `memory` when it needs old context.
2. If retrieval produces a stable reusable conclusion, the agent proposes a
`remember` write.
3. The user reviews and approves or denies the write.
4. Future sessions can reuse the saved document directly.
This avoids repeatedly paying retrieval cost for the same stable conclusion,
while keeping the saved set small and auditable.
## Desktop Candidate Suggestions
The desktop Memory page can scan recent local sessions and produce draft
candidates:
- memory candidates from explicit long-lived preferences, rules, or project
conventions in recent user turns;
- skill candidates from repeated workflow categories across recent sessions.
This is intentionally a suggestion layer, not an automatic writer:
- scanning can be run manually from the Memory page. Users may also enable a
desktop UI preference that scans automatically when the Suggestions tab opens;
- candidates show their proposed body plus short evidence snippets before any
write;
- accepting a memory candidate writes through the controller's active memory
path, so the current session gets the same transient turn-tail update as a
`remember` write;
- accepting a skill candidate writes through the normal skill store, preserving
skill name validation, scope handling, and no-overwrite behavior.
No candidate scan changes the stable system prompt or provider-visible tool
schema. Saved memories and created skills become part of the stable prefix only
through the existing next-session discovery path.
## Archive-on-Forget
`forget` no longer permanently deletes the memory file. It removes the memory
from the active index and moves the file into `.archive/` with a timestamped
filename:
```
.archive/<UTC timestamp>-<name>.md
```
The active store and recall tool ignore archived files. Local management
surfaces still expose them for traceability:
- `/memory`;
- CLI/TUI memory views;
- desktop memory panel.
This is important because an incorrect memory can be more disruptive than no
memory, but a hard delete makes it difficult to audit how the agent reached a
bad conclusion.
## Human Approval Contract
Agent-initiated `remember` and `forget` calls require a fresh approval every
time.
The controller treats these tools like plan approval:
- Auto approval and YOLO/full-access mode do not bypass them.
- Guardian/safety review cannot allow them on the user's behalf.
- Session grants and persistent allow rules are not created for them.
- Pending memory approvals are not drained when the user toggles auto approval.
- Non-interactive headless runs and sub-agents refuse them instead of treating
`Ask` as autonomous allow.
The approval subject is generated from the tool arguments before the
`ApprovalRequest` event is emitted:
- `remember` shows a compact preview of the name/title, normalized type,
description, and body.
- `forget` shows the memory name being archived.
External notification hooks only receive the tool name, not the memory body,
because notification channels may be less private than the local UI.
User-initiated memory edits in the desktop panel or CLI remain direct user
actions and do not go through the agent approval prompt.
## Boot Wiring
`internal/boot` registers the tools in the shared registry:
- `history`;
- `memory`;
- `remember`;
- `forget`.
The saved memory index still folds into the system prompt once at session start,
after the base prompt. This preserves the cache-first prefix contract. Mid-session
memory changes are injected only as transient turn-tail notes and become part of
the stable prefix on the next session.
## UI and CLI Surfaces
Local management surfaces distinguish active and archived memory:
- Active memories can be searched, read, and used by the agent.
- Archived memories are read-only audit entries.
- Candidate suggestions are drafts until the user confirms them.
The desktop `Memory()` payload always returns non-nil arrays for docs, facts,
archives, and scopes. This is a Wails JSON contract: nil Go slices encode as
`null`, while the frontend expects arrays for `.map` and `.length`.
## Test Coverage
The change is covered across layers:
- retrieval scoring, snippets, tokenizer behavior, and relative-score trimming;
- `history` search, global/archive scope, tool input/error indexing,
common-word-noise trimming, path confinement, and `around`;
- `memory` search/read/list, type filtering, 0-result fallback guidance, archived
memory exclusion, and validation;
- archive-on-forget file movement, index updates, timestamp parsing, ordering,
and read-only file repair;
- controller approval behavior under ask/auto/YOLO, including fresh approval and
approval-preview visibility;
- boot-level tool registration and real model tool-call execution;
- desktop `Memory()` payload shape for active and archived facts;
- desktop memory/skill candidate generation, confirmation writes, and non-nil
suggestion arrays;
- frontend CSS and TypeScript checks with generated Wails bindings.
## Operational Notes
- Prefer distinctive search terms: function names, command fragments, error
text, ticket IDs, file names, and decision keywords.
- Use `history` when the original wording or tool output matters.
- Use `memory` when looking for approved, stable conclusions.
- Archive wrong facts instead of overwriting them when the old fact should no
longer influence the agent and should remain traceable.