Build agentic full-stack apps with Genkit Google's open-source framework Genkit has introduced a new Agents API in preview for TypeScript and Go, enabling developers to build full-stack AI-powered applications with built-in support for message history, tool loops, streaming, persistence, and frontend protocols. The API allows defining agents on the server and driving them with a unified chat() interface, supporting both server-managed and client-managed conversation continuity. Genkit https://genkit.dev/ is an open-source framework for building full-stack, AI-powered and agentic applications for any platform with support for TypeScript, Go, Dart, and Python. Some of the most compelling AI features are conversational, like a support assistant that remembers the ticket or a copilot that works across several turns. Each needs more than a single generate call, and building one today means wiring up message history, the tool loop, streaming, persistence, and a frontend protocol by hand. That plumbing repeats on every project and has little to do with what makes your app distinct. Genkit solves this with the Agents API https://genkit.dev/docs/agents/overview/ , which packages all of that behind one interface. You define an agent on the server, then drive it with the same chat API whether it runs in process or behind an HTTP endpoint. The Agents API is in preview today in TypeScript and Go. It can introduce breaking changes in minor version releases. An agent needs a name and a system prompt to start. From there you add tools, state, and a session store as the feature grows. python 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 The same agent object is flexible and can handle a one-shot reply, a streamed turn, a paused tool call, and a multi-turn conversation. You do not reach for a different abstraction as the feature grows. Every conversation needs continuity between turns, and you decide who owns it. Add a store and the agent becomes server-managed . The server persists messages, custom state, and artifacts as snapshots, and clients continue by sending back a session ID. Choose this for persistent chat apps, shared devices, and any workflow where the client should not carry the whole conversation. python 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 , The store you configure decides where snapshots live. For production, Firestore gives you a managed, multi-instance database that several server instances can share. Genkit also ships lighter stores for local work and lets you implement your own, which the section below covers. Leave the store off and the agent is client-managed: the server returns the full state and the client sends it back on the next turn. Use this when your app already owns persistence or you need stateless server deployments. Every successful server-managed turn writes a snapshot, so you can resume the latest state by sessionId or branch from an exact point in history by snapshotId . Branching lets a user explore an alternative from any saved moment without disturbing 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 , Alongside message history, an agent carries two more kinds of state. Custom state is your typed application data, the compact control and UI values that drive the next turn, such as workflow status, a task list, or selected entities. Artifacts are generated outputs the user may inspect, download, or version on their own, such as a report, a patch, or an itinerary. A tool updates either one through the active session, and Genkit streams the changes to the client as they happen. Every agent is already a servable action, so putting one behind an HTTP endpoint is a few lines. The route helpers return descriptors you mount on a standard http.ServeMux, and they wire up the turn endpoint plus the snapshot and abort companions for you. python 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 That same wire protocol is what the client below speaks, so a JavaScript or Go backend serves any client identically. The piece that ties your server and client together is the remote agent. remoteAgent returns a handle with the same chat interface as a local agent, so the code that drives an agent in your backend tests is the code that drives it from the browser. There is no separate request and response protocol to design, and no streaming format to invent. We are launching a JavaScript client, so a web frontend can talk to the same agent endpoint. The following is an example of how to connect to a remote agent from a TypeScript frontend. js import { remoteAgent } from 'genkit/beta/client'; const agent = remoteAgent