AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Memory Persistence

skill-arturseo-geo-claude-code-skills-memory-persistence · by arturseo-geo

>

No reviews yet
0 installs
35 views
0.0% view→install

Install

$ agentstack add skill-arturseo-geo-claude-code-skills-memory-persistence

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-arturseo-geo-claude-code-skills-memory-persistence)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
6mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Memory Persistence? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Memory Persistence Skill

Memory Architecture Overview

Claude Code has no built-in cross-session memory — every new session starts blank. There are six patterns to solve this, ranging from simple to powerful:

| Pattern | Best for | Complexity | |---|---|---| | CLAUDE.md injection | Small, stable facts | Low | | Session summary files | Resuming complex work | Low | | Append-only JSONL | Evolving agent knowledge | Medium | | Graph-based memory | Relationship-rich knowledge | Medium | | Hooks (stop/pre/post) | Auto-saving and auto-loading | Medium | | MCP memory servers | Production multi-user agents | High |


Pattern 1: CLAUDE.md Injection (Always-On Facts)

CLAUDE.md is Claude Code's built-in memory layer. It loads automatically at session start, making it the simplest persistence mechanism. There are three levels:

| File | Scope | Loads when | |---|---|---| | ~/.claude/CLAUDE.md | All projects (global) | Every session | | CLAUDE.md (project root) | This project only | When in project dir | | .claude/CLAUDE.md | This project (hidden) | When in project dir |

All three merge at session start. Use the right level for each fact.

Example CLAUDE.md structure:

## Project Context
- Stack: Next.js 14, Supabase, Tailwind, Vercel
- Auth: Clerk (not NextAuth)
- Primary branch: main (never commit directly)
- Test command: pnpm test:unit
- Deployment: auto on push to main via Vercel

## Key Decisions
- We use Zod for all validation, not Yup
- All API routes live in /app/api, never /pages/api
- Error handling pattern: see /lib/errors.ts

CLAUDE.md Size Limits

Keep it under 150 lines — it loads every session, consuming tokens permanently. Move verbose details to reference files and @-import them only when needed.

