Documentation v0.30.0

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

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.meta.json (per-session) holds last_message_time (format YYYY-MM-DD HH:MM:SS, extracted from the timestamp in message content). On each summary.Generate invocation:

  1. filterAfterTime(histories, cursor) keeps only t > cursor messages
  2. Each chunk runs one generatePass LLM call (the system prompt already includes {{.Summary}}=old summary, so merge happens during generation — no separate mergePass, avoiding 2x cost)
  3. On success, cursor advances to that chunk's max timestamp + SaveSummary triggers the mtime gate
  4. generatePass failure → return (don't bill subsequent chunks; next cron tick retries)

2. Semantic search — ToriiDB (recent conversations)

The search_chat_history tool with mode=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 search_chat_history tool with mode=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 session_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 (/memory compact) or POST /v1/session/:id/compact, which returns 202 Accepted and compacts in the background.

Search routing

mode 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, resolution paths, and abandoned strategies persist across sessions in error_memory with 90-day TTL. 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 injects relevant entries as hints into the next assistant turn:

Outcome on record Hint behavior
resolved Agent must apply the recorded resolution
failed / abandoned Agent must avoid the recorded strategy

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 session_meta start_at — compact cutoff timestamp Cleared on reset / remove-session
summary.json Rolling summary blob Survives reset
ToriiDB error_memory Tool error records with resolution metadata 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 (/memory reset) Deleted Cleared Cleared Preserved
Remove session Directory deleted Cleared Cleared Directory deleted

Migration note

Sessions and error memory used to live under per-session JSON files. Since ToriiDB v0.5.0 they are inside the embedded store. Do not reintroduce JSON paths.


[!NOTE] This document was auto-generated by Claude after reading the full source code.

中文