Memory System
The memory layer in Agenvoy has three tiers for conversation memory plus a cross-session error memory tier.
| Tier | Backed by | Scope |
|---|---|---|
| 1. Context window (24 messages + summary) | history.json + summary.json |
session |
| 2. Semantic search (recent) | ToriiDB DBSessionHist (vector) |
session |
| 3. Full-text archive (all history) | SQLite FTS5 via go-sqlkit | session |
| Error memory | ToriiDB error_memory (90d TTL) |
cross-session |
| Operator notes | SQLite note + note_fts5 (trigram) |
global |
Three-tier conversation memory
1. Context window (limits.max_history_messages, default 24)
Each session keeps the most recent N messages in full and feeds them into the LLM context window. Anything older remains in history.json but is not sent to the LLM.
A rolling summary (summary.json) condenses older conversations and is injected into the system prompt at the top of each turn so older context survives beyond the N-message window.
Incremental cursor: .summary_cursor (per-session; the older summary.meta.json is migrated on startup) holds last_message_time (format YYYY-MM-DD HH:MM:SS, extracted from the timestamp in message content). On each summary.Generate invocation:
filterAfterTime(histories, cursor)keeps onlyt > cursormessages- Each chunk runs one
generatePassLLM call (the system prompt already includes{{.Summary}}=old summary, so merge happens during generation — no separatemergePass, avoiding 2x cost) - On success, cursor advances to that chunk's max timestamp +
SaveSummarytriggers the mtime gate generatePassfailure →return(don't bill subsequent chunks; next cron tick retries)
2. Semantic search — ToriiDB (recent conversations)
The chat_history tool with mode=search and match=semantic runs vector similarity search via ToriiDB db.VSearch. Each hit triggers a context window expansion: 2 entries before + 1 entry after.
ToriiDB entries are cleaned during history.json compaction — entries older than the compact cutoff are removed, keeping ToriiDB focused on recent conversations. Older data lives in SQLite (tier 3).
3. Full-text archive — SQLite FTS5 (all history)
Every message written to history.json is dual-written to SQLite (~/.config/agenvoy/.store/history.db) via go-sqlkit. SQLite always holds the complete conversation history, even after history.json is compacted.
The chat_history tool with mode=search and match=keyword runs FTS5 full-text search on the SQLite archive + ToriiDB substring match on recent entries, combining results.
Compaction: when history.json exceeds max_history_bytes (default 5 MiB), the oldest messages are trimmed to 80% on a complete user+assistant pair boundary. The cutoff timestamp is recorded in SQLite message_meta.start_at so that keyword search excludes entries already present in history.json (avoiding duplicates). ToriiDB entries older than the cutoff are also removed.
Backfill: on first encounter (SQLite has no data for a session but history.json has content), the entire existing history is backfilled into SQLite.
Timestamps: stored as UTC unix nanoseconds. Timestamps in message content are parsed via time.ParseInLocation (local timezone) and converted to UTC for storage. Search queries use time.Now().UnixNano() (already UTC).
In-run compaction
Beyond the stored tiers, internal/agents/exec/compact keeps a single long run inside the model's window. Each step is a separate stage, applied in order and only as far as needed:
| Stage | Trigger | Effect |
|---|---|---|
CheckThreshold(model) |
Every iteration | Token budget for the current model — 80 % of its context window (1 M for Gemini and recent GPT, 500 K for grok-4.5, 256 K for other GPT/Grok, 200 K for Claude, 128 K default) |
ToolHistory |
Budget exceeded | Summarize the oldest tool-call exchanges into text |
ExtractOldHistories |
Still over budget | Fold older conversation turns into the rolling summary |
TrimFallback |
Provider returns a context-length error | Drop the oldest exchange and resend |
RawToolFallback |
Model switch mid-run | Convert tool history to plain text so the new model can read it |
Manual compaction runs from the TUI (/compact; the /memory command was removed, reset is now /reset) or POST /v1/session/:id/memory. That one endpoint takes an action: compact drops older messages and returns removed, summary rebuilds the rolling summary and returns count, and reset clears the conversation — mode: "summary" keeps the rolling summary, mode: "all" wipes it too.
Search routing
match parameter |
Source | Use case |
|---|---|---|
semantic (default) |
ToriiDB VSearch | "What did we discuss about X?" — meaning-based |
keyword |
SQLite FTS5 (archive) + ToriiDB substring (recent) | "Find messages containing 'sandbox'" — exact text |
Cross-session error memory
Tool failures and the fix that resolved them persist across sessions in error_memory with 90-day TTL. Only records written with outcome=resolved are stored; failed / abandoned writes are discarded, so abandoned strategies are no longer kept. On hit (either via keyword Contains or db.VSearch), the entry's TTL is refreshed via db.Expire.
When the same tool name fails in a future session, toolCall.go automatically queries error_memory and appends up to 3 related records to the failed tool result as related_errors; the agent applies the recorded fix.
A record's recorded action can be edited from the web dashboard's Lessons view (PATCH /v1/torii/error, which rewrites action and keeps the TTL); GET /v1/torii/error lists records.
Storage layout
| Store | Content | Lifecycle |
|---|---|---|
history.json |
Recent messages (hot, LLM reads every turn) | Auto-compacted at 5 MiB |
ToriiDB DBSessionHist |
Recent messages with embeddings | Cleaned on compact (entries < cutoff removed) |
SQLite messages |
All messages ever written (dual-write) | Cleared on reset / remove-session |
SQLite message_meta |
start_at — compact cutoff timestamp |
Cleared on reset / remove-session |
summary.json |
Rolling summary blob | Survives reset |
ToriiDB error_memory |
Resolved tool error records with their fix | 90d TTL (refresh on hit) |
Reset / remove behavior
| Operation | history.json |
ToriiDB DBSessionHist |
SQLite (messages + meta) | summary.json |
|---|---|---|---|---|
| Compact (auto) | Trimmed to 80% | Entries < cutoff removed | Untouched (already has all data) | Untouched |
Reset (/reset, mode=summary) |
Deleted | Cleared | Cleared | Preserved |
Reset (mode=all) |
Deleted | Cleared | Cleared | Deleted |
| Remove session | Directory deleted | Cleared | Cleared | Directory deleted |
Migration note
Sessions and error memory used to live under per-session JSON files. They now live in the embedded stores under ~/.config/agenvoy/.store/ — ToriiDB (db_0 tool cache, db_1 chat vectors, db_2 error memory, db_3 online markers) and history.db for session config, operator notes, the message archive, action history, file history and usage. Notes moved from ToriiDB into the SQLite note table in v0.35.3, and the state table was dropped in the same release. bot.json / bot.md / config.json / status.json / usage.log / summary.meta.json are migrated in at daemon startup and no longer written. Do not reintroduce JSON paths.
[!NOTE] This document was auto-generated by Claude after reading the full source code.