Skip to content

Sessions and state

In Genkit, agent state includes message history, custom application state, artifacts, session identity, and snapshot lineage. Choose the state strategy before building the client because it determines who owns continuity between turns.

Genkit agents keep continuity in one of two ownership models.

Server-managed state uses aix.WithSessionStore(store). The server persists snapshots and callers continue with aix.WithSessionID or aix.WithSnapshotID. Choose this for durable conversations, background execution, snapshot reads, branching, or clients that should not hold conversation history.

Client-managed state omits a store. The caller receives AgentOutput.State and sends it back with aix.WithState. Choose this when your service already stores session data, when you need stateless Genkit workers, or when another system controls encryption and retention.

Server-managed state is the default recommendation for user-facing conversational apps. Client-managed state is useful when you need tighter control over where state is stored and how it moves between services.

Session state contains messages, typed custom state, artifacts, and the framework-owned session ID:

import aix "github.com/firebase/genkit/go/ai/exp"
type SessionState[State any] struct {
Artifacts []*aix.Artifact `json:"artifacts,omitempty"`
Custom State `json:"custom,omitempty"`
Messages []*ai.Message `json:"messages,omitempty"`
SessionID string `json:"sessionId,omitempty"`
}
  • Custom is your typed application state. Use it for compact data the agent needs across turns, such as workflow status, selected records, preferences, task state, or progress.
  • Messages is conversation history.
  • Artifacts contains generated outputs that the app or user may inspect independently.

Custom and Artifacts both live in session state, so choose by role:

  • Use custom state for the compact control and UI data that drives the next turn, such as workflow status, selected records, preferences, task state, or progress. It rides in every snapshot, so keep it small.
  • Use artifacts for generated outputs the user may inspect, reuse, or version independently, such as reports, files, patches, or documents.

Do not put large generated documents into Custom just because they serialize to JSON; make them artifacts.

Prompt-backed tools and custom agents can update typed custom state through the active session. In a custom agent, call sess.UpdateCustom:

sess.UpdateCustom(func(state TravelState) TravelState {
state.Status = "Checking weather"
state.LastCity = city
return state
})

The runtime streams custom patches as state changes. AgentConnection.Receive applies those patches, and AgentConnection.Custom() returns the current custom state observed by the connection.

WithSessionStore switches the agent to server-managed state. The store must support snapshot reads and writes. Background detach and abort support also require the store to implement SnapshotSubscriber.

import (
"github.com/firebase/genkit/go/ai/exp/localstore"
genkitx "github.com/firebase/genkit/go/genkit/exp"
)
store, err := localstore.NewFileSessionStore[TravelState]("./.genkit/snapshots/travel")
if err != nil {
// Fails if the snapshot directory cannot be created or is not writable.
log.Fatalf("open travel store: %v", err)
}
agent := genkitx.DefineAgent(g, "travel",
aix.InlinePrompt{
ai.WithModelName("googleai/gemini-flash-latest"),
ai.WithSystem("You are a travel assistant."),
},
aix.WithSessionStore(store),
)

FileSessionStore persists snapshots as JSON files and is safe for concurrent use. It can also notify status subscribers when snapshots change, which enables abort handling and efficient wait loops.

For store options and custom store implementation guidance, see Session stores.

Use the typed agent methods when reading snapshots locally:

snap, err := agent.GetSnapshot(ctx, snapshotID)
latest, err := agent.GetLatestSnapshot(ctx, sessionID)

These methods apply WithStateTransform and read-time shaping. Reading agent.Store() directly returns raw, untransformed state.

StatusMeaning
pendingA detached invocation is processing queued inputs.
completedThe snapshot captures settled state and can be resumed.
failedThe invocation failed and the snapshot contains structured error details.
abortedThe detached invocation was canceled.
expiredA pending snapshot heartbeat is stale. This status is computed on read and is not written back to the store.

Synchronous turns write completed snapshots. A failed turn does not make its partial state the normal continuation point. The output reports the last-good state or snapshot ID.

Use WithSessionID when the caller tracks a conversation:

out, err := agent.RunText(ctx, "What about Paris?",
aix.WithSessionID[TravelState](previous.SessionID),
)

Use WithSnapshotID to branch:

rainPlan, err := agent.RunText(ctx, "Assume it rains.",
aix.WithSnapshotID[TravelState](base.SnapshotID),
)

Use WithState for client-managed agents:

out, err := agent.RunText(ctx, "Add buy milk.",
aix.WithState(previous.State),
)

An empty session ID is rejected. WithState cannot be combined with snapshot or session options.

WithStateTransform redacts or reshapes session state on the way out to clients. It applies to snapshot reads and client-managed output, not to persisted raw state or the agent function’s internal view.

agent := genkitx.DefineAgent(g, "support",
aix.InlinePrompt{
ai.WithSystem("Summarize support cases."),
},
aix.WithSessionStore(store),
aix.WithStateTransform(func(ctx context.Context, state *aix.SessionState[SupportState]) (*aix.SessionState[SupportState], error) {
state.Custom.InternalNotes = ""
return state, nil
}),
)

WithStreamTransform[State] runs on every streamed AgentStreamChunk at the wire boundary. Return nil to drop a chunk. Return an error to fail closed, which prevents unshaped data from reaching the client.

Prefer WithStateTransform for custom state redaction because the runtime diffs transformed state before emitting custom patches.

Prompt-backed and custom agents can update state through the active session. Custom patches are streamed automatically when custom state changes. AgentConnection.Receive applies those patches and AgentConnection.Custom() returns the live custom state observed so far.

Artifacts are named collections of parts. Responder.SendArtifact both streams the artifact and records it in the session, so the artifact is available in the final output or snapshot.