feat: add Altimate Memory — persistent cross-session memory with TTL, namespaces, citations, and audit logging#136
Conversation
…t context Adds a file-based persistent memory system that allows the AI agent to retain and recall context across sessions — warehouse configurations, naming conventions, team preferences, and past analysis decisions. Three new tools: memory_read, memory_write, memory_delete with global and project scoping, YAML frontmatter format, atomic writes, size/count limits, and system prompt injection support. Closes #135 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
…ect analysis - Rename tool IDs to altimate_memory_read/write/delete - Add comprehensive documentation at docs/data-engineering/tools/memory-tools.md - Document context window impact, stale memory risks, wrong memory detection, security considerations, and mitigation strategies - Add altimate_change markers consistent with codebase conventions - Update tools index to include Altimate Memory category Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…udit logging, citations, session extraction, and global opt-out to Altimate Memory Implements P0/P1 improvements: - TTL expiration via optional `expires` field with automatic filtering - Hierarchical namespace IDs with slash-separated paths mapped to subdirectories - Deduplication detection on write with tag-overlap warnings - Audit log for all CREATE/UPDATE/DELETE operations - Citation-backed memories with file/line/note references - Session-end batch extraction tool (opt-in via ALTIMATE_MEMORY_AUTO_EXTRACT) - Global opt-out via ALTIMATE_DISABLE_MEMORY environment variable - Comprehensive tests: 175 tests covering all new features Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| function blockPath(scope: "global" | "project", id: string): string { | ||
| return path.join(dirForScope(scope), ...id.split("/").slice(0, -1), `${id.split("/").pop()}.md`) | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed in two commits:
1. Regex hardened (8b918cb): Replaced the permissive regex with a segment-based regex that validates each path component individually. IDs like a/../b, a/./b, a..b, a//b are now all rejected at the Zod schema level.
2. Runtime guard added (5807afc): blockPath() now verifies the resolved path stays within the memory directory using path.resolve(). Even if a malicious ID somehow bypassed the regex, the runtime check throws before any filesystem access.
71 adversarial tests added covering path traversal, frontmatter injection, Unicode edge cases, and more.
…ests Security fixes: - Replace permissive ID regex with segment-based validation that rejects '..', '.', '//', and all path traversal patterns (a/../b, a/./b, etc.) - Use unique temp file names (timestamp + random suffix) to prevent race condition crashes during concurrent writes to the same block ID The old regex /^[a-z0-9][a-z0-9_/.-]*[a-z0-9]$/ allowed dangerous IDs like "a/../b" or "a/./b" that could escape the memory directory via path.join(). The new regex validates each path segment individually. Adds 71 adversarial tests covering: - Path traversal attacks (10 tests) - Frontmatter injection and parsing edge cases (9 tests) - Unicode and special character handling (6 tests) - TTL/expiration boundary conditions (6 tests) - Deduplication edge cases (7 tests) - Concurrent operations and race conditions (4 tests) - ID validation gaps (11 tests) - Malformed files on disk (7 tests) - Serialization round-trip edge cases (5 tests) - Schema validation with adversarial inputs (6 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| const existing = await list(block.scope, { includeExpired: true }) | ||
| const isUpdate = existing.some((b) => b.id === block.id) | ||
| if (!isUpdate && existing.length >= MEMORY_MAX_BLOCKS_PER_SCOPE) { | ||
| throw new Error( | ||
| `Cannot create memory block "${block.id}": scope "${block.scope}" already has ${MEMORY_MAX_BLOCKS_PER_SCOPE} blocks (maximum). Delete an existing block first.`, | ||
| ) | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed in 5807afc:
write() now only counts non-expired blocks against MEMORY_MAX_BLOCKS_PER_SCOPE. The capacity check changed from:
allBlocks.length >= limit // old: counts expired blocks
to:
allBlocks.filter(b => \!isExpired(b)).length >= limit // new: only active blocks
Additionally, when total files on disk hit capacity, expired blocks are auto-cleaned before writing the new block — so expired blocks never permanently consume disk quota.
Two new tests added: "expired blocks do not count against capacity limit" and "auto-cleans expired blocks when at disk capacity".
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
…guard
Addresses PR review comments:
1. Expired blocks counted against capacity (sentry[bot] MEDIUM):
- write() now only counts non-expired blocks against MEMORY_MAX_BLOCKS_PER_SCOPE
- Auto-cleans expired blocks from disk when total file count hits capacity
- Users no longer see "scope full" errors when all blocks are expired
2. Path traversal defense-in-depth (sentry[bot] CRITICAL):
- Added runtime path.resolve() guard in blockPath() to verify the resolved
path stays within the memory directory, as a second layer behind the
segment-based ID regex from the previous commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
- Add schema validation on disk reads (MemoryBlockSchema.safeParse) - Add safe ID regex to MemoryReadTool and MemoryDeleteTool parameters - Fix include_expired ignored when reading by specific ID - Fix duplicate tags inflating dedup overlap count (dedupe with Set) - Move expired block cleanup to after successful write - Eliminate double directory scan in write() by passing preloaded blocks - Fix docs/code mismatch: max ID length 128 -> 256 - Add 22 new tests covering all fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inject memory blocks into system prompt at session start, gated by ALTIMATE_DISABLE_MEMORY flag - Add memory_operation and memory_injection telemetry events to App Insights - Add memory tool categorization for telemetry - Document disabling memory for benchmarks/CI - Add injection integration tests and telemetry event tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… namespaces, citations, and audit logging (#136) * feat: add Letta-style persistent memory blocks for cross-session agent context Adds a file-based persistent memory system that allows the AI agent to retain and recall context across sessions — warehouse configurations, naming conventions, team preferences, and past analysis decisions. Three new tools: memory_read, memory_write, memory_delete with global and project scoping, YAML frontmatter format, atomic writes, size/count limits, and system prompt injection support. Closes #135 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: rebrand to Altimate Memory, add comprehensive docs and side-effect analysis - Rename tool IDs to altimate_memory_read/write/delete - Add comprehensive documentation at docs/data-engineering/tools/memory-tools.md - Document context window impact, stale memory risks, wrong memory detection, security considerations, and mitigation strategies - Add altimate_change markers consistent with codebase conventions - Update tools index to include Altimate Memory category Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add TTL expiration, hierarchical namespaces, dedup detection, audit logging, citations, session extraction, and global opt-out to Altimate Memory Implements P0/P1 improvements: - TTL expiration via optional `expires` field with automatic filtering - Hierarchical namespace IDs with slash-separated paths mapped to subdirectories - Deduplication detection on write with tag-overlap warnings - Audit log for all CREATE/UPDATE/DELETE operations - Citation-backed memories with file/line/note references - Session-end batch extraction tool (opt-in via ALTIMATE_MEMORY_AUTO_EXTRACT) - Global opt-out via ALTIMATE_DISABLE_MEMORY environment variable - Comprehensive tests: 175 tests covering all new features Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: harden Altimate Memory against path traversal, add adversarial tests Security fixes: - Replace permissive ID regex with segment-based validation that rejects '..', '.', '//', and all path traversal patterns (a/../b, a/./b, etc.) - Use unique temp file names (timestamp + random suffix) to prevent race condition crashes during concurrent writes to the same block ID The old regex /^[a-z0-9][a-z0-9_/.-]*[a-z0-9]$/ allowed dangerous IDs like "a/../b" or "a/./b" that could escape the memory directory via path.join(). The new regex validates each path segment individually. Adds 71 adversarial tests covering: - Path traversal attacks (10 tests) - Frontmatter injection and parsing edge cases (9 tests) - Unicode and special character handling (6 tests) - TTL/expiration boundary conditions (6 tests) - Deduplication edge cases (7 tests) - Concurrent operations and race conditions (4 tests) - ID validation gaps (11 tests) - Malformed files on disk (7 tests) - Serialization round-trip edge cases (5 tests) - Schema validation with adversarial inputs (6 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: expired blocks no longer count against capacity limit, add path guard Addresses PR review comments: 1. Expired blocks counted against capacity (sentry[bot] MEDIUM): - write() now only counts non-expired blocks against MEMORY_MAX_BLOCKS_PER_SCOPE - Auto-cleans expired blocks from disk when total file count hits capacity - Users no longer see "scope full" errors when all blocks are expired 2. Path traversal defense-in-depth (sentry[bot] CRITICAL): - Added runtime path.resolve() guard in blockPath() to verify the resolved path stays within the memory directory, as a second layer behind the segment-based ID regex from the previous commit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address consensus code review findings for Altimate Memory - Add schema validation on disk reads (MemoryBlockSchema.safeParse) - Add safe ID regex to MemoryReadTool and MemoryDeleteTool parameters - Fix include_expired ignored when reading by specific ID - Fix duplicate tags inflating dedup overlap count (dedupe with Set) - Move expired block cleanup to after successful write - Eliminate double directory scan in write() by passing preloaded blocks - Fix docs/code mismatch: max ID length 128 -> 256 - Add 22 new tests covering all fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire up memory injection into system prompt with telemetry - Inject memory blocks into system prompt at session start, gated by ALTIMATE_DISABLE_MEMORY flag - Add memory_operation and memory_injection telemetry events to App Insights - Add memory tool categorization for telemetry - Document disabling memory for benchmarks/CI - Add injection integration tests and telemetry event tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds Altimate Memory — a persistent, cross-session memory system purpose-built for data engineering agents. Instead of re-explaining warehouse configurations, naming conventions, and team preferences every session, the agent remembers what matters and picks up where you left off.
Closes #135
The problem
Every new session starts from zero. The agent doesn't know your Snowflake warehouse is called
ANALYTICS_WH, that your team usesstg_prefix for staging models, or that you optimized theordersquery last week. You re-explain the same context repeatedly.Research backs this up:
What this PR adds
Five altimate-branded tools
altimate_memory_readaltimate_memory_writealtimate_memory_deletealtimate_memory_auditaltimate_memory_extractDual scoping
~/.local/share/altimate-code/memory/.opencode/memory/Key features
TTL expiration (P0)
expiresISO datetime field on any memory blocklist()and prompt injectioninclude_expiredoption on read for inspectionHierarchical namespaces (P1)
warehouse/snowflake,conventions/dbt/naming)list()Deduplication on write (P1)
Audit logging (P1)
.logfile per scopealtimate_memory_audittool to inspect the logCitation-backed memories
citationsarray with file path, line number, and noteSession-end auto-extraction (opt-in)
altimate_memory_extracttool for batch-saving discovered factsALTIMATE_MEMORY_AUTO_EXTRACTenv var (off by default)Global opt-out
ALTIMATE_DISABLE_MEMORY=trueto completely disable all memory toolsFile format
Plain Markdown with YAML frontmatter — human-readable, editable, version-controllable:
Safety and limits
.tmp, then rename — prevents corruption on crashPotential side effects
Context window impact
Stale or incorrect memories
cat .opencode/memory/*.md), ask agent to delete/update, or edit the Markdown fileexpireson volatile information so it auto-expiresWrong information saved by the agent
Memory: Created "..."/Memory: Updated "..."), plus dedup warningsaltimate_memory_delete, direct file edit, or ask agent to rewritealtimate_memory_auditto see what was written and whenSecurity
.gitignoreif it contains sensitive contextFiles changed
Test plan
bun test test/memory/)altimate_memory_writeto save warehouse config, start new session, verifyaltimate_memory_readrecalls it.opencode/memory/files are human-readable Markdownexpires, confirm hidden)warehouse/snowflake, check subdirectory created)altimate_memory_audit)ALTIMATE_DISABLE_MEMORY=trueremoves all memory tools🤖 Generated with Claude Code