Conversation Management
Grid provides a sophisticated conversation management system that handles message history, context preservation, and stateful interactions across different conversation patterns.
Overview​
Conversation management in Grid involves:
- Message History: Tracking and managing conversation messages
- Context Preservation: Maintaining state across interactions
- Memory Management: Optimizing for LLM context windows
- Session Handling: Managing multi-turn conversations
Message Types​
Grid uses Vercel AI SDK's message format:
interface Message {
role: "system" | "user" | "assistant" | "tool";
content: string;
name?: string; // For tool messages
toolCalls?: ToolCall[]; // For assistant messages
toolCallId?: string; // For tool result messages
}
User Messages​
Messages from the user:
const userMessage: Message = {
role: "user",
content: "What's the weather in San Francisco?",
};
Assistant Messages​
Responses from the AI:
const assistantMessage: Message = {
role: "assistant",
content: "I'll check the weather for you.",
toolCalls: [{
id: "call_123",
name: "get_weather",
args: { location: "San Francisco" },
}],
};
Tool Messages​
Results from tool executions:
const toolMessage: Message = {
role: "tool",
content: "72°F and sunny",
toolCallId: "call_123",
name: "get_weather",
};
Using Conversation Primitives​
Grid provides conversation primitives that handle message storage and state management. See Conversation Primitives for detailed API documentation.
Basic Message History​
Using createConversationHistory
for message management:
import { createConversationHistory } from "@mrck-labs/grid-core";
const history = createConversationHistory("You are a helpful assistant");
// Add messages
history.addMessage({
role: "user",
content: "Hello!",
});
history.addMessage({
role: "assistant",
content: "Hi there! How can I help you today?",
});
// Retrieve messages
const all = history.getMessages();
const nonSystem = history.getNonSystemMessages();
const lastUser = history.getLastMessageByRole("user");
Context Management​
Using createConversationContext
for state management:
import { createConversationContext } from "@mrck-labs/grid-core";
const context = createConversationContext();
// Set user preferences
context.updateState("user.name", "Alice");
context.updateStates({
"user.preferences.language": "en",
"user.preferences.timezone": "PST",
"user.preferences.verbose": true,
});
// Set session data
context.updateMetadata("sessionId", "sess_123");
context.updateMetadata("topic", "technical-support");
// Track metrics
context.incrementMessageCount();
context.incrementToolCallCount(1);
Using Context in Agents​
const context = createConversationContext();
const llmService = baseLLMService({ langfuse: { enabled: false } });
const toolExecutor = createToolExecutor();
const agent = createConfigurableAgent({
llmService,
toolExecutor,
config: {
id: "contextual-agent",
type: "general",
version: "1.0.0",
prompts: {
system: "You are a helpful assistant."
},
metadata: {
id: "contextual-agent",
type: "general",
name: "Contextual Agent",
description: "Agent with context awareness",
capabilities: ["general"],
version: "1.0.0"
},
tools: {
builtin: [],
custom: [],
mcp: []
},
behavior: {
maxRetries: 3,
responseFormat: "text"
}
},
customHandlers: {
transformInput: async (input) => {
const userName = context.getStateValue("user.name");
const language = context.getStateValue("user.preferences.language");
// Personalize the interaction
if (userName) {
input.messages[0].content =
`${userName} asks: ${input.messages[0].content}`;
}
return input;
}
}
});
Context Persistence​
Save and restore conversation state:
// Save context automatically with event handlers
const context = createConversationContext({
onStateChanged: async (key, value) => {
await database.state.upsert({ key, value });
},
onMetadataChanged: async (key, value) => {
await database.metadata.upsert({ key, value });
},
});
// Or manually save snapshots
const snapshot = context.getSnapshot();
await saveToDatabase(snapshot);
// Restore context
const saved = await loadFromDatabase();
const newContext = createConversationContext();
// Restore by updating state and metadata
Object.entries(saved.state).forEach(([key, value]) => {
newContext.updateState(key, value);
});
Object.entries(saved.metadata).forEach(([key, value]) => {
newContext.updateMetadata(key, value);
});
Event-Based Persistence​
Grid provides event handlers at all levels of the conversation architecture for seamless database integration:
History-Level Events​
import { createConversationHistory } from "@mrck-labs/grid-core";
const history = createConversationHistory("You are a helpful assistant", {
onMessageAdded: async (message) => {
await database.messages.create({
data: {
role: message.role,
content: message.content,
timestamp: new Date(),
},
});
},
onMessagesCleared: async () => {
await database.messages.deleteMany({ sessionId: currentSession });
},
});
Context-Level Events​
import { createConversationContext } from "@mrck-labs/grid-core";
const context = createConversationContext({
onStateChanged: async (key, value) => {
await database.state.upsert({
where: { key },
update: { value },
create: { key, value },
});
},
onMetadataChanged: async (key, value) => {
await database.metadata.upsert({
where: { key },
update: { value },
create: { key, value },
});
},
});
Manager-Level Grouped Events​
import { createConversationManager } from "@mrck-labs/grid-core";
const manager = createConversationManager({
historyOptions: {
systemPrompt: "You are a helpful assistant"
},
handlers: {
// Grouped handlers for clean organization
manager: {
onUserMessageAdded: async (message) => {
await analytics.track("user_message", { content: message });
},
onAgentResponseProcessed: async (response) => {
await analytics.track("agent_response", {
hasTools: response.toolCalls?.length > 0
});
},
},
history: {
onMessageAdded: async (message) => {
await database.messages.create({ data: message });
},
},
context: {
onStateChanged: async (key, value) => {
await database.state.upsert({ key, value });
},
},
});
Loop-Level Events​
import { createConversationLoop } from "@mrck-labs/grid-core";
const loop = createConversationLoop({
agent,
handlers: {
onConversationStarted: async ({ sessionId, userId }) => {
// Load previous messages from database
const messages = await database.messages.findMany({
where: { sessionId }
});
return { initialMessages: messages };
},
onMessageSent: async (message, context) => {
await database.audit.create({
type: "message_sent",
content: message,
context,
});
},
onResponseReceived: async (response, context) => {
await database.audit.create({
type: "response_received",
response,
context,
});
},
onConversationEnded: async (summary, context) => {
await database.sessions.update({
where: { id: context.sessionId },
data: { summary, endedAt: new Date() },
});
},
},
});
Conversation Patterns​
Simple Q&A​
Basic question-answer pattern:
async function simpleQA(agent: Agent, question: string) {
const response = await agent.act(question);
console.log(response.content);
}
Multi-turn Conversations​
Maintain context across multiple turns using conversation primitives:
import { createConversationManager } from "@mrck-labs/grid-core";
const createConversationSession = (agent) => {
const manager = createConversationManager();
return {
process: async (userInput: string) => {
// Add user message
manager.addUserMessage(userInput);
// Process with agent
const response = await agent.act({
messages: manager.getMessages(),
context: manager.getState(),
});
// Process response
manager.processAgentResponse(response);
return response.content;
},
getState: () => manager.getConversationState(),
reset: () => manager.reset(),
};
};
// Usage
const session = createConversationSession(myAgent);
const response1 = await session.process("Hello!");
const response2 = await session.process("What's my name?"); // Maintains context
Stateful Workflows​
Complex workflows with state transitions:
class WorkflowConversation {
private state: "greeting" | "collecting_info" | "processing" | "complete" = "greeting";
async process(input: string): Promise<string> {
switch (this.state) {
case "greeting":
this.state = "collecting_info";
return "Hello! I'll help you with your request. What's your name?";
case "collecting_info":
this.context.set("user.name", input);
this.state = "processing";
return `Nice to meet you, ${input}! What can I help you with?`;
case "processing":
const response = await this.processRequest(input);
this.state = "complete";
return response;
case "complete":
return "Is there anything else I can help you with?";
}
}
}
Memory Strategies​
Sliding Window​
Keep only recent messages:
const history = new ConversationHistoryService({
strategy: "sliding_window",
windowSize: 20, // Keep last 20 messages
});
Summary Compression​
Summarize old messages to preserve context:
class CompressedHistory extends ConversationHistoryService {
async compress(): Promise<void> {
const oldMessages = this.getMessages({
count: 50,
offset: this.size() - 100
});
const summary = await this.summarizeMessages(oldMessages);
// Replace old messages with summary
this.replaceRange(
this.size() - 100,
50,
{ role: "system", content: `Previous conversation summary: ${summary}` }
);
}
}
Importance-based Retention​
Keep important messages longer:
class SmartHistory extends ConversationHistoryService {
private importance = new Map<string, number>();
addMessage(message: Message, importance: number = 1): void {
super.addMessage(message);
this.importance.set(message.id, importance);
}
trim(): void {
// Sort by importance and recency
const sorted = this.getAllMessages()
.map((msg, idx) => ({ msg, idx, importance: this.importance.get(msg.id) || 1 }))
.sort((a, b) => b.importance - a.importance || b.idx - a.idx);
// Keep top N messages
const keep = sorted.slice(0, this.config.maxMessages);
this.messages = keep.map(item => item.msg);
}
}
Session Management​
Creating Sessions​
import { createConversationLoop } from "@mrck-labs/grid-core";
const createSessionManager = () => {
const sessions = new Map();
return {
createSession: (userId: string, agent: Agent) => {
const sessionId = generateId();
const session = {
id: sessionId,
userId,
startTime: new Date(),
conversation: createConversationLoop({
agent,
}),
};
sessions.set(sessionId, session);
return session;
},
getSession: (sessionId: string) => {
return sessions.get(sessionId);
},
listUserSessions: (userId: string) => {
return Array.from(sessions.values())
.filter(s => s.userId === userId)
.map(s => ({
id: s.id,
startTime: s.startTime,
analytics: s.conversation.getAnalytics(),
}));
},
};
};
Session Persistence​
Save sessions for later:
const createSessionStorage = (database) => {
return {
save: async (session) => {
const exported = session.conversation.exportConversation();
await database.sessions.upsert({
id: session.id,
userId: session.userId,
conversationData: exported,
updatedAt: new Date(),
});
},
load: async (sessionId: string, agent: Agent) => {
const data = await database.sessions.findUnique({
where: { id: sessionId }
});
const conversation = createConversationLoop({
agent,
});
conversation.importConversation(data.conversationData);
return {
id: data.id,
userId: data.userId,
conversation,
};
},
list: async (userId: string) => {
const sessions = await database.sessions.findMany({
where: { userId },
orderBy: { updatedAt: 'desc' },
});
return sessions.map(s => ({
id: s.id,
updatedAt: s.updatedAt,
messageCount: s.conversationData.messages.length,
}));
},
};
};
Advanced Features​
Branching Conversations​
Support multiple conversation branches:
class BranchingConversation {
private branches = new Map<string, ConversationHistory>();
private currentBranch = "main";
branch(name: string): void {
const current = this.branches.get(this.currentBranch);
this.branches.set(name, current.clone());
this.currentBranch = name;
}
switchBranch(name: string): void {
if (this.branches.has(name)) {
this.currentBranch = name;
}
}
merge(fromBranch: string, toBranch: string): void {
const from = this.branches.get(fromBranch);
const to = this.branches.get(toBranch);
// Merge strategy: append messages
from.getAllMessages().forEach(msg => to.addMessage(msg));
}
}
Conversation Analytics​
Track conversation metrics:
class ConversationAnalytics {
private metrics = {
messageCount: 0,
toolCallCount: 0,
avgResponseTime: 0,
sentimentScores: [],
};
trackMessage(message: Message, responseTime?: number): void {
this.metrics.messageCount++;
if (message.toolCalls?.length) {
this.metrics.toolCallCount += message.toolCalls.length;
}
if (responseTime) {
this.updateAvgResponseTime(responseTime);
}
}
getMetrics(): ConversationMetrics {
return {
...this.metrics,
avgSentiment: this.calculateAvgSentiment(),
conversationLength: this.calculateDuration(),
};
}
}
Best Practices​
1. Context Window Awareness​
Always consider LLM context limits when designing conversations.
2. State Management​
Use the context service for state that needs to persist across messages.
3. Message Cleanup​
Implement appropriate retention policies for production systems.
4. Error Recovery​
Design conversations to gracefully handle and recover from errors.
5. Privacy​
Be mindful of sensitive information in conversation history.
Next Steps​
- Observability - Monitor conversations with Langfuse
- Event Handlers - Implement persistence with events
- Conversation Primitives - Deep dive into primitives