Workflow.session() returns a Session, a handle on one session of a workflow revision. It reads the session’s event
stream, lists its listeners
and switches them, and lists the executions a listener started. Everything goes through the revision’s data channel, so
the SDK opens no WebSocket: following the stream is polling.
dict records, as everywhere in the SDK. The SDK checks no event type: the taxonomy is the
platform’s, so a new type needs no SDK release.
The session handle
workflow.session(session, *, verify=True) takes a session id or alias.
It never creates a session, unlike
execute(session=...).
session.refresh() reads the record again. A handle that holds a record sends its id on every read, so one opened
by alias keeps reading the same session even if that session is deleted and the alias is reused. An unverified handle
sends the name it was given.
A finished run leads back to its session: run.session is the Session for the execution’s session_id, and
run.source says what started it.
Reading events
session.events(filter=None, *, after=None, before=None, ascending=True, limit=None, page_size=100, follow=False, interval=2.0, timeout=None) returns a lazy iterator over the stream.
Each request is one
list_session_events; the iterator follows the platform’s cursor until it is null, and never
stops on a short page, because the filter runs on the server and a page can hold fewer matches than page_size while
more remain. Arguments the SDK can check without a request are checked before the first one: a bad page_size,
limit, after, before or filter is ValueError. The filter’s own grammar is the server’s.
session.latest() returns the newest event, or None for an empty stream, in one request. session.event(event_id)
returns one event and raises NotFound when there is none.
Following
follow=True keeps the iterator open. When the range is exhausted it sleeps interval seconds and reads again, until
the caller closes the iterator, limit events were yielded, or timeout seconds have passed. The deadline ends the
follow whether or not anything remains to read. Following needs ascending=True and no before; either combination is
ValueError.
after: <last id> alone can skip a row that commits late. Each pass of a follow therefore re-reads from a bound a
minute behind the newest id it knew before the previous pass began, and drops the ids it already yielded. See
Settle time. Two consequences:
- Events arrive in id order except a late one, which arrives when it is found, after events with larger ids. No event is yielded twice.
- A quiet stream re-reads the same last minute every
interval: one small listing a poll. Raiseintervalfor a slow reader. A clock skew between the platform’s producers larger than the window can still hide a row.
PermissionDenied.
Boundary ids
boundary_id(moment) returns the id that sorts before every event of moment’s microsecond, so after=boundary_id(t)
starts at t. It takes epoch seconds or an aware datetime; a naive datetime is ValueError, because the SDK never
guesses a zone.
execution.ended
run.wait() ends on the run’s status, not on this event. To read the event itself:
status, and its route and error when there are any. The platform writes it best-effort, so it
is usually there but not guaranteed; the run’s status is the authority.
Listeners
session.listeners(*, binding=None) returns the session’s listeners as the platform reports them: every declared
listener of the revision, then the connection listeners open on the session. binding="declared" or "connection"
keeps only that kind, and anything else is ValueError.
definition_id, alias, description, enabled (the effective switch), session_enabled
(present only when the session overrides the definition), definition, counts, last_outcome, outcomes (the
newest 20), in_flight and, when a batch is collecting, pending. A connection entry carries listener_id,
connection_id, filter, batch and created_at. See Listeners
for what the outcomes mean.
session.set_listener(listener, enabled) switches a declared listener on or off for this session and returns its entry.
listener is an alias or definition id and enabled must be a bool; anything else is TypeError or ValueError
before any request. An unknown listener is NotFound.
listeners() to learn the state, or set it again, which is idempotent.
session.executions(listener=None) yields the session’s executions as Execution objects, newest first, following the
platform’s cursor to its end. With listener (an alias or definition id) it yields only the runs that listener
started; their run.source["kind"] is listener.
PermissionDenied, unless it is shaped like an id: a
deleted listener’s id still lists the executions it started.
Creating, uploading and editing a listener definition is blobhub-cli’s job,
as for workflow definitions. A connection listener needs a WebSocket, which the SDK does not open; use the
WebSocket listener protocol.
Errors
See also
- Session Events — the record, its source, the types and the settle time.
- Event filter — the clauses
filtertakes. - Listeners — the body, outcomes and the switch.
- Workflow —
execute,waitand the execution log.

