Files
rohitg00__agentmemory/.env.example
T
efenex e68d4ebe9b fix(summarize): chunk large sessions to fit LLM context window (#472)
* fix(summarize): chunk large sessions to fit LLM context window

JSONL-imported sessions can have far more observations than the
500-cap MAX_OBS_PER_SESSION that constrains native sessions.
mem::summarize previously built one prompt containing every
observation and shipped it as a single LLM call, which exceeded the
provider's context window for sessions >~7,000 observations and
returned an unhelpful 400 from upstream — silently leaving large
bulk-imported sessions out of the semantic tier.

Approach: map-reduce inside mem::summarize.
- Sessions ≤ SUMMARIZE_CHUNK_SIZE (default 400) take the legacy
  single-call path with no overhead
- Larger sessions are split into chunks, each summarized with the
  existing per-session prompt in parallel batches of
  SUMMARIZE_CHUNK_CONCURRENCY (default 6), and partial summaries
  merged via a new REDUCE_SYSTEM prompt
- Per-chunk retry-once on transient parse / provider errors
- Persistently-failing chunks are skipped (not propagated) so a
  flaky chunk doesn't waste 30+ already-completed LLM calls on
  the same session
- Bail with too_many_chunks_skipped only if >50% of chunks fail

Companion operator tool: scripts/backfill-imported-sessions.sh
walks jsonl-imported sessions and POSTs mem::summarize per session,
with project / agent / obs-count filters, cost estimation, and
per-failure payload dumping for debugging provider rejections.

Validated locally against a real corpus:
- 5,392-obs session (14 chunks, c=6): 39s
- 10,704-obs session (27 chunks, c=6): 34s
- 105,966-obs session (265 chunks, c=50): handler completes
  server-side and persists
- 52-session bulk backfill → 25 new semantic facts + 6 new reflect
  insights produced by consolidate-pipeline

Known limit: iii-engine has a hardcoded 180s function-invocation
timeout. Sessions large enough that chunked summarize wallclock
exceeds that will return a timeout/500 to the HTTP client even
though the handler completes and persists server-side. High-RPM
providers (Novita / DeepInfra / DeepSeek typically allow 100+
concurrent) can raise SUMMARIZE_CHUNK_CONCURRENCY to push the
cliff well past any realistic session size. True fix is an
async-job pattern; left as follow-up.

- src/prompts/summary.ts: add REDUCE_SYSTEM + buildReducePrompt
- src/functions/summarize.ts: chunking, retry, skip, parallelism
- test/summarize.test.ts: 9 cases covering single-call path,
  chunking, env-override, retry-then-success, persistent skip,
  too-many-skipped bail, provider error after retry, concurrency
- .env.example: document SUMMARIZE_CHUNK_SIZE / _CONCURRENCY
- .gitignore: agentmemory-debug/, data-*/  (operator artefacts)
- scripts/backfill-imported-sessions.sh: bulk-import backfill tool

9/9 new tests pass; existing tests untouched.

* fix(summarize): address CodeRabbit review on #472

Four nits flagged by the automated reviewer, all worth fixing:

- scripts/backfill: add curl --connect-timeout + --max-time profiles
  (META_CURL_OPTS vs WORK_CURL_OPTS). Metadata reads fail fast and
  retry on transient blips; LLM-backed work calls get a wide 30-min
  cap and no retry (retrying a half-finished LLM job double-spends).
- scripts/backfill: sanitize sessionId before joining with DEBUG_DIR
  in dump_failure() (otherwise a session id containing `/` or `..`
  could escape the debug dir). UUIDs in practice, but the server
  doesn't enforce that.
- scripts/backfill: switch the observations query to
  `--get --data-urlencode "sessionId=$id"` so special characters
  can't corrupt the query string.
- scripts/backfill: guard `jq` on summarize + consolidate responses
  with `jq -e . </dev/null 2>&1` first. iii's HTTP layer occasionally
  returns non-JSON (HTML 5xx, empty body on timeout). Without the
  guard, `set -e` aborts the whole backfill loop on a single bad
  response — now it logs `invalid_json_response` and moves on.
- test/summarize.test.ts: fix `vi.mock("./audit.js", ...)` path to
  `"../src/functions/audit.js"`. The old path resolved to
  `test/audit.js` (nonexistent), so the mock was a silent no-op.
  Tests passed anyway because `safeAudit` writes to a mocked KV.

9/9 tests still pass; backfill dry-run still resolves the corpus
cleanly.
2026-05-19 19:29:06 +01:00

176 lines
11 KiB
Bash

# =============================================================================
# agentmemory configuration
# =============================================================================
#
# Copy this file to `~/.agentmemory/.env` (or to your project root if you
# prefer scoped config) and uncomment the lines you want to override.
#
# Every line is OFF by default — `agentmemory` runs out of the box with no
# LLM key, no embedding key, and no API auth. Set keys here only when you
# want to enable the corresponding feature.
#
# Run `npx @agentmemory/agentmemory init` to copy this file into place
# automatically. Run `npx @agentmemory/agentmemory doctor` to verify that
# the daemon reads the env you expect.
#
# Defaults shown in comments. Listed in priority order — the first key
# present wins on the LLM detection path (see src/config.ts::detectProvider).
# -----------------------------------------------------------------------------
# 1. LLM provider — pick ONE
# -----------------------------------------------------------------------------
#
# Without a provider key, agentmemory runs in noop mode: observations are
# indexed via zero-LLM synthetic compression, hybrid search still works,
# but LLM-backed summarisation / reflection / consolidation are disabled.
# The detection order is OPENAI_API_KEY → MINIMAX_API_KEY → ANTHROPIC_API_KEY
# → GEMINI_API_KEY → OPENROUTER_API_KEY → noop.
# OPENAI_API_KEY=sk-... # Used for OpenAI-compatible embeddings today. PR #307 will extend this to chat completions (DeepSeek, SiliconFlow, vLLM, LM Studio, Ollama via `/v1`).
# OPENAI_BASE_URL=https://api.openai.com # Override for OpenAI-compatible providers
# ANTHROPIC_API_KEY=sk-ant-...
# ANTHROPIC_MODEL=claude-sonnet-4-20250514 # Default Anthropic model
# ANTHROPIC_BASE_URL=https://api.anthropic.com # Override for Anthropic-compatible proxies / Azure AI Foundry
# GEMINI_API_KEY=... # Either env name works; GEMINI_API_KEY takes precedence
# GOOGLE_API_KEY=... # Alias for GEMINI_API_KEY when set alone (emits a one-time stderr hint)
# GEMINI_MODEL=gemini-2.5-flash # Default Gemini model (auto-detected GA model)
# OPENROUTER_API_KEY=sk-or-...
# OPENROUTER_MODEL=anthropic/claude-sonnet-4-20250514
# MINIMAX_API_KEY=...
# MINIMAX_MODEL=MiniMax-M2.7
# MAX_TOKENS=4096 # Cap LLM completion tokens for compression / summarise calls
# Outbound LLM / embedding timeout — shared across every raw-fetch provider
# (Gemini, OpenRouter, MiniMax, OpenAI LLM, and OpenAI/Cohere/Voyage/OpenRouter
# embedding). The OpenAI LLM path also honours the OpenAI-scoped
# OPENAI_TIMEOUT_MS alias for back-compat with v0.9.17 (precedence).
# AGENTMEMORY_LLM_TIMEOUT_MS=60000 # Default: 60 000 ms (60 s)
# Opt-in Claude-subscription fallback (spawns @anthropic-ai/claude-agent-sdk
# child sessions). Off by default — the agent-sdk fallback can trigger
# Stop-hook recursion (#149 follow-up) when invoked from inside Claude Code.
# AGENTMEMORY_ALLOW_AGENT_SDK=true
# FALLBACK_PROVIDERS=anthropic,gemini # Comma-separated chain tried after the primary provider returns an error (e.g. rate limit)
# -----------------------------------------------------------------------------
# 2. Embedding provider — auto-detected, override via EMBEDDING_PROVIDER
# -----------------------------------------------------------------------------
#
# Without an embedding key, agentmemory runs in BM25-only mode for hybrid
# search. Detection order: EMBEDDING_PROVIDER override → GEMINI_API_KEY →
# OPENAI_API_KEY → VOYAGE_API_KEY → COHERE_API_KEY → OPENROUTER_API_KEY →
# local (Xenova/all-MiniLM-L6-v2, 384-dim).
# EMBEDDING_PROVIDER=local # local | openai | voyage | cohere | gemini | openrouter
# VOYAGE_API_KEY=pa-... # Optimised for code embeddings
# COHERE_API_KEY=... # General-purpose embeddings
# Reuses OPENAI_API_KEY / OPENAI_BASE_URL above when EMBEDDING_PROVIDER=openai.
# OPENAI_EMBEDDING_MODEL=text-embedding-3-small # Embedding model when EMBEDDING_PROVIDER=openai
# OPENAI_EMBEDDING_DIMENSIONS=1536 # Required when the model is not in the known-models table
# OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small # When EMBEDDING_PROVIDER=openrouter
# -----------------------------------------------------------------------------
# 3. Auth & security
# -----------------------------------------------------------------------------
#
# Bearer-token auth for the REST API + viewer + all integration plugins.
# Without a secret, REST endpoints are open on loopback. Set this when
# you expose the daemon beyond loopback or run behind a reverse proxy.
# AGENTMEMORY_SECRET=your-secret-here
# -----------------------------------------------------------------------------
# 4. Search tuning
# -----------------------------------------------------------------------------
# BM25_WEIGHT=0.4 # Hybrid search weight for BM25 leg
# VECTOR_WEIGHT=0.6 # Hybrid search weight for vector leg
# AGENTMEMORY_GRAPH_WEIGHT=0.2 # Graph traversal bonus on smart-search ranking
# TOKEN_BUDGET=2000 # Max tokens injected via mem::context per session
# MAX_OBS_PER_SESSION=500 # Per-session observation cap before consolidation kicks in
# SUMMARIZE_CHUNK_SIZE=400 # When mem::summarize sees a session larger than this, it chunks observations and map-reduces (chunk-summarize → reduce-merge) to stay within the LLM's context window. Default 400 ≈ 50k tokens per chunk at ~110 tok/obs. Native sessions are capped by MAX_OBS_PER_SESSION; chunking primarily matters for bulk-imported jsonl sessions, which bypass that cap.
# SUMMARIZE_CHUNK_CONCURRENCY=6 # Parallel chunk LLM calls during chunked summarize. Default 6 fits ~100-chunk sessions under iii's 180s function-invocation timeout at typical ~8s/call. High-throughput providers (Novita, DeepInfra, DeepSeek) commonly allow 100+ concurrent — bump this for very large imported sessions.
# -----------------------------------------------------------------------------
# 5. Behaviour flags
# -----------------------------------------------------------------------------
# AGENTMEMORY_AUTO_COMPRESS=true # Run LLM compression on every observation batch (requires a provider key). Default off — synthetic compression handles most cases.
# AGENTMEMORY_INJECT_CONTEXT=true # Inject recalled memories back into agent prompts (#143). Default off — hooks capture observations but do not modify conversation.
# CONSOLIDATION_ENABLED=true # Run the 4-tier consolidation pipeline (memories → semantic → procedural). Default off — opt in once you've measured the LLM cost.
# CONSOLIDATION_DECAY_DAYS=30 # Age (days) after which non-reinforced memories decay during consolidation
# GRAPH_EXTRACTION_ENABLED=true # Extract concept-graph edges on remember; powers the graph-traversal recall path
# GRAPH_EXTRACTION_BATCH_SIZE=8 # Memories per graph-extraction batch
# AGENTMEMORY_REFLECT=true # Periodically auto-synthesize lessons from memories
# AGENTMEMORY_DROP_STALE_INDEX=true # Drop on-disk BM25 / vector index on startup if dim guard fires (#248). Recovery toggle for stuck-state debugging.
# AGENTMEMORY_IMAGE_EMBEDDINGS=true # Enable image embeddings when an image provider is present (experimental).
# -----------------------------------------------------------------------------
# 6. CLI / runtime knobs
# -----------------------------------------------------------------------------
# AGENTMEMORY_TOOLS=all # core (7 tools, default) | all (51 tools) — surface exposed to MCP clients
# AGENTMEMORY_SLOTS=memory # Comma-separated plugin slot names the CLI should claim
# AGENTMEMORY_DEBUG=1 # Trace MCP shim probe + standalone fallback decisions to stderr
# AGENTMEMORY_FORCE_PROXY=1 # Skip the MCP shim livez probe and trust AGENTMEMORY_URL (for sandboxed MCP clients that can't reach localhost)
# AGENTMEMORY_PROBE_TIMEOUT_MS=2000 # MCP shim livez probe timeout
# AGENTMEMORY_URL=http://localhost:3111 # REST base URL — honored by status, doctor, MCP shim
# AGENTMEMORY_VIEWER_URL=http://localhost:3113 # Override the viewer URL printed by `agentmemory status`
# AGENTMEMORY_EXPORT_ROOT=~/agentmemory-backup # Default destination for `agentmemory export`
# STANDALONE_MCP=1 # MCP shim only — bypass the worker and run @agentmemory/mcp in-process
# STANDALONE_PERSIST_PATH=~/.agentmemory/local.db # Path used by the standalone MCP shim's local fallback store
# Snapshot exporter — periodic snapshots of state_store + stream_store.
# SNAPSHOT_ENABLED=true
# SNAPSHOT_DIR=~/.agentmemory/snapshots
# SNAPSHOT_INTERVAL=3600 # Seconds between snapshots
# Team sharing — when set, memories are scoped to (TEAM_ID, USER_ID) tuples.
# TEAM_MODE=shared
# TEAM_ID=acme
# USER_ID=rohit
# -----------------------------------------------------------------------------
# 7. Ports
# -----------------------------------------------------------------------------
# III_REST_PORT=3111 # REST API port (also affects viewer at +2)
# III_STREAMS_PORT=3112 # Streams API port
# III_ENGINE_URL=ws://localhost:49134 # iii-engine WebSocket URL (used by the worker)
# -----------------------------------------------------------------------------
# 8. iii engine pin
# -----------------------------------------------------------------------------
#
# agentmemory currently pins iii-engine to v0.11.2 — v0.11.6 introduces a
# new sandbox-everything-via-`iii worker add` model that agentmemory
# hasn't been refactored for yet. Override with AGENTMEMORY_III_VERSION
# only after migrating to the sandbox model manually.
# AGENTMEMORY_III_VERSION=0.11.2
# -----------------------------------------------------------------------------
# 9. Claude Code bridge (opt-in)
# -----------------------------------------------------------------------------
# CLAUDE_MEMORY_BRIDGE=true # Mirror compressed memories into Claude Code's CLAUDE.md
# CLAUDE_PROJECT_PATH=/path/to/your/project # Required when CLAUDE_MEMORY_BRIDGE=true
# CLAUDE_MEMORY_LINE_BUDGET=200 # Lines of memory CLAUDE.md should hold
# -----------------------------------------------------------------------------
# 10. Obsidian export (opt-in)
# -----------------------------------------------------------------------------
# OBSIDIAN_AUTO_EXPORT=true # Auto-export memories to an Obsidian vault on every consolidation