Skip to main content
The NSP daemon broadcasts a StateChangeNotification over WebSocket every time a probe cycle detects that an application’s state has changed. The Python SDK wraps this in an async generator — you iterate over it with async for and receive events the moment they arrive, without any polling. Streaming is the right approach when your agent needs to react quickly to changes, build monitoring loops, or trigger actions as soon as a condition appears in state.

Basic streaming with session.watch()

session.watch() is the recommended entry point. It returns an async context manager that yields StateChangeNotification objects.
streaming_basic.py

StateChangeNotification fields

Every event the stream yields is a StateChangeNotification with these fields:

Refreshing state on each event

Events tell you which keys changed, but they don’t carry the new values. Call session.refresh() inside the loop to pull the updated state, or enable auto_refresh_on_event=True to have the SDK do it automatically.

client.watch(pid) vs session.watch()

Both entry points subscribe to the same underlying WebSocket endpoint. The difference is context: session.watch() knows the session’s PID and app name, and can optionally keep the session’s cached state updated. client.watch(pid) is a raw stream — use it when you need to stream events for a PID without a full session object.

Handling disconnects and reconnection

By default, session.watch() reconnects automatically if the WebSocket connection drops. You can detect reconnections by checking for gaps in event.sequence.
reconnect_detection.py
For scenarios where you need explicit control over reconnection timing, disable auto-reconnect and implement exponential backoff:
manual_reconnect.py

Sequence gaps

event.sequence is a monotonically increasing integer. If you observe event.sequence > last_sequence + 1, one of the following occurred:
  • The internal event queue overflowed and the oldest events were dropped (see queue configuration below).
  • The WebSocket connection dropped and was re-established, causing the daemon to start a new sequence.
  • The daemon restarted.
Treat sequence gaps as a signal to call await session.refresh() and re-read current state rather than attempting to reconstruct what changed during the gap.

Watching multiple apps simultaneously

Combine asyncio.gather with per-session watch tasks to monitor several apps at the same time.
watch_all.py

Queue configuration

Events are buffered in an internal queue. If your consumer processes events more slowly than the daemon emits them, the oldest events are dropped to make room. Increase max_queue if your agent does significant work inside the loop.
Watch events do not count against your action rate limit. You can keep a stream open indefinitely without affecting how many actions per second your agent can execute.

Bounding a watch session by time

Use asyncio.timeout (Python 3.11+) to stop watching after a fixed duration:
bounded_watch.py