| Content type | Put in CLAUDE.md? | Alternative | |---|---|---| | Stack, tools, test commands | Yes | — | | Architectural decisions ( list[dict]: memories = [] with open(path) as f: next(f) # skip schema line for line in f: m = json.loads(line) if types and m["type"] not in types: continue if m.get("importance", 1.0) float: """Score = importance recencydecay""" now = now or datetime.now(timezone.utc) ts = datetime.fromisoformat(memory["ts"].replace("Z", "+00:00")) agedays = (now - ts).totalseconds() / 86400 recency = math.exp(-0.01 agedays) # half-life ~69 days importance = memory.get("importance", 0.7) return importance * recency


**Retrieval strategy**: load all memories, score each, return top-N. This keeps
context lean while surfacing the most relevant knowledge.

---

## Pattern 4: Graph-Based Memory (Relationship-Rich Knowledge)

For complex projects where entities relate to each other — use a graph structure
stored as a JSON file. Faster context loading, fewer tokens, safer for refactoring.

```json
{
  "nodes": {
    "UserModel": {"type": "entity", "file": "lib/models/user.ts", "deps": ["AuthService", "DB"]},
    "AuthService": {"type": "service", "file": "lib/auth.ts", "deps": ["Clerk", "UserModel"]},
    "DB": {"type": "external", "url": "Supabase"}
  },
  "edges": [
    {"from": "UserModel", "to": "AuthService", "rel": "used-by"},
    {"from": "AuthService", "to": "Clerk", "rel": "delegates-auth-to"}
  ]
}

When to use: projects with 20+ modules, microservices, or complex dependency chains. Claude can query: "what depends on AuthService?" without loading all files.

Graph Queries

Common queries an agent can run against the graph:

| Query | How | |---|---| | What depends on X? | Filter edges where from == X | | What does X depend on? | Read nodes[X].deps | | Impact of changing X? | BFS from X along used-by edges | | Which services touch the DB? | Filter nodes where "DB" in deps |


Pattern 5: Hooks for Auto-Save and Auto-Load

Hooks let you automate memory operations so the user never has to remember to save.

Stop Hook — Auto-Save Session Learnings

A Stop hook runs at session end. It captures what Claude discovered and appends to memory automatically. Use Stop not UserPromptSubmit — runs once per session, not on every message, so no latency impact.

.claude/hooks/stop-memory.sh:

#!/bin/bash
# Runs at end of every session
# Asks Claude to extract learnings and append to JSONL

claude -p "Review this session. Extract any:
- Debugging patterns discovered
- Project-specific facts learned
- User preferences observed
- Decisions made

Format each as a JSON line:
{\"ts\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"type\": \"[type]\", \"key\": \"[short_key]\", \"value\": \"[what to remember]\", \"context\": \"[brief context]\", \"importance\": [0.0-1.0]}

Append to .claude/memory.jsonl — do not overwrite."

Register in .claude/settings.json:

{
  "hooks": {
    "Stop": [{"command": "bash .claude/hooks/stop-memory.sh"}]
  }
}

PreToolUse Hook — Auto-Load Relevant Memory

Load memory before specific tool calls to give Claude context exactly when needed:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash .claude/hooks/load-memory.sh"
      }
    ]
  }
}

.claude/hooks/load-memory.sh:

#!/bin/bash
# Before editing files, surface relevant memories
# Read last 20 memories and output as context
tail -20 .claude/memory.jsonl 2>/dev/null || true

PostToolUse Hook — Capture Outcomes

After a tool runs, record what happened for future sessions:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "command": "bash .claude/hooks/record-outcome.sh"
      }
    ]
  }
}

Pattern 6: MCP Memory Servers

For production memory, connect a memory MCP server instead of flat files:

| Server | Storage | Best for | |---|---|---| | @modelcontextprotocol/memory | In-process key-value | Simple persistence | | Chroma MCP | Vector DB | Semantic search over memories | | SQLite MCP | Relational | Structured queries over memory |

Important: if a memory MCP is configured but no skill/hook actually writes to it, disable it — it consumes context window tokens with no benefit.


Memory Retrieval Strategies

When memory grows large, you need strategies to load only what matters:

| Strategy | How it works | Best for | |---|---|---| | Recency | Load last N entries | Fast-moving projects | | Type filter | Load only decision + pattern | Focused recall | | Importance threshold | Load entries with importance >= 0.7 | Mature memory stores | | Decay scoring | Score = importance * recency_decay | Balanced retrieval | | Semantic search | Vector similarity via MCP | Large memory stores (1000+) | | Key prefix | Filter by key prefix (auth_*) | Domain-specific queries |

Combining Strategies

For best results, combine multiple strategies:

def retrieve_relevant(path: str, query_types: list[str], top_n: int = 20) -> list[dict]:
    all_memories = load_memories(path, types=query_types)
    scored = [(m, score_memory(m)) for m in all_memories]
    scored.sort(key=lambda x: x[1], reverse=True)
    return [m for m, s in scored[:top_n]]

Quick Reference — Which Pattern to Use?

Need to remember forever?          -> CLAUDE.md (if  Session summary file
Agent that learns over time?       -> Append-only JSONL + Stop hook
Complex codebase relationships?    -> Graph-based JSON
Auto-save without user action?     -> Stop hook + JSONL
Production multi-user agent?       -> Memory MCP server
Surviving context compaction?      -> Write to disk early and often

Pattern 7: Groq-Powered Memory Processing (Zero Claude Token Cost)

Use Groq's free tier to handle memory operations that would otherwise consume Claude tokens: scoring, compaction, summarisation, and relevance ranking.

Why offload memory ops to Groq?

Memory operations are repetitive, low-reasoning tasks — perfect for Llama 3.1 8B. Scoring 500 memories on Claude Sonnet costs ~$2. On Groq: $0.

Groq setup

pip install groq
export GROQ_API_KEY="gsk_..."  # Free at console.groq.com

Memory scoring with Groq

Instead of loading all memories into Claude's context, score them on Groq first and feed only the top-N:

import os, json, time
from groq import Groq

groq_client = Groq(api_key=os.environ["GROQ_API_KEY"])

def score_memories_groq(memories: list[dict], context: str,
                        top_n: int = 20) -> list[dict]:
    """Score memory relevance using Groq free tier, return top-N."""
    scored = []
    batch_size = 15  # Send 15 per request to reduce RPM usage

    for i in range(0, len(memories), batch_size):
        batch = memories[i:i+batch_size]
        batch_text = "\n".join([
            f"{j+1}. [{m['type']}] {m['key']}: {m['value']}"
            for j, m in enumerate(batch)
        ])

        resp = groq_client.chat.completions.create(
            model="llama-3.1-8b-instant",
            messages=[{
                "role": "user",
                "content": f"""Rate each memory's relevance to this context (0.0-1.0).
Return ONLY a JSON array of numbers.

Context: {context}

Memories:
{batch_text}

Scores:"""
            }],
            temperature=0,
            max_tokens=200
        )

        try:
            scores = json.loads(resp.choices[0].message.content)
            if isinstance(scores, dict):
                scores = list(scores.values())[0]
            for m, s in zip(batch, scores):
                scored.append((m, float(s)))
        except:
            for m in batch:
                scored.append((m, 0.5))

        time.sleep(2)  # Respect 30 RPM limit

    scored.sort(key=lambda x: x[1], reverse=True)
    return [m for m, s in scored[:top_n]]

# Usage: score 500 memories for free, feed top 20 to Claude
all_memories = load_memories(".claude/memory.jsonl")
relevant = score_memories_groq(all_memories, "debugging auth webhook")
# Now pass only 20 memories to Claude instead of 500

Memory summarisation with Groq

Compress old memories into summaries to reduce JSONL file size:

def summarise_old_memories_groq(memories: list[dict],
                                 group_size: int = 20) -> list[str]:
    """Summarise groups of old memories into compact paragraphs."""
    summaries = []
    for i in range(0, len(memories), group_size):
        group = memories[i:i+group_size]
        group_text = "\n".join([
            f"- [{m['type']}] {m['key']}: {m['value']}"
            for m in group
        ])

        resp = groq_client.chat.completions.create(
            model="llama-3.1-8b-instant",
            messages=[{
                "role": "user",
                "content": f"Summarise these memories into 3-5 bullet points, preserving the most important facts:\n\n{group_text}"
            }],
            temperature=0.1,
            max_tokens=300
        )
        summaries.append(resp.choices[0].message.content.strip())
        time.sleep(2)
    return summaries

Memory deduplication with Groq

Find and merge duplicate memories without burning Claude tokens:

def find_duplicates_groq(memories: list[dict]) -> list[list[int]]:
    """Identify duplicate/near-duplicate memories using Groq."""
    batch_text = "\n".join([
        f"{i+1}. [{m['type']}] {m['key']}: {m['value']}"
        for i, m in enumerate(memories)
    ])

    resp = groq_client.chat.completions.create(
        model="llama-3.1-8b-instant",
        messages=[{
            "role": "user",
            "content": f"""Find groups of duplicate or near-duplicate memories.
Return JSON array of arrays, e.g. [[1,5], [3,7,12]].
If no duplicates, return [].

Memories:
{batch_text[:4000]}

Duplicate groups:"""
        }],
        temperature=0,
        max_tokens=500
    )

    try:
        return json.loads(resp.choices[0].message.content)
    except:
        return []

Haiku for real-time memory retrieval

For in-session memory operations that need to stay inside Claude Code, use Haiku subagents instead of the main model:

{
  "env": {
    "CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
  }
}

Then delegate memory tasks to subagents:

Use a subagent to: read .claude/memory.jsonl, find all memories
related to "authentication", and return the top 5 most relevant.

Haiku costs 4% of Opus — memory retrieval via subagent is nearly free.

Combined workflow: Groq + Haiku + Claude

| Memory operation | Route to | Cost | |---|---|---| | Score 500 memories for relevance | Groq | $0.00 | | Summarise old memories (bulk) | Groq | $0.00 | | Find and merge duplicates | Groq | $0.00 | | Load top-20 into session context | Haiku subagent | ~$0.001 | | Use memories for reasoning | Sonnet/Opus (main) | Normal | | Write new memories (Stop hook) | Haiku subagent | ~$0.001 |

Result: memory operations that cost $2-5/day on Claude now cost <$0.01/day.

See references/groq-memory.md for complete integration scripts.


Anti-Patterns to Avoid

| Anti-pattern | Why it fails | Fix | |---|---|---| | CLAUDE.md over 300 lines | Wastes tokens every session | Router pattern + reference files | | Overwriting memory files | Loses history, breaks diffs | Append-only JSONL | | Memory in chat only | Lost on compaction or new session | Write to disk | | No importance scoring | All memories treated equal | Add importance field | | Hook on every message | Latency on each prompt | Use Stop hook (once per session) | | MCP server with no writes | Token cost, zero benefit | Disable unused MCP tools |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.