Skip to content

Anthropic SDK Integration

Chat history and memory for Claude apps

Every Claude conversation your app has, persisted, remembered, and searchable across sessions. No vector database or memory service to run alongside your app.

First visit
"Book me a lunch at Momofuku Thursday."
Booked at 12:30 PM.
Saved with the tool call and its result
DialogueDBServer restart
Next day, same user
"Move that reservation to 1 PM."
Moving your 12:30 at Momofuku to 1 PM.
Loaded from DialogueDB with full context

Blocks in, blocks out

The Messages API returns blocks. Store them as blocks.

Claude answers with response.content as an array of typed blocks. Keep it as an array between calls and the next tool_result finds its pair by id. Flatten it to a string and that pairing is gone.

Kept as an array

Every block round-trips with its type and id. The next tool_result matches on tool_use_id, the assistant picks up mid-tool.

Flattened to a string

The tool_use_id becomes a note inside a sentence. Claude sees a stray tool_result on the next call with nothing to pair against.

What DialogueDB adds

A managed history for Claude, plus three things the SDK leaves you

Blocks handled. The same client covers the three concerns a Claude app runs into next.

Every user gets their own conversation

Two users chat with the same assistant and never see each other. Their history, memory, and search all stay separated with one line of code.

Returning users do not start over

Facts worth keeping stay with the user, not the transcript. The next conversation opens with what they already told your assistant.

Answer follow-ups from earlier sessions

Search past exchanges by meaning to pull the right context into the next call. No embeddings pipeline and no vector database to run alongside your app.

The integration pattern

Five calls around the request you already make

Load, save, call, save. Nothing wraps messages.create, and no adapter class sits between your code and Claude.

The patternsrc/turn.ts
// 1. Load the stored conversation for this user
const dialogue = await db.getOrCreateDialogue({ id, namespace })
await dialogue.loadMessages({ order: "asc" })

// 2. Persist the incoming turn before the model call
await dialogue.saveMessage({ role: "user", content: input })

// 3. Call Claude with the rebuilt history
const response = await anthropic.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 16000,
  messages: toMessageParams(dialogue),
})

// 4. Store the blocks exactly as they came back
await dialogue.saveMessage({
  role: "assistant",
  content: response.content,
})

// 5. A tool result is a block, not a string
await dialogue.saveMessage({
  role: "user",
  content: [{ type: "tool_result", tool_use_id, content: result }],
})

DialogueDB does not proxy the Messages API or ship a client wrapper. The calls above are the whole integration.

The same client does more

Same install, six more things it gets you.

Resume a conversation on any worker

Load the dialogue by id and rebuild the messages array from scratch, whichever instance handles the request.

dialogue.loadMessages({ order: "asc" })

Facts and preferences across conversations

Store what the assistant should remember about a user, independent of any single transcript.

db.createMemory()

Recall by meaning before you call Claude

Search stored memories against the new turn and put the matches in the system prompt.

db.searchMemories()

Token counts alongside the message

Keep usage from each response on the message it belongs to, so cost per conversation is a query, not a guess.

metadata: { inputTokens }

Sub-agent branches off a conversation

Give a delegated sub-agent its own transcript while the main conversation stays clean. Parent and child dialogues stay linked without mixing turns.

dialogue.createThread()

Per-conversation scratchpad

Give each conversation its own state object for workflow flags, partial results, or in-progress context.

dialogue.saveState()

Install in 3 steps

From npm install to the first persisted turn, in under five minutes.

1

Install the client

npm install dialogue-db
2

Set the API key

DIALOGUE_DB_API_KEY=...
3

Save each turn

await dialogue.saveMessage({
  role: "assistant",
  content: response.content,
})
Get Your Free API Key

No credit card. Free tier to start. Starter is $29/month when usage grows.

The reference example

A working turn function and a manual tool loop you can clone and run as-is, both showing a conversation that survives a restart.

Runs on Edge and serverless too. The client is HTTP-only, with no connection pool and no native dependencies.

src/turn.ts
import Anthropic from "@anthropic-ai/sdk"
import { DialogueDB } from "dialogue-db"
import { toMessageParams } from "./persist"
 
const anthropic = new Anthropic()
const db = new DialogueDB({ apiKey: process.env.DIALOGUE_DB_API_KEY! })
 
export async function turn(id: string, namespace: string, input: string) {
const dialogue = await db.getOrCreateDialogue({ id, namespace })
await dialogue.loadMessages({ order: "asc" })
 
await dialogue.saveMessage({ role: "user", content: input })
 
const response = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 16000,
messages: toMessageParams(dialogue),
})
 
// content is an array of blocks, stored and returned untouched
await dialogue.saveMessage({
role: "assistant",
content: response.content,
metadata: {
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
},
})
 
return response
}

Where DialogueDB fits

Four ways to keep a Messages API conversation, and what each one asks of you.

ApproachSurvives a restartKeeps content blocks intactMemory and search included
In-process arrayNoYes, until the process endsNo
Your own tableYesIf you model JSON columns for itAdd a vector store and a pipeline
Standalone memory serviceYes, for extracted factsRarely, transcripts are summarizedMemory yes, full transcript no
DialogueDBYesYes, stored as sentYes, one API

If you know the Messages API, you already know this

Everything nests under a namespace, and the concepts line up with what the Anthropic SDK already gives you.

The data model

namespace

scoped to one user or tenant

dialogue

messages, threads, state

indexed for semantic search

memory

facts and preferences, cross-conversation

indexed for semantic search

Anthropic SDKDialogueDB
messages: MessageParam[] you pass to messages.createdialogue.loadMessages({ order: 'asc' }) rebuilds it
MessageParammessage, stored as sent
content block array (text, tool_use, tool_result)Kept as an array, exactly as returned
role: 'user' | 'assistant'Same role field on stored messages
system prompt stringComposed from db.searchMemories() at request time
usage.input_tokens, usage.output_tokensmetadata on the assistant message

What DialogueDB sees

Only what's needed for conversation storage

Your call to the Messages API goes straight from your server to Anthropic. DialogueDB sits beside that request, never inside it.

The client only receives what you explicitly send: message content, memory values, and the identifiers you scope them with. Your Anthropic key, your user tokens, and everything else in your app never pass through it.

What we receive

  • Message content blocks and roles
  • Memory values and metadata
  • Dialogue and namespace IDs

What we never see

  • Your Anthropic API key
  • User authentication tokens
  • Your prompts, model settings, or tool schemas

Two independent calls from your server

Your server

src/turn.ts

Your code decides what goes where.

nothing routed through us

Anthropic

api.anthropic.com

The model call goes direct. DialogueDB never sees the request or the credential.

DialogueDB

Messages and memory only

Receives what you explicitly save. Nothing implicit, nothing extra.

Frequently asked questions

One API for messages, memory, and search

Add it to your Anthropic SDK app in minutes.