Skip to content

Chat Sessions

Genkit supports multi-turn chat sessions with automatic history management, typed session state, multiple conversation threads, and pluggable persistence via SessionStore.

Session<Void> session = genkit.createSession();
import com.google.genkit.ai.session.*;
Session<ConversationState> session = genkit.createSession(
SessionOptions.<ConversationState>builder()
.store(sessionStore)
.initialState(new ConversationState("John"))
.sessionId("custom-session-id") // optional, auto-generated if omitted
.build());

Create a Chat from a session. The chat manages message history, tool execution, and streaming automatically:

Chat<ConversationState> chat = session.chat(
ChatOptions.<ConversationState>builder()
.model("openai/gpt-4o-mini")
.system("You are a helpful assistant.")
.tools(List.of(noteTool, searchTool))
.config(GenerationConfig.builder().temperature(0.7).build())
.build());

Each send() call automatically includes the full conversation history:

ModelResponse r1 = chat.send("What is the capital of France?");
System.out.println(r1.getText()); // "Paris"
ModelResponse r2 = chat.send("And what about Germany?");
System.out.println(r2.getText()); // "Berlin" — knows we're asking about capitals

Override model or tools for a specific message:

ModelResponse response = chat.send("Summarize everything",
Chat.SendOptions.builder()
.model("openai/gpt-4o") // use a different model for this turn
.maxTurns(5)
.build());
ModelResponse response = chat.sendStream("Tell me a long story",
(chunk) -> System.out.print(chunk.getText()));

Sessions can hold typed state that persists across messages and is saved to the store:

public class ConversationState {
private String userName;
private int messageCount;
private List<String> topics;
// constructors, getters, setters...
}
// Read state
ConversationState state = session.getState();
System.out.println("User: " + state.getUserName());
// Update state
state.incrementMessageCount();
state.getTopics().add("geography");
session.updateState(state).join();

A single session can have multiple independent conversation threads, each with its own history:

Chat<ConversationState> generalChat = session.chat("general",
ChatOptions.<ConversationState>builder()
.model("openai/gpt-4o-mini")
.system("You are a general assistant.")
.build());
Chat<ConversationState> supportChat = session.chat("support",
ChatOptions.<ConversationState>builder()
.model("openai/gpt-4o-mini")
.system("You are a support agent.")
.build());
// Each thread maintains separate history
generalChat.send("What's the weather like?");
supportChat.send("I have a billing issue");
List<Message> history = chat.getHistory();
List<Message> supportHistory = session.getMessages("support");
List<Message> mainHistory = session.getMessages(); // default "main" thread

Resume a previous session by loading it from the store:

Session<ConversationState> loaded = genkit.loadSession(
"session-123",
SessionOptions.<ConversationState>builder()
.store(sessionStore)
.build()
).get(); // returns CompletableFuture
// Continue the conversation with full history
Chat<ConversationState> chat = loaded.chat();
chat.send("Where were we?");

The SessionStore interface defines how sessions are persisted. Genkit includes an in-memory implementation, and you can create custom stores for any backend.

public interface SessionStore<S> {
CompletableFuture<SessionData<S>> get(String sessionId);
CompletableFuture<Void> save(String sessionId, SessionData<S> sessionData);
CompletableFuture<Void> delete(String sessionId);
}

The default implementation stores sessions in a ConcurrentHashMap. Useful for development and testing — data is lost when the process stops:

InMemorySessionStore<ConversationState> store = new InMemorySessionStore<>();
Session<ConversationState> session = genkit.createSession(
SessionOptions.<ConversationState>builder()
.store(store)
.initialState(new ConversationState("John"))
.build());

Implement the SessionStore interface to persist sessions to any backend — Redis, a database, Firebase, etc.

import com.google.genkit.ai.session.SessionStore;
import com.google.genkit.ai.session.SessionData;
public class RedisSessionStore<S> implements SessionStore<S> {
private final RedisClient redis;
private final ObjectMapper mapper;
public RedisSessionStore(RedisClient redis, ObjectMapper mapper) {
this.redis = redis;
this.mapper = mapper;
}
@Override
public CompletableFuture<SessionData<S>> get(String sessionId) {
return CompletableFuture.supplyAsync(() -> {
String json = redis.get("session:" + sessionId);
if (json == null) return null;
return mapper.readValue(json, SessionData.class);
});
}
@Override
public CompletableFuture<Void> save(String sessionId, SessionData<S> data) {
return CompletableFuture.runAsync(() -> {
String json = mapper.writeValueAsString(data);
redis.set("session:" + sessionId, json);
});
}
@Override
public CompletableFuture<Void> delete(String sessionId) {
return CompletableFuture.runAsync(() -> {
redis.del("session:" + sessionId);
});
}
}

Then use it like any other store:

RedisSessionStore<ConversationState> store =
new RedisSessionStore<>(redisClient, objectMapper);
Session<ConversationState> session = genkit.createSession(
SessionOptions.<ConversationState>builder()
.store(store)
.initialState(new ConversationState("John"))
.build());

The SessionData object is what gets persisted. It contains:

FieldTypeDescription
idStringSession ID
stateSYour typed session state
threadsMap<String, List<Message>>All conversation threads and their message histories
// Access thread data directly
SessionData<ConversationState> data = store.get("session-123").get();
List<Message> mainThread = data.getThread("main");
Map<String, List<Message>> allThreads = data.getThreads();

See the chat-session sample for a complete multi-turn chat implementation with state, tools, and session persistence.