> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runflow.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory

> Intelligent conversation history management

The **Memory** system intelligently manages conversation history.

## Memory vs KV Store

Memory is built for **conversation context** — message history, summaries, who spoke last, session status. Its content is *managed*: old turns are trimmed by `maxTurns`/`maxTokens` and compacted by summarization, so what you appended yesterday may only survive as part of a summary.

That makes Memory the wrong place to park business data. If you need a value to come back **exactly as you stored it** — a cart, a feature flag, a counter, a customer preference — use the [KV Store](/core-concepts/kv-store) instead.

|           | Memory                                       | KV Store                                 |
| --------- | -------------------------------------------- | ---------------------------------------- |
| Stores    | Messages, summaries, session metadata        | Any JSON value                           |
| Scope     | Per conversation / entity                    | Tenant-wide (all agents)                 |
| Lifecycle | Trimmed and summarized automatically         | Persistent until deleted (or TTL)        |
| Read back | Formatted history, semantic search           | Exact value, glob pattern search         |
| Use for   | Dialogue context, follow-ups, session status | Carts, flags, counters, idempotency keys |

<Warning>
  Appending fake messages to conversation history to persist arbitrary data is unreliable — summarization can compact it away and every message inflates the context sent to the LLM. Store data in the [KV Store](/core-concepts/kv-store); keep Memory for the conversation. See [Abandoned Cart Recovery](/use-cases/abandoned-cart-recovery) for the two working together.
</Warning>

## Memory Integrated in Agent

```typescript theme={null}
const agent = new Agent({
  name: 'Memory Agent',
  instructions: 'You remember everything.',
  model: openai('gpt-4o'),
  memory: {
    maxTurns: 20,           // Limit turns
    maxTokens: 4000,        // Limit tokens
    summarizeAfter: 50,     // Summarize after N turns
    summarizePrompt: 'Create a concise summary with key facts and action items',
    summarizeModel: openai('gpt-4o-mini'), // Cheaper model for summaries
  },
});
```

## Standalone Memory Manager

```typescript theme={null}
import { Memory } from '@runflow-ai/sdk';

// Using static methods (most common - 99% of cases)
await Memory.append({
  role: 'user',
  content: 'Hello!',
  timestamp: new Date(),
});

await Memory.append({
  role: 'assistant',
  content: 'Hi! How can I help you?',
  timestamp: new Date(),
});

// Get formatted history
const history = await Memory.getFormatted();
console.log(history);

// Get recent messages
const recent = await Memory.getRecent(5); // Last 5 turns

// Search in memory
const results = await Memory.search('order');

// Check if memory exists
const exists = await Memory.exists();

// Get full memory data
const data = await Memory.get();

// Clear memory
await Memory.clear();
```

## Memory with User Identification

```typescript theme={null}
import { Memory } from '@runflow-ai/sdk';
import { identify } from '@runflow-ai/sdk/observability';

// Identify user
identify('+5511999999999');

// Memory automatically uses the context
await Memory.append({
  role: 'user',
  content: 'My order number is 12345',
  timestamp: new Date(),
});

// Memory is automatically bound to the phone number
```

## Custom Memory Key

```typescript theme={null}
// Create memory with custom key
const memory = new Memory({
  memoryKey: 'custom_key_123',
  maxTurns: 10,
});

// Now use instance methods
await memory.append({ role: 'user', content: 'Hello', timestamp: new Date() });
const history = await memory.getFormatted();
```

## Cross-Session Access

```typescript theme={null}
// Access memory from different sessions (admin, analytics, etc)
const dataUser1 = await Memory.get('phone:+5511999999999');
const dataUser2 = await Memory.get('email:user@example.com');

// Search across multiple sessions
const results = await Promise.all([
  Memory.search('bug', 'user:123'),
  Memory.search('bug', 'user:456'),
  Memory.search('bug', 'user:789'),
]);

// Get recent from specific session
const recent = await Memory.getRecent(5, 'session:abc123');

// Clear specific session
await Memory.clear('phone:+5511999999999');
```

## Listing Sessions

`Memory.list()` queries all sessions for the current agent with filtering. Each result includes the last message, so you can decide what to do without loading the full conversation.

```typescript theme={null}
import { Memory } from '@runflow-ai/sdk';

// List all sessions for this agent
const all = await Memory.list();

// List sessions inactive for 4+ hours
const inactive = await Memory.list({
  lastInteractionBefore: new Date(Date.now() - 4 * 60 * 60 * 1000),
});

// List only sessions with a specific status
const inProgress = await Memory.list({
  status: 'in_progress',
});

// Combine filters
const needsFollowUp = await Memory.list({
  lastInteractionBefore: new Date(Date.now() - 4 * 60 * 60 * 1000),
  limit: 50,
});
```

Each session in the result includes:

| Field          | Type      | Description                                                         |
| -------------- | --------- | ------------------------------------------------------------------- |
| `id`           | `string`  | Session ID                                                          |
| `entityType`   | `string?` | Entity type from `identify()` (e.g., `phone`, `email`)              |
| `entityValue`  | `string?` | Entity value (e.g., `+5511999999999`)                               |
| `status`       | `string?` | Session status (null = in progress, or `qualified`, `closed`, etc.) |
| `updatedAt`    | `string`  | Timestamp of last interaction                                       |
| `messageCount` | `number`  | Total messages in session                                           |
| `summary`      | `string?` | Auto-generated conversation summary                                 |
| `lastMessage`  | `object?` | Last message: `{ role, content, timestamp }`                        |

### Checking Who Spoke Last

Use `lastMessage.role` to know if the user or the agent was the last to speak — useful for deciding whether to follow up:

```typescript theme={null}
const inactive = await Memory.list({
  lastInteractionBefore: new Date(Date.now() - 4 * 60 * 60 * 1000),
});

for (const session of inactive) {
  // Skip sessions already closed/qualified
  if (session.status) continue;

  // Only follow up if the agent was the last to speak (user didn't respond)
  if (session.lastMessage?.role !== 'assistant') continue;

  await agent.process({
    message: 'Follow up with this lead.',
    entityType: session.entityType,
    entityValue: session.entityValue,
    channel: 'whatsapp',
  });
}
```

<Note>
  Don't use `identify()` in a loop — it sets a global singleton that would get overwritten on each iteration. Pass `entityType`/`entityValue` directly in the `agent.process()` input instead.
</Note>

## Session Status

Mark sessions with a status to track their lifecycle. Status is stored in session metadata — no migration needed.

```typescript theme={null}
import { Memory } from '@runflow-ai/sdk';

// Set status on the current session (requires identify() first)
await Memory.setStatus('qualified');
await Memory.setStatus('closed');
await Memory.setStatus('nurturing');
```

### Status via Tools (Recommended)

The best pattern is to let **tools set the status** — the LLM decides when to call them based on the conversation:

```typescript theme={null}
import { createTool } from '@runflow-ai/sdk';
import { Memory } from '@runflow-ai/sdk';

const qualifyLeadTool = createTool({
  id: 'qualify-lead',
  description: 'Score the lead based on qualification criteria. This marks the session status.',
  inputSchema: z.object({
    score: z.number().min(0).max(10),
  }),
  execute: async (params) => {
    const status = params.score >= 7 ? 'qualified' : 'nurturing';
    await Memory.setStatus(status);
    return { score: params.score, status };
  },
});

const closeConversationTool = createTool({
  id: 'close-conversation',
  description: 'Close this conversation. Use when lead is not interested or conversation is finished.',
  inputSchema: z.object({
    reason: z.enum(['not_interested', 'wrong_contact', 'completed', 'other']),
  }),
  execute: async (params) => {
    await Memory.setStatus('closed');
    return { closed: true, reason: params.reason };
  },
});
```

The developer never calls `Memory.setStatus()` directly — the tools do it. The agent's instructions guide the LLM to use the right tool at the right time.

### Status Lifecycle

```
New conversation            → status: null (in progress)
Lead qualified (score >= 7) → tool sets "qualified"
Lead scored low (score < 7) → tool sets "nurturing"
Lead not interested         → tool sets "closed"
CRON checks inactive leads  → Memory.list() skips sessions with status
```

<Tip>
  See the [SDR Agent with Follow-ups](/use-cases/sdr-follow-up) use case for a complete working example using `Memory.list()`, `Memory.setStatus()`, and scheduled callbacks.
</Tip>

## Custom Summarization

```typescript theme={null}
// Agent with custom summarization
const agent = new Agent({
  name: 'Smart Agent',
  model: openai('gpt-4o'),
  memory: {
    summarizeAfter: 30,
    summarizePrompt: `Summarize in 3 bullet points:
- Main issue discussed
- Solution provided
- Next steps`,
    summarizeModel: anthropic('claude-3-haiku'), // Fast & cheap
  },
});

// Manual summarization with custom prompt
const summary = await Memory.summarize({
  prompt: 'Extract only the key decisions from this conversation',
  model: openai('gpt-4o-mini'),
});
```

## Memory Configuration Options

| Option            | Type            | Description                        |
| ----------------- | --------------- | ---------------------------------- |
| `maxTurns`        | `number`        | Maximum conversation turns to keep |
| `maxTokens`       | `number`        | Maximum tokens to keep             |
| `summarizeAfter`  | `number`        | Trigger summary after N turns      |
| `summarizePrompt` | `string`        | Custom prompt for summarization    |
| `summarizeModel`  | `ModelProvider` | Custom model for summarization     |
| `memoryKey`       | `string`        | Custom memory key                  |

## Cross-agent memory administration

The `Memory` module operates on the **caller's own** agent context — its key is prefixed with the caller's agentId so each agent has its own namespace.

To curate **another** agent's memory (inject a system message, clear stale sessions, audit messages, summarize), use the [`MemoryAdmin`](/core-concepts/cross-agent#memory-admin) module from the cross-agent SDK. Same six operations, but you pass the target agent explicitly and the backend prefixes the key with the target's id instead of the caller's.

```typescript theme={null}
import { MemoryAdmin } from '@runflow-ai/sdk/memory-admin';

const admin = new MemoryAdmin();

// List slots updated in the last 7 days
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const { sessions } = await admin.list('customer-support', {
  dateFrom: since,
  dateField: 'updated_at',
  limit: 50,
});

// Inject a system message
await admin.append('customer-support', 'phone:+5511999999999', {
  role: 'system',
  content: 'IMPORTANTE: cliente prioritário, responder em <2min.',
});

// LLM summary of the slot
const { summary } = await admin.summarize(
  'customer-support',
  'phone:+5511999999999',
  { prompt: 'Resume em até 5 bullets, em português:' },
);
```

Tenant-isolated: cross-tenant references return 404. See [Cross-Agent SDK](/core-concepts/cross-agent) for the full reference and recipes (curator agent, follow-up agent).

## Next Steps

<CardGroup cols={2}>
  <Card title="Context Management" icon="user" href="/core-concepts/context-management">
    Learn about context management
  </Card>

  <Card title="Cross-Agent SDK" icon="diagram-project" href="/core-concepts/cross-agent">
    Operate on another agent's memory and executions
  </Card>

  <Card title="Tools" icon="wrench" href="/core-concepts/tools">
    Create custom tools
  </Card>

  <Card title="Abandoned Cart Recovery" icon="cart-shopping" href="/use-cases/abandoned-cart-recovery">
    Complete project: conversation in Memory, cart state in KV
  </Card>
</CardGroup>
