StateChangeNotification over WebSocket every time a tracked process’s state changes. The Python SDK delivers these events through StateStream — an async iterator you consume inside an async for loop. This lets you react to application state changes in real time with sub-100 ms latency.
When to Use Streaming vs. Polling
Use streaming (session.watch())
When you need sub-second reaction to state changes, are building a monitoring loop, or want to trigger an action the moment a condition changes.
Use polling (session.refresh())
When you only need periodic reads (e.g. every 5 seconds) or are running a one-shot agent task that does not need to react to changes live.
Opening a Stream
Via Session (Recommended)
Usesession.watch() for the most ergonomic experience. It automatically binds the stream to the session’s PID:
Via Client (Raw Stream)
Useclient.watch(pid=...) when you do not have a session object, or when you need a raw stream for a specific PID:
StateChangeNotification Fields
OS process ID of the application that changed.
Stable semantic app identifier, e.g.
"gmail-chrome-12847".Monotonically increasing counter. A gap between consecutive sequence numbers means one or more events were dropped (queue overflow or reconnection).
The state keys that changed since the previous notification, e.g.
["inbox.unread_count", "inbox.emails[0].is_read"].Identifier for the state capture that produced this notification.
Identifier for the previous capture, enabling diff lookups.
UTC timestamp of the state capture that produced this event.
Refreshing State After Events
When you receive aStateChangeNotification, session.state is not automatically updated unless you pass auto_refresh_on_event=True. You control when to refresh:
Filtering by Changed Keys
React only to the parts of state you care about by inspectingchanged_keys before doing any work:
Reconnection and Sequence Gaps
By default,StateStream reconnects automatically if the WebSocket drops. To detect reconnections, watch for gaps in event.sequence:
auto_reconnect=False to receive a NSPConnectionError instead of a transparent reconnect when the connection drops.
Queue Configuration
Events are buffered in an internal asyncio queue. If your consumer is slower than the daemon’s emit rate, the oldest events are dropped and reflected as sequence gaps:Timeout and Cancellation
Limit how long you watch usingasyncio.timeout (Python 3.11+):
Watching Multiple Apps Simultaneously
Open one stream per session and run them concurrently withasyncio.gather:
Low-Level: open_state_stream(pid, client)
For advanced use cases, use open_state_stream() directly to obtain a StateStream without a session:
