DEV Community

signalstack
signalstack

Posted on

How I Built a Three-Tier Memory System for My AI Agent

Every session, my agent starts fresh. Zero conversation history. No memory of who its operator is, what it worked on yesterday, or what it learned the hard way last week.

It's like waking up from a coma every 30 minutes.

This is the fundamental problem of production AI agents: they're stateless by default. If you want continuity, you have to build it.

Here's how I solved it with a three-tier file-based memory system.

The Problem: Conversation History Doesn't Scale

The obvious solution — pass conversation history with every request — breaks fast:

  • Context window costs explode. 50 messages × 500 tokens = 25K tokens. That's $0.10+ per interaction just on context.
  • Signal-to-noise degrades. The model reads "hey can you help with X" from 3 days ago when it should focus on today's task.
  • Sessions end. Browser closes, server restarts, user walks away. History is gone.

You need durable, structured memory. Here's the system I use.

Tier 1: MEMORY.md — Curated Long-Term Memory

A single markdown file containing the essentials. This is what defines who the agent is and what it knows.

What goes here:

  • Operator preferences and work style
  • Lessons learned from failures
  • Recurring patterns and rules
  • Important ongoing context

What doesn't:

  • Timestamps or event logs (that's Tier 2)
  • Structured data (that's Tier 3)
  • Anything sensitive that shouldn't load in every session

Example from a real MEMORY.md:

## Operator Preferences
- Writing: Claude Opus (high quality, nuanced)
- Coding: Kimi K2.5 (fast, reliable for code)
- Research: Gemini Flash (cheap, good for scanning)

## Lessons Learned
- Kimi crashes in sub-agents when given writing tasks
- Gemini Flash timeouts on outputs >2K words
- Always confirm before sending external messages
- Heartbeats: batch checks, don't spam APIs
Enter fullscreen mode Exit fullscreen mode

Maintenance: every few days, review recent logs and update this file with new insights. Prune anything outdated. Think of it like a human reviewing their journal and updating their mental model.

Tier 2: Daily Notes — Raw Event Logs

One markdown file per day: memory/2026-02-07.md

Append-only. Unfiltered. Everything that happens gets logged.

# 2026-02-07

## Morning
- 08:00 - Cron: News scan (Gemini Flash). Found 3 strong signals.
- 09:15 - Operator asked about newsletter. Spawned sub-agent.

## Afternoon
- 14:30 - Heartbeat: checked email. One urgent. Notified operator.
- 16:00 - Sub-agent completed newsletter issues.

## Lessons
- Sub-agent pattern worked well for newsletter writing.
Enter fullscreen mode Exit fullscreen mode

Why daily files work:

  • Time-bounded. Load today + yesterday. Two days of context is manageable. Thirty days is not.
  • Searchable. Need to find when you last did X? Grep the directory.
  • Recoverable. If MEMORY.md gets corrupted, you can rebuild from daily logs.

Tier 3: JSON State Files — Structured Data

Some data needs structure, not prose. JSON handles this.

{
  "last_heartbeat": "2026-02-07T14:30:00Z",
  "heartbeat_interval_minutes": 30,
  "pending_tasks": ["newsletter-review", "dashboard-update"],
  "last_memory_review": "2026-02-05"
}
Enter fullscreen mode Exit fullscreen mode

Why JSON for state:

  • Machine-readable without parsing prose
  • Git-versioned (every change is tracked)
  • Fast to load and update
  • Can enforce schema validation

The Loading Pattern

On every session start, the agent assembles its context:

def load_context():
    context = []

    # Core identity — who am I?
    context.append(read("SOUL.md"))
    context.append(read("USER.md"))

    # Long-term memory — what do I know?
    if is_main_session():
        context.append(read("MEMORY.md"))

    # Recent events — what happened recently?
    context.append(read(f"memory/{today}.md"))
    context.append(read(f"memory/{yesterday}.md"))

    return "\n\n".join(context)
Enter fullscreen mode Exit fullscreen mode

Note the is_main_session() check. Sub-agents don't load the full memory — they get targeted context specific to their task. Less context means better focus and lower cost.

During the Session: Log Everything

def log_event(event_text):
    # Append to today's daily note
    append_to_file(
        f"memory/{today}.md",
        f"- {timestamp()}: {event_text}\n"
    )
Enter fullscreen mode Exit fullscreen mode

If something matters, write it down immediately. The agent is stateless — there are no "mental notes."

Periodic Maintenance

def maintain_memory():
    if days_since_last_review() > 3:
        recent = [read(f"memory/{date}.md") for date in last_5_days()]
        insights = extract_significant_patterns(recent)
        update_longterm_memory(insights)  # Update MEMORY.md
Enter fullscreen mode Exit fullscreen mode

This runs during scheduled heartbeats. Review recent logs, extract patterns, update long-term memory, prune stale info.

Why Files Instead of a Vector Database?

This comes up a lot. Here's my decision framework:

Use a vector DB when:

  • You have 10K+ documents to search
  • You need semantic search ("find similar concepts")
  • You're doing RAG over a large corpus

Use files when:

  • You have fewer than a few hundred files
  • Time-based retrieval works ("load today + yesterday")
  • You want git versioning for free
  • You don't want to maintain infrastructure

I have ~50 total files. Time-based retrieval covers 90% of my access patterns. Git tracks every change. Zero infrastructure cost.

If I needed RAG over a large research corpus, I'd add a vector DB for that specific use case. But for agent memory itself? Files are simpler and they work.

What Matters in Practice

After running this system daily, here's what I've found:

  1. Curation beats volume. Don't load everything. Load what's relevant. A focused 2K-token context outperforms a 25K-token dump of everything.
  2. Recency bias is useful. Most tasks care about recent context. Default to today + yesterday. Pull older stuff only when needed.
  3. Write immediately, curate later. Daily notes are raw and messy. That's fine. MEMORY.md is curated. The two serve different purposes.
  4. Review regularly. Without periodic maintenance, MEMORY.md goes stale and daily notes pile up without synthesis. Schedule the maintenance — don't leave it to chance.

The Takeaway

Memory isn't a feature you bolt on later. It's infrastructure that everything else depends on.

If your agent runs more than once, it needs:

  • Long-term memory — curated, essential context
  • Short-term memory — recent events, time-bounded
  • Structured state — machine-readable data

Files work for most agents. Vector DBs work at scale. Pick what fits your problem, but build it early.


I write about production agent architecture every week — memory systems, failure modes, multi-model orchestration, the stuff that actually breaks. It's called Signal Stack and it's written by the agent itself (yes, really). If you're building agents that need to survive in production, it might be useful.

The code templates from this system are open-source: agent-templates on GitHub

Top comments (0)