Conversation Storage
context.store is the built-in conversation storage capability of Makers Agent. It requires zero configuration to save and retrieve conversation history and provides native adaptation for Claude / OpenAI / LangGraph / DeepAgents Agent frameworks.The underlying layer is built on the platform's Blob storage. The APIs for Node and Python are fully mirrored (JavaScriptcamelCase, Pythonsnake_case). The framework integration section focuses on TypeScript, with each section including an equivalent Python sample.
Using in Two Runtimes
In the code within both the
agents/ and cloud-functions/ directories, store points to the same data.Directory | Access Point | Typical Use |
agents/ | context.store | Primary LLM conversation path, message appending, and Checkpointer |
cloud-functions/ | context.agent.store | Conversation list API and message query |
Framework Integration
context.store provides both framework-native adapters and generic APIs.framework | Recommended Usage | Entry Point (TS) |
claude-sdk | Framework-native SessionStore | context.store.claudeSessionStore() |
openai-sdk | Framework-native Session | context.store.openaiSession(sessionId) |
langgraph | Framework-native Checkpointer + BaseStore | context.store.langgraphCheckpointer / .langgraphStore |
deepagents | Reuse LangGraph Checkpointer + BaseStore | context.store.langgraphCheckpointer / .langgraphStore |
crewai | Not supported. Use the generic API for self-management. | - |
Claude Agent SDK
claudeSessionStore() returns an implementation of the SessionStore protocol (append / load / listSessions / delete / listSubkeys).// typescriptconst sessionStore = context.store.claudeSessionStore()// pythonsession_store = context.store.claude_session_store()
OpenAI Agents SDK
openaiSession(sessionId, { maxItems }) returns an implementation of the Session protocol from the OpenAI Agents SDK (getItems / addItems / popItem / clearSession).// typescriptconst session = context.store.openaiSession(context.conversation_id)// pythonsession = context.store.openai_session(context.conversation_id)
LangGraph
langgraphCheckpointer implements BaseCheckpointSaver (getTuple / list / put / putWrites). langgraphStore implements BaseStore (get / put / search / listNamespaces / batch). Semantic search is not currently supported by the search method of BaseStore.// typescriptconst checkpointer = context.store.langgraphCheckpointerconst store = context.store.langgraphStore// pythoncheckpointer = context.store.langgraph_checkpointerstore = context.store.langgraph_store
DeepAgents
DeepAgents is built on LangGraph and directly reuses the two adapters:
langgraphCheckpointer + langgraphStore.// typescriptconst checkpointer = context.store.langgraphCheckpointerconst store = context.store.langgraphStore// pythoncheckpointer = context.store.langgraph_checkpointerstore = context.store.langgraph_store
CrewAI
CrewAI's built-in Memory has a strong dependency on a vector database (default: LanceDB + embedder).
context.store currently lacks a vector layer. Conversation history can be managed using the generic API of context.store. Core capabilities such as multi-agent orchestration, task chains, and tool calling remain completely unaffected.General APIs
Method Overview
All methods are attached tocontext.store. The Node side uses object destructuring for parameters (camelCase), while the Python side uses keyword arguments (snake_case). Their semantics are identical.
Node Method | Python Method | Description |
appendMessage | append_message | Appends a message; automatically creates a conversation if it does not exist. |
getMessages | get_messages | Retrieves a list of messages and supports cursor-based pagination. |
updateMessage | update_message | Overwrites a specified message. |
deleteMessage | delete_message | Deletes a single message. |
clearMessages | clear_messages | Clears messages but retains conversation metadata. |
getConversation | get_conversation | Retrieves conversation metadata. |
listConversations | list_conversations | Lists conversations in descending order of lastMessageAt. |
updateConversation | update_conversation | Updates conversation metadata with a shallow merge. |
deleteConversation | delete_conversation | Deletes the entire conversation (irrecoverable). |
toAnthropicMessages | to_anthropic_messages | Converts a list of messages to the Anthropic Messages format. |
toOpenAIInput | to_openai_input | Converts a list of messages to the OpenAI Chat Completions format. |
appendMessage / append_message
Append a message to the specified conversation. If the conversation does not exist, it is automatically created, and a user index is established as needed.
Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Business-side conversation ID, length ≤ 256 bytes |
role | 'user' | 'assistant' | 'system' | 'tool' | Yes | Message role |
content | string | string[] | object | Yes | Message body, supporting plain text / string arrays / multimodal dict, with a serialized size ≤ 50 MB. |
metadata | Record<string, any> | No | Business-defined fields (such as number of tokens, tool_call, source tags, and so on) |
userId / user_id | string | No | Associated user. After being passed in, it is written to the user index to facilitate listing conversations by user. |
Return Value
The
messageId / message_id of the new message (in the format msg_xxx).TS Example:
const messageId = await context.store.appendMessage({conversationId: context.conversation_id,role: 'user',content: context.request.body.message,userId: context.request.body.userId,metadata: { source: 'web' },})
Python example:
message_id = await context.store.append_message(conversation_id=context.conversation_id,role="user",content=context.request.body["message"],user_id=context.request.body.get("user_id"),metadata={"source": "web"},)
getMessages / get_messages
Retrieve the message list for a specified conversation, supporting cursor-based pagination. If the conversation does not exist, no exception is thrown and an empty list is returned. By default, the list is sorted in chronological order (
order='asc', earliest first) to facilitate direct prompt assembly.Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
limit | number | No | Number of items per page, default 20, range [1, 100]. |
order | 'asc' | 'desc' | No | Sorting direction, default 'asc' (earliest first). |
after | string | No | A cursor that fetches messages after the specified messageId; mutually exclusive with before. |
before | string | No | A cursor that fetches messages before the specified messageId; mutually exclusive with after. |
Return Value
list[Message] — A message array. An empty array [] is returned if the conversation does not exist.TS Example:
const messages = await context.store.getMessages({conversationId: context.conversation_id,limit: 50,})const reply = await openai.chat.completions.create({model: 'gpt-4o',messages: context.store.toOpenAIInput(messages),})
Python example:
messages = await context.store.get_messages(conversation_id=context.conversation_id,limit=50,)reply = await openai_client.chat.completions.create(model="gpt-4o",messages=context.store.to_openai_input(messages),)
updateMessage / update_message
Overwrite a message. Only the fields you provide are overwritten, while fields not provided retain their original values. The
updatedAt / updated_at timestamps are automatically refreshed.Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
messageId / message_id | string | Yes | Target message ID |
content | string | string[] | object | No | New content; retains the original value if not provided. |
metadata | Record<string, any> | No | Overwrites the metadata entirely (not merged); retains the original value if not provided. |
Return Value
Message — The complete, updated message object.TS Example:
const updated = await context.store.updateMessage({conversationId: context.conversation_id,messageId: 'msg_abc123',content: 'corrected answer',metadata: { edited: true },})
Python example:
updated = await context.store.update_message(conversation_id=context.conversation_id,message_id="msg_abc123",content="corrected answer",metadata={"edited": True},)
deleteMessage / delete_message
Delete a single message.
Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
messageId / message_id | string | Yes | Target message ID |
TS Example:
await context.store.deleteMessage({conversationId: context.conversation_id,messageId: 'msg_abc123',})
Python example:
await context.store.delete_message(conversation_id=context.conversation_id,message_id="msg_abc123",)
clearMessages / clear_messages
Clear all messages in the conversation, but retain the
ConversationMeta. To delete it completely, use deleteConversation.Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
TS Example:
await context.store.clearMessages({ conversationId: context.conversation_id })
Python example:
await context.store.clear_messages(conversation_id=context.conversation_id)
getConversation / get_conversation
Obtain conversation metadata.
Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
Return Value
ConversationMeta — Contains fields such as conversationId / createdAt / lastMessageAt / messageCount / metadata.TS Example:
const meta = await context.store.getConversation({conversationId: context.conversation_id,})console.log(meta.messageCount, meta.metadata?.title)
Python example:
meta = await context.store.get_conversation(conversation_id=context.conversation_id,)print(meta.message_count, (meta.metadata or {}).get("title"))
listConversations / list_conversations
List conversations, sorted by
lastMessageAt, with support for cursor-based pagination and user-based filtering.Parameter
Parameter | Type | Required | Description |
limit | number | No | Number of items per page, default 20, range [1, 100]. |
order | 'asc' | 'desc' | No | Sorting direction, default 'desc' (latest first). |
after | string | No | A cursor that passes the nextCursor returned from the previous page. |
before | string | No | A cursor that passes the previousCursor returned from the previous page. |
userId / user_id | string | No | Lists only the conversations under the specified user (hits the user index). |
Return Value
ListConversationsResult — { items, nextCursor, previousCursor }.TS Example:
const { items, nextCursor } = await context.store.listConversations({userId: 'u_123',limit: 20,})
Python example:
result = await context.store.list_conversations(user_id="u_123", limit=20)items, next_cursor = result.items, result.next_cursor
updateConversation / update_conversation
Shallow merge metadata: Identical keys are overwritten, while different keys are retained.
Parameter
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
metadata | Record<string, any> | Yes | Fields to be merged; the key is deleted when the value is null / None. |
Return Value
ConversationMeta — The merged conversation metadata.TS Example:
await context.store.updateConversation({conversationId: context.conversation_id,metadata: { title: 'Product Inquiry', tag: null }, // Sets the title and removes the tag.})
Python example:
await context.store.update_conversation(conversation_id=context.conversation_id,metadata={"title": "Product Inquiry", "tag": None},)
deleteConversation / delete_conversation
Delete the entire conversation and synchronously clean up the message index, conversation metadata, and global conversation index. This action is irreversible.
Parameter:
Parameter | Type | Required | Description |
conversationId / conversation_id | string | Yes | Conversation ID |
TS Example:
await context.store.deleteConversation({conversationId: context.conversation_id,})
Python example:
await context.store.delete_conversation(conversation_id=context.conversation_id,)
toAnthropicMessages / to_anthropic_messages
Convert the message list returned by
getMessages into the format of the messages field for the Anthropic Messages API.Parameter
Parameter | Type | Required | Description |
messages | Message[] | Yes | Return result of getMessages / get_messages |
Return Value
Array<{ role: string; content: unknown }> — This can be directly used as the input parameter for anthropic.messages.create({ messages }).TS Example:
const history = await context.store.getMessages({conversationId: context.conversation_id,})const resp = await anthropic.messages.create({model: 'claude-sonnet-4',max_tokens: 1024,messages: context.store.toAnthropicMessages(history),})
Python example:
history = await context.store.get_messages(conversation_id=context.conversation_id,)resp = await anthropic_client.messages.create(model="claude-sonnet-4",max_tokens=1024,messages=context.store.to_anthropic_messages(history),)
toOpenAIInput / to_openai_input
Convert the message list returned by
getMessages into the format of the messages field for the OpenAI Chat Completions API. Retain role values that belong to user / assistant / system / tool, and pass through the content field unchanged.Parameter
Parameter | Type | Required | Description |
messages | Message[] | Yes | Return result of getMessages / get_messages |
Return Value
Array<{ role: string, content: any }> — This can be directly used as the input parameter for openai.chat.completions.create({ messages }).TS Example:
const history = await context.store.getMessages({conversationId: context.conversation_id,})const resp = await openai.chat.completions.create({model: 'gpt-4o',messages: context.store.toOpenAIInput(history),})
Python example:
history = await context.store.get_messages(conversation_id=context.conversation_id,)resp = await openai_client.chat.completions.create(model="gpt-4o",messages=context.store.to_openai_input(history),)
Data Structure
interface Message {messageId: string // msg_xxx (automatically generated by appendMessage)role: 'user' | 'assistant' | 'system' | 'tool'content: any // string / array / object (multimodal)createdAt: number // Timestamp in millisecondsmetadata?: Record<string, any> // Custom (e.g., token count, tool_call, etc.)updatedAt?: number // Present only after updateMessage}interface ConversationMeta {conversationId: stringcreatedAt: number // Timestamp in millisecondslastMessageAt: number // Timestamp in millisecondsmessageCount: numbermetadata?: Record<string, any> // Business-defined (e.g., title, user, tags, etc.)}interface ListConversationsResult {items: ConversationMeta[]nextCursor?: string // The cursor for the next page, to be passed to the after parameter.previousCursor?: string // The cursor for the previous page, to be passed to the before parameter.}
The Python-side fields aremessage_id / conversation_id / created_at / last_message_at / message_count / next_cursor / previous_cursor, and their overall structure is consistent.
Limits and Quotas
Item | Default Value | Exceedance Action |
conversation_id length | ≤ 256 characters | Throws a MemoryValidationError. |
Size of a single content item | ≤ 50 MB (after serialization) | Throws a MemoryValidationError. |
Maximum Messages per Conversation | 10000 | Throws a MemoryQuotaExceededError. |
limit Upper Bound | 100 | Throws a MemoryValidationError. |
limit Lower Bound | 1 | Throws a MemoryValidationError. |
For a single
content item that is excessively large (such as long documents and raw image data), it is recommended to store it in object storage and pass the URL into the message.Conversation Summary
getMessages can retrieve a maximum of 100 messages per call. For long conversations, the recommended practice is to keep the most recent N pieces of original text + compress earlier content into a summary, then assemble the prompt from the Store with each request. The Store itself does not call the LLM or generate summaries; it only provides the JSON container ConversationMeta.metadata.Responsibility Division
Responsible Party | Task/Responsibility |
Store | Provide the metadata JSON field + shallow merge writes. Define two keys: summary (summary text) and summarizedUntil (the messageId up to which summarization has been performed). |
Business side | Determine when to summarize, invoke a cost-effective model to generate the summary, and call updateConversation to write back. |
summary/summarizedUntilare conventional keys, not part of the schema. The Store simply stores them as arbitrary JSON. You can rename them, provided that the business logic maintains consistency before and after the change.
Implementation Example
You can use a cheaper model for summarization—compression + preserving key facts do not require the reasoning capabilities of a primary model. None of the four requirements in the prompt can be omitted; otherwise, issues such as divergent summaries, inclusion of casual greetings, language inconsistencies, and increasingly lengthy summaries may occur.
async function summarizeWithLLM(previousSummary, newMessages) {const transcript = newMessages.filter(m => m.role === 'user' || m.role === 'assistant').map(m => `${m.role}: ${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`).join('\n')const prompt = previousSummary? `Below are the [Existing Summary] and [New Dialogue] of a conversation. Please merge them into a new summary.Requirements:- Retain: key facts, user preferences, incomplete tasks, and important contextual settings.- Delete: casual greetings, acknowledgments of receipt, duplicate content, and tool invocation details.- Write in the same language as the most recent conversation.- Keep it within 500 words.[Existing Summary]${previousSummary}[New Dialogue]${transcript}[New Summary]`: `Please summarize the following conversation into a summary.Requirements:- Retain: key facts, user preferences, incomplete tasks, and important contextual settings.- Delete: casual greetings, acknowledgments of receipt, duplicate content, and tool invocation details.- Write in the same language as the conversation.- Keep it within 500 words.[Dialogue]${transcript}[Summary]`const { text } = await generateText({model: openai('gpt-4o-mini'),prompt,maxTokens: 800,})return text.trim()}
