Skip to content

Agents Overview

Agents are stateful, multi-turn AI actors. Unlike a single generate call, an agent carries conversation history across turns, calls tools, and can persist its session so a conversation survives across requests, processes, or machines. The Agents API is in beta, so you opt in explicitly.

Enable experimental features when you build Genkit, then reach the agent factories through genkit.beta():

Genkit genkit = Genkit.builder()
.options(GenkitOptions.builder().experimental(true).build())
.plugin(OpenAIPlugin.create())
.build();

You can also set GENKIT_EXPERIMENTAL=true in the environment before your process starts. Without the flag, calling genkit.beta() throws.

Use defineAgent to register a model-backed agent. The only required field is name; everything else is optional.

import com.google.genkit.agent.AgentConfig;
import com.google.genkit.ai.agent.Agent;
import com.google.genkit.ai.agent.FileSessionStore;
Agent<Map<String, Object>> weatherAgent = genkit.beta().defineAgent(
AgentConfig.<Map<String, Object>>builder()
.name("weatherAgent")
.description("A helpful weather assistant")
.system("You are a helpful weather assistant. Use the getWeather tool.")
.tools(getWeather) // Tool objects from genkit.defineTool
.model("openai/gpt-4o-mini")
.store(new FileSessionStore<>("./.snapshots")) // persist the session; omit to keep it client-side
.build());

The options you’ll reach for most often:

FieldDescription
nameRegistered name; also how the agent appears in the Dev UI
systemSystem prompt sent at the start of every turn
toolsTools the model may call during a turn
modelModel ID; falls back to your Genkit default model
configGeneration settings such as temperature and max tokens
maxTurnsCap on tool-calling turns within a single send
storeA SessionStore to persist the session server-side; omit for client-managed state
stateTypeJava class for your custom session state

See Define agents for the full configuration surface and for writing your own turn logic.

Agent.chat(...) returns an AgentChat that manages turn-by-turn history for you.

AgentChat<Map<String, Object>> chat = weatherAgent.chat();

To continue an earlier conversation, seed the chat with a snapshotId or sessionId:

import com.google.genkit.ai.agent.AgentInit;
AgentChat<Map<String, Object>> chat = weatherAgent.chat(
AgentInit.<Map<String, Object>>builder()
.snapshotId("existing-snapshot-id")
.build());

Call send for a blocking turn, or sendStream to observe chunks as the turn runs. Both return the same AgentResponse, and both automatically carry state forward to the next turn.

AgentResponse<Map<String, Object>> res = chat.send("What is the weather in London?");
System.out.println(res.text()); // "It is sunny and 22°C in London."
// History carries forward automatically:
AgentResponse<Map<String, Object>> res2 = chat.send("Now say that in French");
System.out.println(res2.text());
chat.sendStream("Summarize in one sentence", chunk -> {
if (chunk.text() != null) {
System.out.print(chunk.text()); // incremental tokens
}
});

AgentResponse gives you everything about the completed turn:

MethodReturns
text()The assistant message’s text
finishReason()Why the turn ended (STOP, LENGTH, INTERRUPTED, …)
snapshotId()This turn’s snapshot ID (server-managed)
sessionId()The session ID
custom()Your custom session state after the turn
artifacts()Any artifacts produced this turn
interrupts()Pending interrupts when the turn paused for input

See Run and stream for the full AgentResponse surface and every chunk shape.

You choose where an agent’s session is stored:

ModeHow to enableWhere state lives
Server-managedPass a SessionStore to .store(...)The store (memory, disk, Firestore, …)
Client-managedOmit .store(...)Carried by AgentChat, round-tripped each turn

Server-managed sessions are the norm when you want conversations to persist or to run across processes. Client-managed sessions are handy for stateless servers or when you’d rather own the conversation history yourself — AgentChat carries the messages and custom state along on every turn, so the model always sees the full context.

// Client-managed (stateless) agent — no .store(...)
Agent<Map<String, Object>> statelessAgent = genkit.beta().defineAgent(
AgentConfig.<Map<String, Object>>builder()
.name("weatherAgentStateless")
.system("You are a helpful weather assistant.")
.tools(getWeather)
.model("openai/gpt-4o-mini")
.build());
AgentChat<Map<String, Object>> statelessChat = statelessAgent.chat(ctx);
statelessChat.send("What is the weather in Tokyo?");
statelessChat.send("Compare that to Paris"); // history round-trips automatically

defineAgent builds a model-backed turn for you. When you want full control over what happens on each turn — a deterministic workflow, or an agent that only sometimes calls a model — use defineCustomAgent and write the turn logic yourself as an AgentFn:

import com.google.genkit.ai.agent.CustomAgentConfig;
import com.google.genkit.ai.agent.AgentFn;
import com.google.genkit.ai.agent.AgentResult;
import com.google.genkit.ai.agent.AgentFinishReason;
import com.google.genkit.ai.agent.InMemorySessionStore;
import com.google.genkit.ai.Message;
AgentFn<Map<String, Object>> echoFn = (sess, fnCtx) -> {
String userText = sess.getMessages().get(sess.getMessages().size() - 1).getText();
return AgentResult.builder()
.message(Message.model("echo: " + userText))
.finishReason(AgentFinishReason.STOP)
.build();
};
Agent<Map<String, Object>> echoAgent = genkit.beta().defineCustomAgent(
CustomAgentConfig.<Map<String, Object>>builder()
.name("echoAgent")
.store(new InMemorySessionStore<>())
.build(),
echoFn);

See Define agents for the full AgentFn and SessionRunner API.

See samples/agents-weather for a complete, runnable example that combines both modes and demonstrates streaming.