Skip to content

feat: add Altimate Memory — persistent cross-session memory with TTL, namespaces, citations, and audit logging#136

Merged
anandgupta42 merged 7 commits intomainfrom
feat/memory-blocks
Mar 15, 2026
Merged

feat: add Altimate Memory — persistent cross-session memory with TTL, namespaces, citations, and audit logging#136
anandgupta42 merged 7 commits intomainfrom
feat/memory-blocks

Conversation

@anandgupta42
Copy link
Contributor

@anandgupta42 anandgupta42 commented Mar 14, 2026

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 uses stg_ prefix for staging models, or that you optimized the orders query last week. You re-explain the same context repeatedly.

Research backs this up:

  • 62% of AI agent memories are wrong in opaque memory systems — Altimate Memory uses human-readable Markdown files you can inspect and fix
  • Memory poisoning is a real attack vector — Altimate Memory uses per-user/per-project isolation and plaintext files (auditable by humans and CI) rather than opaque vector stores
  • Context bloat degrades agent performance — Altimate Memory caps injection via a token budget and sorts by recency

What this PR adds

Five altimate-branded tools

Tool Purpose
altimate_memory_read Read/filter memory blocks by scope, tags, or ID (supports expired block inclusion)
altimate_memory_write Create or update a persistent memory block (with TTL, citations, dedup detection)
altimate_memory_delete Remove outdated or incorrect blocks
altimate_memory_audit View audit log of all memory create/update/delete operations
altimate_memory_extract Batch-save session facts as memory blocks (opt-in)

Dual scoping

Scope Location Use case
global ~/.local/share/altimate-code/memory/ User-wide: SQL style, preferred models
project .opencode/memory/ Project-specific: warehouse config, naming conventions, dbt patterns

Key features

TTL expiration (P0)

  • Optional expires ISO datetime field on any memory block
  • Expired blocks automatically filtered from list() and prompt injection
  • include_expired option on read for inspection

Hierarchical namespaces (P1)

  • Slash-separated IDs (e.g., warehouse/snowflake, conventions/dbt/naming)
  • Automatically mapped to subdirectories on disk
  • Recursive directory scanning in list()

Deduplication on write (P1)

  • Detects blocks with ≥50% tag overlap when writing
  • Returns duplicate warnings without blocking the write
  • Helps prevent redundant memory accumulation

Audit logging (P1)

  • Every CREATE, UPDATE, DELETE operation logged to .log file per scope
  • ISO-timestamped entries for debugging and compliance
  • altimate_memory_audit tool to inspect the log

Citation-backed memories

  • Optional citations array with file path, line number, and note
  • Rendered as "Sources" section in prompt injection
  • Helps verify and trace where memories came from

Session-end auto-extraction (opt-in)

  • altimate_memory_extract tool for batch-saving discovered facts
  • Controlled by ALTIMATE_MEMORY_AUTO_EXTRACT env var (off by default)
  • Accepts up to 10 facts per invocation

Global opt-out

  • Set ALTIMATE_DISABLE_MEMORY=true to completely disable all memory tools
  • No memory tools registered, no prompt injection, zero overhead

File format

Plain Markdown with YAML frontmatter — human-readable, editable, version-controllable:

---
id: warehouse-config
scope: project
created: 2026-03-14T10:00:00.000Z
updated: 2026-03-14T10:00:00.000Z
tags: ["snowflake", "warehouse"]
expires: 2027-01-01T00:00:00.000Z
citations: [{"file":"profiles.yml","line":5,"note":"Connection config"}]
---

## Warehouse Configuration

- **Provider**: Snowflake
- **Default warehouse**: ANALYTICS_WH
- **Naming convention**: stg_ for staging, int_ for intermediate, fct_/dim_ for marts

Safety and limits

Safeguard Detail
Block size limit 2,048 characters per block
Block count limit 50 blocks per scope
Citation limit 10 citations per block
Atomic writes Write to .tmp, then rename — prevents corruption on crash
Token budget System prompt injection capped at 8,000 chars (configurable)
Graceful degradation Missing/unreadable memory directory = agent works normally
Audit trail All operations logged with timestamps

Potential side effects

Context window impact

  • Memory injection is a one-time cost at session start (default 8,000 chars, fits 15-40 typical blocks)
  • Does NOT grow during the session — agent tool calls consume far more context
  • Expired blocks are automatically excluded from injection
  • If context pressure occurs: reduce block count, keep blocks concise, or lower the budget

