Using Brain with Claude

By Arc Labs Research7 min read

The Problem

Claude has a large context window (200K tokens), but every token costs. Long conversations bloat your prompts: including the full history of 100 turns at 500 tokens each consumes 50,000 tokens before Claude sees the current question. At $3 per 1M input tokens, that's inefficient.

Solution: Selective Memory with Brain

Brain stores conversation history, decisions, and learned patterns outside Claude's context window. Before calling Claude, retrieve only the relevant memories. Your prompt shrinks from 50K tokens to 5K, while Claude remains fully informed about past context.

Install

# Python Brain SDK
pip install brain-sdk

# Claude SDK
pip install anthropic

# (Optional) Async support
pip install httpx aiohttp

Integration Steps

Step 1: Initialize Brain for Your Claude Application

Create a Brain instance to store conversation context, decisions, and learned patterns for this application.

from brain_sdk import Brain

# Memory for this Claude application
app_memory = Brain(
    name="claude-app",
    metadata={
        "application": "customer-support-agent",
        "model": "claude-3-5-sonnet-20241022"
    }
)

Step 2: Store Conversation and Decisions

After each Claude API call, store the turn and any decisions made. This builds your memory of the conversation.

from anthropic import Anthropic

client = Anthropic()

def claude_turn_with_storage(
    app_memory,
    conversation_history,
    user_message,
    user_id,
    system_prompt=None
):
    """Call Claude and store the turn in memory."""
    
    # 1. Call Claude with full conversation (first approach)
    messages = [
        {"role": "user" if i % 2 == 0 else "assistant", "content": msg}
        for i, msg in enumerate(conversation_history + [user_message])
    ]
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system=system_prompt or "You are a helpful assistant.",
        messages=messages
    )
    
    assistant_message = response.content[0].text
    
    # 2. Extract key decision or insight
    decision_markers = ["decided", "concluded", "recommend", "should", "next step"]
    has_decision = any(word in assistant_message.lower() for word in decision_markers)
    
    # 3. Store the turn
    app_memory.store(
        text=f"User: {user_message}\n\nAssistant: {assistant_message}",
        memory_type="conversation" if not has_decision else "decision",
        metadata={
            "user_id": user_id,
            "turn": len(conversation_history),
            "has_decision": has_decision,
            "model": "claude-3-5-sonnet"
        }
    )
    
    return assistant_message

# Example usage
conversation = []
user_id = "user_1001"

# Turn 1
response_1 = claude_turn_with_storage(
    app_memory,
    conversation,
    "I'm building an AI assistant. Where should I start?",
    user_id,
    system_prompt="You are an AI architect advising startups."
)
conversation.append(response_1)

# Turn 2
response_2 = claude_turn_with_storage(
    app_memory,
    conversation,
    "What about memory? Should I use Brain?",
    user_id,
    system_prompt="You are an AI architect advising startups."
)
conversation.append(response_2)

Step 3: Retrieve Selective Context Before Claude Calls

Before calling Claude, query Brain for relevant memories instead of including full conversation history. Inject only the essential context.

def retrieve_context(app_memory, current_query, user_id, top_k=5):
    """Retrieve relevant memories to include in Claude's context."""
    memories = app_memory.retrieve(
        query=current_query,
        filters={"user_id": user_id},
        top_k=top_k
    )
    
    return "\n".join([m.text for m in memories])

def claude_turn_selective(
    app_memory,
    current_message,
    user_id,
    system_prompt=None
):
    """Call Claude with selective memory instead of full history."""
    
    # 1. Retrieve only relevant past context
    context = retrieve_context(
        app_memory,
        query=current_message,
        user_id=user_id,
        top_k=5
    )
    
    # 2. Build prompt with selective context, not full history
    context_prompt = ""
    if context:
        context_prompt = f"Relevant prior context:\n{context}\n\n"
    
    # 3. Call Claude
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system=(system_prompt or "You are a helpful assistant.") + "\n" + context_prompt,
        messages=[{"role": "user", "content": current_message}]
    )
    
    assistant_message = response.content[0].text
    
    # 4. Store this turn
    app_memory.store(
        text=f"User: {current_message}\n\nAssistant: {assistant_message}",
        memory_type="conversation",
        metadata={"user_id": user_id}
    )
    
    return assistant_message

# After storing context from earlier turns, now use selective retrieval
response_3 = claude_turn_selective(
    app_memory,
    "Should I build my own memory system or use Brain?",
    user_id,
    system_prompt="You are an AI architect advising startups."
)
# Claude receives only the most relevant prior turns (via Brain),
# not the entire conversation history.

Step 4: Implement Async for Production

For production applications, use async to avoid blocking on Brain queries.

import asyncio
from anthropic import AsyncAnthropic

async_client = AsyncAnthropic()

