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 can keep conversation continuity in one of two ways:

Server-managed state means the agent has a store (for example FileSessionStore or InMemorySessionStore). The server persists messages, custom state, and artifacts between turns. Clients continue the chat by sending a session_id (loads latest state) or snapshot_id (loads exact state). Use this for long-running chat applications, multi-turn generators, background detached tasks, or any workflow where client payload size should remain small.

Client-managed state means the agent has no store. The server returns the full session at the end of each turn, and the client must echo that state back on the next turn using chat(messages=..., state=..., artifacts=...). Use this when your client app or another system already owns persistence, when you need stateless server deployments, or when you want to encrypt conversation data outside Genkit.

In both modes, AgentChat tracks continuity for you. Server-managed chats carry forward a session_id / snapshot_id. Client-managed chats carry forward messages, custom state, and artifacts.

Session state has three main fields:

  • custom is your application-specific data. Use it for lightweight status variables, preferences, selected options, or list state that tools or the model needs across turns. Keep this payload small as it is serialized in every snapshot and client payload.
  • messages is the accumulated message history. The runtime appends user prompts and model completions automatically.
  • artifacts is a list of structured, versioned outputs generated by the agent or tools. Use artifacts for larger outputs that the user might download or inspect independently.
  • Use custom state for lightweight control parameters and variables that direct the agent’s next model call or UI rendering. Keep it small.
  • Use artifacts for files, patches, or comprehensive reports that the agent produces. Do not store large documents directly in custom state.

Tools and custom agents can update custom state using session.update_custom(). With a state_schema, chat.state, response.state, and streamed chunk.custom come back as that Pydantic model. Genkit automatically diffs the result and streams RFC 6902 JSON Patches to the client mid-stream.

from pydantic import BaseModel
class TaskItem(BaseModel):
id: int
title: str
done: bool = False
class TaskState(BaseModel):
tasks: list[TaskItem] = []
next_id: int = 1
sess = ai.current_session()
assert sess is not None
def mutate(custom: TaskState | None) -> TaskState:
state = custom or TaskState()
next_id = state.next_id
new_task = TaskItem(id=next_id, title='Buy milk')
return TaskState(tasks=[*state.tasks, new_task], next_id=next_id + 1)
await sess.update_custom(mutate)

Keep custom state serializable, and provide the matching state_schema when defining your agent so the typed state is validated at load time.

Add a store when defining your agent to enable server-managed persistence:

from genkit.agent import FileSessionStore
store = FileSessionStore('.sessions')
weather_agent = ai.define_agent(
name='weatherAgent',
model='googleai/gemini-flash-latest',
system='You are a helpful weather assistant.',
store=store,
)

On every successful turn, the store saves a completed snapshot capturing the conversation’s exact state. If a turn fails, the resume handle stays on the last successful snapshot so the next turn does not continue from a broken partial state.

For store choices, see Session stores.

Read a snapshot directly by ID or fetch the latest snapshot in a session:

snapshot = await weather_agent.get_snapshot(snapshot_id='snapshot-123')
latest = await weather_agent.get_snapshot(session_id='session-123')
StatusMeaning
pendingA detached background invocation is still running.
completedThe snapshot captures a settled, resumable state.
failedThe invocation failed. Error details are stored on the snapshot.
abortedThe detached invocation was canceled.
expiredA pending snapshot heartbeat went stale, so the background worker is presumed dead.

Only completed snapshots are valid resume points. Other statuses are useful for UI progress indicators and background tracking.

To continue a server-managed conversation from the latest leaf, pass the session_id:

chat = weather_agent.chat(session_id='user-session-123')
await chat.send('What is the weather in Tokyo?')

To branch from a specific historical point, pass the snapshot_id (or use load_chat(snapshot_id=...)):

branch = await weather_agent.load_chat(snapshot_id='snapshot-abc-456')
await branch.send('Assume the user changed their mind.')

If your agent does not use a server store, capture messages, custom state, and artifacts yourself, then pass them back:

chat = weather_agent.chat()
res = await chat.send('My name is Ada. Remember it.')
messages, state, artifacts = chat.messages, chat.state, chat.artifacts
resumed = weather_agent.chat(messages=messages, state=state, artifacts=artifacts)
await resumed.send('What is my name?')

When custom state changes during a turn, the runtime streams incremental RFC 6902 JSON Patch chunks. AgentChat applies them automatically, yielding the updated state on chunk.custom and chat.state.

turn = task_agent.chat().send_stream('Add buy milk to my list.')
async for chunk in turn.stream:
if chunk.custom is not None:
update_todo_list_ui(chunk.custom)

Record independent artifacts from tools or custom agents using the active session:

from genkit import Part, TextPart
from genkit.agent import Artifact
sess = ai.current_session()
assert sess is not None
await sess.add_artifacts([
Artifact(
name='report.md',
parts=[Part(root=TextPart(text='# Research Report\nThis is the content.'))],
)
])

Artifacts with identical names overwrite earlier ones, while unnamed artifacts are appended to the session.

Use state_transform and chunk_transform to redact or reshape what leaves the server before it reaches a client:

from genkit.agent import SessionState
def redact_state(state: SessionState) -> SessionState:
# Drop secrets before the client sees them.
return state
agent = ai.define_agent(
name='supportAgent',
model='googleai/gemini-flash-latest',
system='Help customers.',
state_transform=redact_state,
)