ConceptsSessions & Memory

Session & MemoryStore

The Session and MemoryStore primitives in Kikoyu SDK manage multi-turn conversation turn history across multiple separate Runner.run() invocations.


Why Storage-Agnostic Memory?

AI agents in production need to retain user turn history across HTTP requests, server restarts, or distributed serverless instances (Next.js, AWS Lambda, Cloudflare Workers).

Kikoyu SDK provides a storage-agnostic architecture:

  • Session: The session wrapper representing a unique user conversation turn log.
  • MemoryStore: The pluggable storage engine interface (InMemoryStore, Redis, PostgreSQL, DynamoDB).

Basic Usage with InMemoryStore

import { Agent, InMemoryStore, Runner, Session } from '@kikoyu/core';
 
// 1. Instantiate a shared memory store
const memoryStore = new InMemoryStore();
 
// 2. Create or resume a session
const session = new Session({
  sessionId: 'session_usr_1042',
  store: memoryStore,
});
 
// 3. Define agent
const agent = Agent.builder()
  .setName('Personal Concierge')
  .setInstructions('Remember user preferences across multiple turns.')
  .build();
 
const runner = new Runner();
 
// --- Turn 1 ---
await runner.run(agent, 'My favorite coffee order is an iced oat milk latte with vanilla.', { session });
 
// --- Turn 2 (Separate request, same session) ---
const response = await runner.run(agent, 'What coffee should I order today?', { session });
console.log(response.text); 
// "I recommend your favorite: an iced oat milk latte with vanilla!"

Building a Custom Database MemoryStore (Redis / Postgres)

To persist session state in external databases like Redis or PostgreSQL, implement the simple MemoryStore interface:

import { MemoryStore, Message } from '@kikoyu/core';
import { Redis } from 'ioredis';
 
export class RedisMemoryStore implements MemoryStore {
  private redis: Redis;
 
  constructor(redisUrl: string) {
    this.redis = new Redis(redisUrl);
  }
 
  async getMessages(sessionId: string): Promise<Message[]> {
    const raw = await this.redis.get(`session:${sessionId}`);
    return raw ? JSON.parse(raw) : [];
  }
 
  async saveMessages(sessionId: string, messages: Message[]): Promise<void> {
    // Save conversation turn history with a 7-day TTL
    await this.redis.set(`session:${sessionId}`, JSON.stringify(messages), 'EX', 604800);
  }
 
  async clearSession(sessionId: string): Promise<void> {
    await this.redis.del(`session:${sessionId}`);
  }
}

Managing Turn History & Sliding Windows

To prevent hitting LLM context window limits during long conversations, you can trim or clear turn history using Session helpers:

// Clear all history for a session
await session.clear();
 
// Get current turn message count
const history = await session.getMessages();
console.log(`Current session contains ${history.length} messages.`);

Best Practices

  1. Use Unique Session IDs: Key session IDs by composite tenant keys (e.g. tenantId:userId:chatId) to prevent multi-tenant data bleed.
  2. Implement Database TTLs: Automatically expire inactive sessions after 7–30 days in your storage database.
  3. Pass Session to Runner: Always pass { session } in runner.run(agent, prompt, { session }) for continuous multi-turn flows.