Announcing Genkit Agents: A full-stack foundation for conversational AI
Genkit is an open source framework for: Build full-stack, AI-powered agent applications for any platform Supports TypeScript, Go, Dart, and Python. Some of the most appealing AI features are conversational, such as a support assistant that remembers tickets and a co-pilot that operates over multiple turns. each requires multiple generate() Building this now means manually connecting message history, tool loops, streaming, persistence, and front-end protocols. This plumbing is repeated for every project and has little to do with the features of the app.
Genkit solves this like this: Agent APIpackaging all this behind one interface. Define the agent on the server and drive the agent with the same. chat() Whether the API runs in-process or behind an HTTP endpoint.
> The agent API is: preview Today we’ll be talking about TypeScript and Go. Minor version releases may introduce breaking changes.
Define the agent
A name and system prompt are required to start the agent. From there, add tools, state, and session stores as your functionality grows.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
g := genkit.Init(ctx,
genkit.WithPlugins(&googlegenai.GoogleAI{}),
genkit.WithExperimental(), // Enables preview features like Agents API.
)
assistant := genkitx.DefineAgent(g, "assistant",
aix.InlinePrompt{
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithSystem("You are a helpful assistant."),
},
)
out, err := assistant.RunText(ctx, "Hello. What can you do?")
if err != nil {
log.Fatal(err)
}
fmt.Println(out.Message.Text())
go
The same agent object is flexible and can handle one-shot responses, streaming turns, paused tool calls, and multi-turn conversations. As your functionality grows, you will never reach another abstraction.
State to live where you want
All conversations require continuity between turns, and you decide who owns it.
addition store and the agent server management. The server maintains a snapshot of messages, custom state, and artifacts, and the client continues by sending back a session ID. Select this for persistent chat apps, shared devices, and workflows that don’t require the client to carry out the entire conversation.
import firebasex "github.com/firebase/genkit/go/plugins/firebase/exp"
import genkitx "github.com/firebase/genkit/go/genkit/exp"
store, err := firebasex.NewFirestoreSessionStore[WeatherState](ctx, g,
firebasex.WithCollection("snapshots"),
firebasex.WithCheckpointInterval(10),
)
if err != nil {
log.Fatal(err)
}
weatherAgent := genkitx.DefineAgent(g, "weatherAgent",
aix.InlinePrompt{
ai.WithSystem("Answer weather questions. Ask for a location when one is missing."),
ai.WithTools(getWeather),
},
aix.WithSessionStore(store),
)
go
The store you configure determines where your snapshots reside. For production environments, Firestore provides a managed multi-instance database that can be shared by multiple server instances. Genkit also ships with a lightweight store for local work, and you can also implement your own store. This is explained in the section below.
If you leave the store unchecked, the agent becomes client-managed. The server returns the complete state and the client sends it back on its next turn. Use this if your app already has persistence or requires stateless server deployment.
A snapshot is written after each successful server management turn, so you can resume up to date by: sessionId Or branch from a precise point in history with: snapshotId. Branching allows users to explore alternatives from the moment they are saved without disrupting the original thread.
// Continue the latest state in a conversation.
out, err := weatherAgent.RunText(ctx, "Continue where we left off.",
aix.WithSessionID[WeatherState]("user-session-123"),
)
// Or branch from a specific saved point.
branch, err := weatherAgent.RunText(ctx, "Revise this plan for a smaller budget.",
aix.WithSnapshotID[WeatherState](approvedPlanSnapshotID),
)
go
In addition to message history, agents maintain two additional types of state. custom state Application data entered, compact controls, and UI values that drive the next turn (workflow status, task list, selected entity, etc.). artifact Generated output, such as reports, patches, and itineraries, that users can independently inspect, download, or version. The tool updates one over the course of an active session, and Genkit streams the changes to the client as they occur.
Serve over HTTP
All agents are already serviceable actions, so putting them behind an HTTP endpoint is a matter of a few lines. The root helper returns a standard http.ServeMux mounted descriptor, connects the turn endpoint and snapshot, and aborts the companion.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
mux := http.NewServeMux()
for _, route := range genkitx.AllAgentRoutes(g) {
mux.HandleFunc(route.Pattern(), route.Handler())
}
log.Fatal(http.ListenAndServe(":8080", mux))
go
The same wire protocol is what the following clients communicate with, so the JavaScript or Go backend serves all clients similarly.
Rich client for full stack integration
The connection between the server and client is the remote agent. remoteAgent() returns handle same chat() interface It runs as a local agent, so the code that drives the agent in backend tests is the code that drives the agent from the browser. There is no need to design separate request and response protocols, and no need to invent streaming formats.
Since we are launching a JavaScript client, the web frontend can communicate with the same agent endpoint. Below is an example of how to connect to a remote agent from a TypeScript frontend.
import { remoteAgent } from 'genkit/beta/client';
const agent = remoteAgent({
url: 'http://localhost:8080/api/weatherAgent',
});
const chat = agent.chat();
const res = await chat.send('Weather in Tokyo?');
console.log(res.text);
JavaScript
The client communicates a single wire protocol over the agent route, so it works similarly for JavaScript or Go backends. Resolve dynamic authentication headers on each request, patch the streamed state, and proceed with the next turn using the session ID, snapshot ID, or client-managed state used by the agent.
Streaming is built into the same interface. sendStream() A chunk stream and final response are provided, and each chunk can contain text, custom state, or generation artifacts.
const turn = agent.chat().sendStream('Write a long report.');
for await (const chunk of turn.stream) {
if (chunk.text) process.stdout.write(chunk.text);
if (chunk.custom) updateStatus(chunk.custom);
if (chunk.artifact) renderArtifact(chunk.artifact);
}
const res = await turn.response;
JavaScript
If you already have an app that uses the Vercel AI SDK UI library, @genkit-ai/vercel-ai the package provides that adapter useChat Hook. of GenkitChatTransport adapter connects useChat This allows you to assemble interfaces from Vercel’s AI Elements components while still taking advantage of all the benefits of Genkit on the backend.
Built-in human approval
The tool can pause the agent and return control to the user. The model determines that external input is required, the tool interrupts, and the client accepts, rejects, or supplies missing values before continuing with the turn. It’s a way to involve a human before paying, deploying, or any action you don’t want to take automatically.
import genkitx "github.com/firebase/genkit/go/genkit/exp"
import "github.com/firebase/genkit/go/ai/exp/tool"
runShell := genkitx.DefineInterruptibleTool(g, "run_shell",
"Run a shell command after a safety check.",
func(ctx context.Context, input ShellInput, confirm *Confirmation) (ShellOutput, error) {
if isRisky(input.Command) {
if confirm == nil {
return ShellOutput{}, tool.Interrupt(ShellInterrupt{
Command: input.Command,
Reason: "The command can modify files.",
})
} else if !confirm.Approved {
return ShellOutput{}, errors.New("user rejected shell command execution")
}
}
return execute(input.Command)
},
)
go
The turn ends as follows: interrupted Suspended request with reason and response. The client resumes when the user responds, and the runtime validates the resume payload against the session history, so the tool cannot be tricked into running with bogus input.
Work that continues beyond requests
Some turns may take longer than you would like. In server-managed state, clients can detach turns, close tabs, and reconnect later by snapshot ID. The agent continues to run on the server and writes its progress to a pending snapshot that another session can poll, wait for, or abort.
const chat = reportAgent.chat({ sessionId: 'report-123' });
const task = await chat.detach('Write the quarterly market report.');
// Persist this so any client can reconnect to the work later.
savePendingSnapshot(task.snapshotId);
for await (const snapshot of task.poll({ intervalMs: 1000 })) {
renderStatus(snapshot.status);
if (snapshot.status === 'completed') renderMessages(snapshot.state.messages);
}
JavaScript
This makes long research jobs, multi-step planning, and tool-intensive workflows practical without having to keep connections open or building separate job queues.
coordination specialist
If one prompt doesn’t do everything well, you can split the work among specialized agents and have an orchestrator combine the results. of Agents The middleware injects delegation tools for each subagent, so the orchestrator model can route some of the requests to the appropriate specialist. Genkit’s subagents give you complete control and the ability to implement your own orchestration.
import middlewarex "github.com/firebase/genkit/go/plugins/middleware/exp"
coordinator := genkit.DefineAgent(g, "coordinator",
aix.InlinePrompt{
ai.WithSystem("Delegate to specialists, inspect their results, then answer the user."),
ai.WithUse(
&middlewarex.Agents{
Agents: []aix.AgentRef{researcher.Ref(), coder.Ref()},
MaxDelegations: 5,
ArtifactStrategy: middlewarex.ArtifactStrategySession,
},
&middlewarex.Artifacts{Readonly: true},
),
},
)
go
Delegation appears as a regular tool activity within the Orchestrator’s stream, and the specialist’s artifacts can be merged into the parent session, allowing you to build the final answer based on what each specialist has created.
If you want to use ADK instead
Genkit agents are application primitives built to run within full-stack user-facing apps. Consider the Agent Development Kit (ADK) instead if:
- Multi-agent orchestration is an entire system, not just one feature. The ADK is purpose-built for complex agent topologies, and Genkit’s delegation middleware is intentionally lightweight and not built into the core of the agent abstraction.
- You need a managed runtime, not just a library. ADK works with Agent Runtime on Gemini Enterprise Agent Platform to host, scale, and manage sessions.
Choose your staying power
The Server Management Agent stores snapshots through the session store. Genkit ships with several snapshots so you can match your store to where you’re running.
- In-memory For testing, demos, and single-process experiments.
- file For local development and single-host apps that require snapshots to survive reboots.
- fire store For production apps on Google Cloud or Firebase that require a managed, multi-instance database without having to write store code.
- custom If you need to use your own database or authentication, or require a specific retention policy. You can implement your own persistence layer using the “store” interface.
Test and explore in the developer UI
Agents are first class in the Genkit developer UI. new agent runner You can start conversations, send turns, monitor stream output and state updates, suspend tools, and inspect snapshots without creating a client. This is the fastest way to run the agent while building it and replay the conversation while debugging.
Let’s get started
The Agents API turns the iterative plumbing of conversational, full-stack AI into something you configure rather than rebuild. Define the agent on the server, specify a store if you want persistence, and use the same chat() interface to drive the agent from the front end via remoteAgent().
Visit our full-stack agent documentation to learn more, or get started with Genkit if you’re new to the framework. The API is in beta, so we welcome your feedback. Submit questions about what you want to build and what you want to change.
Have fun coding! 🚀