async def claude_turn_async(
    app_memory,
    current_message,
    user_id,
    system_prompt=None
):
    """Async Claude call with selective memory."""
    
    # 1. Retrieve context (async)
    memories = await app_memory.retrieve_async(
        query=current_message,
        filters={"user_id": user_id},
        top_k=5
    )
    
    context = "\n".join([m.text for m in memories])
    
    # 2. Call Claude (async)
    response = await async_client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system=(system_prompt or "You are helpful.") + f"\nContext:\n{context}",
        messages=[{"role": "user", "content": current_message}]
    )
    
    return response.content[0].text

Step 5: Monitor Token Savings

Track the difference between full context and selective context. This shows your cost reduction.

def estimate_tokens(text):
    """Rough estimate: ~1 token per 4 characters."""
    return len(text) / 4

# With full conversation history (naive approach)
full_history_tokens = estimate_tokens(
    "\n".join([f"Turn {i}: {msg}" for i, msg in enumerate(conversation)])
)
print(f"Full history tokens: {full_history_tokens:.0f}")

# With selective memory (Brain approach)
selective_context = retrieve_context(
    app_memory,
    query="Should I use Brain?",
    user_id=user_id,
    top_k=5
)
selective_tokens = estimate_tokens(selective_context)
print(f"Selective context tokens: {selective_tokens:.0f}")

# Savings
savings = (full_history_tokens - selective_tokens) / full_history_tokens
print(f"Token reduction: {savings:.1%}")

Code Comparison

Without Brain: Full Conversation in Every Prompt

from anthropic import Anthropic

client = Anthropic()

# Store entire conversation in memory
conversation_history = []

def chat_no_memory(user_message, system_prompt):
    """Claude sees entire conversation history."""
    
    # Add user message to history
    conversation_history.append({
        "role": "user",
        "content": user_message
    })
    
    # Send entire history to Claude
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system=system_prompt,
        messages=conversation_history  # Full history: expensive
    )
    
    assistant_message = response.content[0].text
    
    # Add to history
    conversation_history.append({
        "role": "assistant",
        "content": assistant_message
    })
    
    return assistant_message

# Conversation grows...
for i in range(20):
    response = chat_no_memory(
        f"Follow-up question {i+1}...",
        "You are a helpful assistant."
    )

# By turn 20, conversation_history has 40 entries
# At 500 tokens per turn: 20,000 tokens sent on EVERY API call
# Cost: 20,000 tokens × $3 per 1M = $0.06 per turn
# 20 turns = $1.20 in input tokens alone

With Brain: Selective Memory Only

from brain_sdk import Brain
from anthropic import Anthropic

client = Anthropic()

# 1. Initialize Brain
app_memory = Brain(
    name="claude-app",
    metadata={"application": "chat"}
)

def chat_with_brain(user_message, system_prompt, user_id):
    """Claude sees only relevant prior context."""
    
    # 2. Retrieve relevant memories (not full history)
    memories = app_memory.retrieve(
        query=user_message,
        filters={"user_id": user_id},
        top_k=5  # Only top 5 relevant turns
    )
    
    # 3. Build context from memories
    context = ""
    if memories:
        context = "Relevant prior context:\n" + "\n".join([m.text for m in memories]) + "\n\n"
    
    # 4. Call Claude with selective context
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system=system_prompt + "\n" + context,
        messages=[{"role": "user", "content": user_message}]
    )
    
    assistant_message = response.content[0].text
    
    # 5. Store this turn for future retrieval
    app_memory.store(
        text=f"User: {user_message}\n\nAssistant: {assistant_message}",
        memory_type="conversation",
        metadata={"user_id": user_id}
    )
    
    return assistant_message

# Conversation grows...
user_id = "user_123"
for i in range(20):
    response = chat_with_brain(
        f"Follow-up question {i+1}...",
        "You are a helpful assistant.",
        user_id
    )

# By turn 20, Brain stores 20 memories
# But each API call only retrieves top 5 relevant (2,500 tokens)
# Cost: 2,500 tokens × $3 per 1M = $0.0075 per turn
# 20 turns = $0.15 in input tokens (87.5% savings vs. naive approach)

Implementation Checklist

  • Install Brain SDK (pip install brain-sdk) and Claude SDK (pip install anthropic)
  • Create a Brain instance for your Claude application
  • After each Claude API call, store the turn and any decisions using app_memory.store()
  • Before Claude calls, retrieve relevant memories with app_memory.retrieve() instead of including full history
  • Include user_id in metadata and filters to isolate per-user conversations
  • Estimate token savings: compare full history vs. selective memory size
  • Test a 10-turn conversation and verify memory retrieval surfaces relevant prior context
  • (Production) Use async calls with retrieve_async() and AsyncAnthropic()

Next Steps

Metric

50% reduction in context window usage. By storing full conversation history outside Claude's context window and retrieving only relevant memories, you cut prompt tokens in half. A 20-turn conversation drops from 20,000 tokens to 2,500 tokens per call, saving 87.5% in input costs while keeping Claude fully context-aware.

Related reading

Updates from the lab.

Engineering notes, research drops, occasional product updates. Roughly monthly.