Sessions
Give an action a durable identity across many events, with ordered turns, an end, a clock, approvals, and held turns.
Without a session, each execution of an action stands alone: it sees one event and remembers nothing. Some work is about one subject over time instead. Watching a newly opened account for its first 30 days means reviewing every entry posted to that account, in order, knowing what was already reviewed and which findings are open, and stopping when the 30 days are over.
A session gives an action that continuity. Each session has an ID, an input fixed when it starts, state that the action carries from turn to turn, an ordered history of everything delivered to it, and a lifecycle. The same action and session ID always address the same session.
The Sessions reference lists every field and rule. This page uses one running example: an early-fraud-detector agent that watches each new account for 30 days.
The session block
An action's session block declares how its sessions are identified, when they end, and the clock they keep; its triggers declare which events start or reach a session.
triggers: [
{
name: "opened"
event: "event.type == \"account.inserted\""
session: { start: { input: { account_id: "string(event.data.account_id)" } } }
}
{
name: "entry"
event: "event.type == \"entry.inserted\""
session: { resume: { id: "string(event.data.account_id)" } }
}
]
session: {
inputSchema: {
type: "object"
properties: { account_id: { type: "string", format: "uuid" } }
required: ["account_id"]
}
id: "string(session.input.account_id)"
ends: "session.started_at + duration('720h')"
schedule: [{ name: "morning", cron: "0 9 * * 1-5", timezone: "America/New_York" }]
turns: { on: ["entry", "morning"] }
}
| Field | What it decides |
|---|---|
inputSchema | The shape of each session's input, fixed when it starts. Here, the account it watches. |
id | The session ID, computed from the input. One account, one session. |
ends | When each session ends. Here, 720 hours after it started. See The end of a session. |
schedule | The session's own clock. See Schedules. |
turns | When the session works. See Turns. |
Each session keeps the action version it started with. Updating the definition changes the triggers, schemas, tools, end, and clock of sessions started after the update, never of those already open.
Session roles
With a session block, every trigger has a session role: it either starts a session or resumes one.
| Role | What a matching event does |
|---|---|
start: {input} | Maps the event to the session's input, computes the ID, and finds the open session with that ID or creates it. A closed session ID never starts again. |
resume: {id} | Delivers the event to the open session with that ID. It never creates a session. |
resume: {input} | Delivers the event to every open session whose input has a field equal to a value from the event, such as every session watching one customer. |
In the example, account.inserted starts the account's session, and each entry.inserted for that account resumes it. An entry that arrives before its account's session exists waits up to 24 hours for the session to start.
An event that fails to start or resume a session, such as one whose mapped input fails inputSchema, runs nothing. It is kept as an intake record: a failed execution that names the trigger and the step that failed. Fix the definition, then rerun the record to deliver the event.
Turns
A session works in turns. A turn is one pass of the action's handler over a set of the session's pending inputs; for an agent, it is the agent's whole loop up to its outcome. Turns run one at a time, and each commits its new state, its output, and the consumption of its inputs together, so every turn builds on what the turn before committed. See Session turns.
Events delivered to a session wait in it, in the order they arrived, until a turn takes them. session.turns decides when that happens:
| Setting | Use it to |
|---|---|
on | Name the triggers and schedule entries whose inputs start a turn. Other inputs wait for the next turn. In the example, the opening itself starts no turn: the first entry, or the next morning firing, starts a turn that reviews the opening with it. |
window | After the session has been idle, wait this long after the first input before starting a turn, so a burst of entries is one turn instead of many. |
maxInputs, maxBytes | Bound how much one turn takes. An agent's turn takes every pending input up to 1,000 by default; a Starlark action's, one. |
An agent's turn shows each input to the model whole by default. A trigger's view shows only the fields the agent needs, such as an entry's amount, direction, and time, and the agent can still read the whole event.
The lifecycle
account.inserted
|
v
+---------+ turn reports OPEN +---------+
| OPEN | <--------------------- | turn |
| | ---------------------> | running |
+---------+ input starts a turn +---------+
| ^ | |
| | respondToSession | +--> COMPLETED (final turn, or a turn
| | | that completes it)
| +------------ WAITING <-----------+ approval needed, or turn held
| |
| +-----> FAILED (turn failed for good)
|
+--> COMPLETED, reason "ended" (end reached, nothing pending)
+--> CANCELLED (closeSession, from any open status)
| Session status | Meaning | Final |
|---|---|---|
OPEN | Accepting inputs. A turn runs when an input starts one. | no |
WAITING | Waiting for a person: an approval or a held turn. New inputs wait, in order. | no |
COMPLETED | A turn completed it, or its end passed with nothing pending. | yes |
FAILED | A turn failed for good: after its retries, or with an error that is not retried. Its inputs remain, unconsumed, and the reason names the execution. | yes |
CANCELLED | An operator closed it. | yes |
No input is delivered to a session in a final status. Each turn has a status of its own: RUNNING, COMMITTED, FAILED, HELD, or CANCELLED.
The end of a session
session.ends makes "30 days" a rule the platform enforces rather than an instruction to the model. It is evaluated when the session starts, and the result is the session's endsAt. See Session end.
- Before the end, no turn may complete the session. An agent is offered only
OPEN. - At the end, a session with pending inputs gets a final turn at once. It takes every pending input, whatever
turns.onnames, and must complete the session with a reason. - With nothing pending, the session completes with the reason
ended, and no turn runs. A quiet account costs nothing. - Nothing goes unreviewed. A turn is final only when it takes every pending input; if
maxInputsleaves some, the next turn continues.
Without ends, a session completes only when a turn reports COMPLETED or an operator closes it.
Schedules
session.schedule gives each session its own clock: up to 8 named entries, each a cron expression in a timezone or an every period from the session's start. In the example, the morning entry gives each account's session a weekday review at 9:00 in New York, even on a day with no entries. See Session clock.
A firing is an input to that session alone, of kind SCHEDULE. It starts a turn at once when turns.on names its entry or is omitted, and never waits for a window. A turn takes at most one firing of each entry, the latest due, and missed counts the firings it passed over, for example while the session was waiting for an approval. No entry fires at or after the session's end.
Use a schedule trigger, not a session schedule, for work that is about no particular subject, such as a nightly report.
Approvals
Some tool calls should not happen without a person. Give a tool reference approval: REQUIRED, and before each call to it the agent's turn commits with the session WAITING. Everything the turn did before the call commits with it. See Approvals.
tools: [
{ name: "get_account_activity", input: { account_id: "string(session.input.account_id)" } }
{ name: "notify_human_ops", approval: REQUIRED }
]
The turn's execution lists the paused call in invocation.approvals, with the tool, the version it would run, and its input. Answer with respondToSession and response.approval: APPROVE, optionally with replacement input, or DENY, each with an optional reason. The answer is delivered as a COMMAND input in a turn of its own, and the agent continues from where it paused: an approved call runs, and a denied call is reported to the agent with the reason. Answering requires the RESPOND policy action on the agent's action record, and the answer is recorded with the responder.
Only an agent with a session can require approval. A gated tool cannot be called from programmatic tool calling. In preview mode, a gated call runs at once as if approved, and is marked grantedInPreview so the record shows no person approved it.
Held turns
A turn that the model declines to continue, or that never reports a valid outcome, does not fail the session. It is held: nothing it did commits, its transcript is kept, and the session waits with the state the turn before left. See Held turns.
The turn's execution has invocation.hold, with the cause, REFUSED or UNFINISHED, and, for a refusal, what the provider reported. Read its transcript, then answer with respondToSession and response.hold:
RETRYruns the held turn's inputs again, with your optionalnoteshown to the agent.SKIPgoes on without them, with no model call. A turn that resumed from approval responses cannot be skipped, since the responses cannot be given again: retry it, or close the session.
A retried turn runs at the session's pinned version, so fixing the agent's definition helps sessions started after the fix, not the held one.
Inspecting and closing a session
A session reaches everything that happened in it: its state, its turns with each turn's inputs, checkpoint, and execution, its inputs in delivery order, and its history of versions. Read one with session(actionId, id), or list them with sessions(where: ...). See Inspecting and closing sessions.
Closing a session
closeSession(actionId, id, reason) cancels one session. It accepts no further input, a running turn cannot commit, and its history stays readable. Effects already performed are not undone. Close a session to stop one subject's work without touching the others; disable the action to stop new sessions.
Tool sessions
A tool can have a session too. A tool session is call history only: each call names its session with a start mapping over tool.input, and each committed call adds one CALL input to it, with its input and output. It has no state, turns, end, or schedule, and calls never wait for one another. Use it when a tool should see its earlier calls for the same subject, such as a notification tool that must not notify twice about one finding.
tool: {
name: "notify_human_ops"
description: "Notify human operations about a finding."
inputSchema: {
type: "object"
properties: { account_id: { type: "string" }, note: { type: "string" } }
required: ["account_id"]
}
session: { start: { input: { account_id: "tool.input.account_id" } } }
}
An MCP server records every request in a session of its own, grouped by user, host, or your run ID; see MCP sessions.
Next
Memory
Keep facts beyond a session, and let a session search its own earlier turns.
Sessions tutorial
Build the 30-day account watch, step by step.
Sessions reference
Every field, rule, and operation.