Stale or incorrect memories

  • Blocks persist indefinitely unless TTL is set
  • If warehouse config changes, the agent continues using outdated info until the block is updated
  • How users detect it: agent makes assumptions that don't match current setup
  • How users fix it: inspect files directly (cat .opencode/memory/*.md), ask agent to delete/update, or edit the Markdown file
  • TTL mitigation: set expires on volatile information so it auto-expires

Wrong information saved by the agent

  • Agent may occasionally save incorrect inferences
  • How users detect it: every write produces visible output (Memory: Created "..." / Memory: Updated "..."), plus dedup warnings
  • How users fix it: altimate_memory_delete, direct file edit, or ask agent to rewrite
  • Audit trail: use altimate_memory_audit to see what was written and when

Security

  • Plaintext storage (not encrypted) — treat like config files
  • Do not save credentials, API keys, or PII in memory blocks
  • Per-user and per-project isolation — no cross-user/cross-project leakage
  • Project memory should be in .gitignore if it contains sensitive context

Files changed

packages/opencode/src/memory/
├── index.ts                      # Module exports
├── types.ts                      # MemoryBlock + Citation types, Zod schemas, constants
├── store.ts                      # File-based CRUD with atomic writes, TTL, dedup, audit
├── prompt.ts                     # System prompt injection with token budget
└── tools/
    ├── memory-read.ts            # altimate_memory_read
    ├── memory-write.ts           # altimate_memory_write
    ├── memory-delete.ts          # altimate_memory_delete
    ├── memory-audit.ts           # altimate_memory_audit
    └── memory-extract.ts         # altimate_memory_extract

packages/opencode/test/memory/
├── store.test.ts                 # 52 tests: CRUD, TTL, citations, hierarchical IDs, dedup, audit
├── prompt.test.ts                # 20 tests: formatting, injection, expired skipping, citations
├── tools.test.ts                 # 57 tests: all 5 tool schemas, integration, opt-out
└── types.test.ts                 # 46 tests: CitationSchema, MemoryBlockSchema, hierarchical IDs

packages/opencode/src/flag/flag.ts         # ALTIMATE_DISABLE_MEMORY, ALTIMATE_MEMORY_AUTO_EXTRACT
packages/opencode/src/tool/registry.ts     # Conditional memory tool registration
packages/opencode/src/altimate/index.ts    # Re-export memory module

Test plan

  • All 196 tests pass (bun test test/memory/)
  • Typecheck passes across all 13 packages (pre-push hook verified)
  • Manual: launch altimate-code, use altimate_memory_write to save warehouse config, start new session, verify altimate_memory_read recalls it
  • Manual: verify .opencode/memory/ files are human-readable Markdown
  • Manual: verify TTL expiration (create block with past expires, confirm hidden)
  • Manual: verify hierarchical namespace (write warehouse/snowflake, check subdirectory created)
  • Manual: verify dedup warning when writing block with overlapping tags
  • Manual: verify audit log (altimate_memory_audit)
  • Manual: verify ALTIMATE_DISABLE_MEMORY=true removes all memory tools
  • Manual: verify block limit enforcement (50 blocks per scope)
  • Manual: verify atomic write safety (kill process mid-write, no corruption)
  • Manual: verify graceful degradation (delete memory dir, agent still works)

🤖 Generated with Claude Code

…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>
@github-actions
Copy link

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

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>
@anandgupta42 anandgupta42 changed the title feat: Add Letta-style persistent memory blocks for cross-session context feat: Add Altimate Memory — persistent cross-session agent context for data engineering Mar 14, 2026
…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>
@anandgupta42 anandgupta42 changed the title feat: Add Altimate Memory — persistent cross-session agent context for data engineering feat: add Altimate Memory — persistent cross-session memory with TTL, namespaces, citations, and audit logging Mar 14, 2026
Comment on lines +22 to +24
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Comment on lines +187 to +193
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

@github-actions
Copy link

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>
Comment on lines +30 to +33
const block = await MemoryStore.read(scope, args.id)
if (block) {
return {
title: `Memory: ${block.id} (${block.scope})`,

This comment was marked as outdated.

@github-actions
Copy link

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>
@anandgupta42 anandgupta42 merged commit 94f4536 into main Mar 15, 2026
7 checks passed
anandgupta42 added a commit that referenced this pull request Mar 17, 2026
… 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>
@anandgupta42 anandgupta42 deleted the feat/memory-blocks branch March 17, 2026 00:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add Altimate Memory — persistent cross-session agent context for data engineering

1 participant