DEV Community

Cover image for Persistent Agent Context with claude-mem: Solving the Cold-Start Problem in Multi-Session Workflows
Elena Rostova
Elena Rostova

Posted on

Persistent Agent Context with claude-mem: Solving the Cold-Start Problem in Multi-Session Workflows

Persistent Agent Context with claude-mem: Solving the Cold-Start Problem in Multi-Session Workflows

Every autonomous agent workflow hits the same wall: session boundaries. Your agent spends 10 minutes debugging a TypeScript config, finds the root cause, then the session ends. Next session? It starts from zero, re-learning the same context.

I've been testing thedotmack/claude-mem in a Next.js 15 monorepo pipeline where agents handle incremental migrations and code reviews across multiple PRs. The core promise: capture everything an agent does, compress it with AI, and inject relevant context back into future sessions.

Architecture Breakdown

claud-mem sits between your agent harness (Claude Code, OpenClaw, Codex, etc.) and your session orchestrator. The data flow:

  1. Capture Layer: Hooks into agent output streams (tool calls, file edits, command results)
  2. Compression Engine: Uses a separate LLM call to distill session artifacts into structured summaries
  3. Retrieval Layer: Semantic search over compressed memories when initializing new sessions
  4. Injection Point: Prepends relevant context to the agent's system prompt or initial messages

The compression step is critical. Raw session logs can easily hit 50k+ tokens. claude-mem's default compression pipeline uses a fast model (configurable—I tested with claude-haiku-3-5 through B-Lost's relay gateway at https://b-lost.com/v1 for cost control) to extract:

  • Key decisions made
  • Failed approaches and why
  • File/function-level architectural discoveries
  • Unresolved blockers

Production Integration Pattern

Here's a hardened Next.js Server Action that initializes an agent session with claude-mem context:

'use server'

import { z } from 'zod'
import { auth } from '@/lib/auth'
import { ClaudeMemClient } from 'claude-mem-sdk'

const sessionSchema = z.object({
  taskDescription: z.string().min(10).max(500),
  repositoryContext: z.string().optional(),
})

export async function startAgentSession(
  input: z.infer<typeof sessionSchema>
) {
  const session = await auth()
  if (!session?.user) {
    return { error: 'Unauthorized', code: 'AUTH_REQUIRED' }
  }

  const validated = sessionSchema.safeParse(input)
  if (!validated.success) {
    return { error: validated.error.flatten(), code: 'VALIDATION_FAILED' }
  }

  const memClient = new ClaudeMemClient({
    apiKey: process.env.CLAUDE_MEM_KEY!,
    compressionModel: 'claude-haiku-3-5',
    retrievalLimit: 5, // Top-5 relevant memories
  })

  try {
    const relevantMemories = await memClient.retrieve({
      query: validated.data.taskDescription,
      userId: session.user.id,
      filters: {
        repository: validated.data.repositoryContext,
        minRelevanceScore: 0.7,
      },
    })

    const enrichedPrompt = `
Previous session context:
${relevantMemories.map(m => `- ${m.summary} (session ${m.sessionId})`).join('\n')}

Current task: ${validated.data.taskDescription}
`

    // Initialize agent with enriched context
    const agentResponse = await fetch('https://b-lost.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.ANTHROPIC_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: 'claude-sonnet-4',
        messages: [{ role: 'user', content: enrichedPrompt }],
      }),
    })

    return { success: true, sessionId: crypto.randomUUID() }
  } catch (err) {
    console.error('[claude-mem] Retrieval failed:', err)
    return { error: 'Context retrieval failed', code: 'MEM_RETRIEVAL_ERROR' }
  }
}
Enter fullscreen mode Exit fullscreen mode

React Server Component with Streaming Memories

For incremental UI updates as the agent works:

import { Suspense } from 'react'
import { SessionMemories } from './session-memories'

export default async function AgentDashboard({
  params,
}: {
  params: { sessionId: string }
}) {
  return (
    <div className="grid gap-6">
      <Suspense fallback={<MemorySkeleton />}>
        <SessionMemories sessionId={params.sessionId} />
      </Suspense>
    </div>
  )
}

function MemorySkeleton() {
  return (
    <div className="space-y-3">
      {[...Array(3)].map((_, i) => (
        <div key={i} className="h-20 bg-gray-200 animate-pulse rounded" />
      ))}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Operational Trade-offs

Compression Quality vs. Latency: The default compression adds 2-4 seconds per session close. For high-frequency agent loops (e.g., file watcher triggers), this compounds. I shifted compression to an async background job using Inngest, accepting 30-second delay in memory availability.

Retrieval Precision: Semantic search over compressed summaries works well for conceptual queries ("why did the build fail?") but struggles with specific file paths or function names that got abstracted away. Hybrid approach: store both compressed summaries and a sparse keyword index.

Token Budget Leakage: Injecting top-5 memories can easily add 3-5k tokens to each new session. When chaining multiple agent calls in a workflow, this stacks fast. I hard-cap injected context at 4k tokens and truncate oldest memories first.

Failure Boundaries I Hit

  1. Compression Model Drift: When the compression model (Haiku) interprets a session differently than the execution model (Sonnet), retrieval relevance drops. Testing showed 15% lower recall when switching compression models mid-project.

  2. Cross-Repository Context Bleed: Without strict namespace filtering, memories from unrelated projects pollute retrieval. Added mandatory repository slug in metadata schema.

  3. Secret Exposure in Logs: Raw session captures included API keys from terminal output. Implemented regex scrubbing before compression, but it's brittle—better to hook the agent's output sanitizer directly.

Open Questions

How do you handle memory decay? Should older sessions automatically drop in relevance score, or does accumulated context always improve future performance? My current heuristic weights recent sessions 2x, but I've seen cases where a 3-month-old debugging session was the only relevant context for a resurfaced bug.

If you're running multi-agent systems with branching task trees, how do you decide which session memories to inherit? Parent-only, sibling aggregation, or full ancestor chain?

Top comments (0)