Python

Open interrupts, stream live sessions and receive human decisions from your Python agents - by polling or webhooks.

When an AI agent reaches an action that needs sign-off, it opens an interrupt - a request for a human decision. A teammate handles it in the inbox, and your code continues with the outcome. In between, a session streams the agent's conversation to Live View so operators can watch it as it happens. vigilator-py-sdk is the official Python SDK for both:

Open interrupts

A typed client that creates interrupts from your agent and fetches their outcome, with retries and typed errors built in.

Stream live sessions

Start a session when a run begins, append messages as the conversation grows, and end it when the run completes.

Receive events

A webhook handler that verifies signatures and turns interrupt and session deliveries into typed events - no polling loop required.

Fully typed

Every request, response and event is a Pydantic model generated from the live API spec, so your editor knows every field.

Requirements

Python 3.10 or newer. The SDK's only dependencies are httpx and pydantic.

Installation

uv add vigilator-py-sdk

Quick Start

Create a client

Grab an API key for your organisation from the dashboard and pass it to Client. It is sent as the x-api-key header on every request, and scopes everything the SDK does to that organisation.

from vigilator_py_sdk import Client

client = Client(key="your-api-key")

Open an interrupt

When your agent needs a human, create an interrupt. The action requests are its decision surface - the concrete things a reviewer will approve, edit, reject or respond to - so every interrupt needs at least one:

agent.py
from vigilator_py_sdk import ActionRequest, AllowedDecision, InterruptsPostRequest

interrupt = client.create_interrupt(
    InterruptsPostRequest(
        title="Send onboarding email",
        description="The agent wants to email a new customer.",
        externalId="run_42",  # your own correlation id, e.g. the agent run
        actionRequests=[
            ActionRequest(
                name="send_email",
                args={"to": "customer@example.com"},
                allowedDecisions=[AllowedDecision.approve, AllowedDecision.edit, AllowedDecision.reject],
            )
        ],
    )
)

A plain question to a human is just an action request whose name is the question, with allowedDecisions=[AllowedDecision.respond].

Learn the outcome

Poll get_interrupt with the id you got back - answered flips true once every action request is decided:

result = client.get_interrupt(interrupt.id)
if result.answered:
    for request in result.actionRequests:
        print(request.name, request.decision, request.decidedByName)

Or skip polling entirely and let Vigilator call you - see handling webhooks below.

Acting on decisions

Each decided action request tells you what the reviewer chose and everything you need to act on it:

DecisionMeaningWhat to read
approveRun the action exactly as proposed.args as you sent them
editRun the action with the reviewer's changes.editedArgs replaces your args
rejectDo not run the action.responseText may hold the reason
respondThe reviewer answered your question.responseText holds the answer

Every decision also carries decidedByName and decidedAt, so your agent can log who made the call and when.

Client behaviour

The client retries requests that fail with 429/502/503/504 or a connection error - 5 attempts with exponential backoff by default, tunable via the constructor. Use it as a context manager to release the connection pool when you are done:

with Client(key="your-api-key") as client:
    ...

Live View sessions

Interrupts capture the moments an agent stops to ask; a session covers everything in between. Register one when a run starts, stream the conversation as it progresses, and end it when the run completes - operators watch the transcript grow in Live View and can step in when something looks off. Sessions are standalone: they are not linked to interrupts, so you can monitor agents that never raise one.

Start a session

Give the session the agent's name (that is how it appears in Live View) and, optionally, the opening context - typically the user prompt that started the run. Use external_id for your own correlation id, such as the run or thread id in your agent framework.

agent.py
from vigilator_py_sdk import Message, MessageType

session = client.start_session(
    "billing-agent",
    external_id="thread_42",
    messages=[Message(type=MessageType.human, content="Refund order #42")],
)

Append messages as the conversation grows

Call append_session_messages whenever the agent produces or receives messages - each call adds up to 200 messages in conversation order. Every call also marks the session as active, so an agent that keeps appending is never mistaken for a crashed one.

client.append_session_messages(
    session.id,
    [
        Message(type=MessageType.ai, content="Looking up order #42.", name="billing-agent"),
        Message(type=MessageType.ai, content="Refund issued.", name="billing-agent"),
    ],
)

Message is the same model you attach to interrupts. type is human or ai; name labels the speaker, and toolCalls, additionalKwargs and responseMetadata carry provider extras such as tool invocations.

End the session

When the run completes, end the session. Ending an already-ended session is a no-op that returns the current state, so it is safe to retry - and safe to call even if a watcher disconnected the session first.

client.end_session(session.id)

A session that goes quiet for longer than your organisation's session timeout is ended automatically, so a crashed agent never lingers in the queue. If your run may pause for long stretches - waiting on an interrupt, say - end the session before the pause and start a new one afterwards, or make sure the timeout in Integrations → Live View covers it.

Session state

Each call returns the session as the API sees it:

FieldMeaning
idPass it to append_session_messages and end_session.
statusSessionStatus.active or SessionStatus.ended.
startedAt, endedAtWhen the session opened and closed - endedAt is None while active.
lastActivityAtBumped on every append; Live View shows a session as quiet when this goes stale.
messages, messageCountThe most recent messages (capped at 200) and the true total.
assignee, escalatedThe member watching the session, and whether it was raised to the escalated queue.

Monitored time counts towards your organisation's agent hours, recorded when each session ends. Once a free organisation's monthly allowance is spent, start_session raises UsageLimitError until it resets.

Handling webhooks

Vigilator signs webhook deliveries (Svix / Standard Webhooks). WebhookHandler verifies the signature, parses the payload into a typed event, and dispatches it to registered callbacks. Create an endpoint in the dashboard under your organisation's integrations, copy its signing secret, and register callbacks for the events you care about:

from vigilator_py_sdk import InterruptAnsweredEvent, WebhookHandler

webhooks = WebhookHandler(secret="whsec_...")  # the endpoint's signing secret

@webhooks.on("interrupt.answered")
def resume_agent(event: InterruptAnsweredEvent) -> None:
    for request in event.data.action_requests:
        ...  # act on approve / edit / reject / respond

The handler is framework-agnostic - pass the raw request body and the request headers from any web framework:

from fastapi import FastAPI, HTTPException, Request, Response
from vigilator_py_sdk import WebhookVerificationError

app = FastAPI()

@app.post("/webhooks/vigilator")
async def vigilator_webhook(request: Request) -> Response:
    try:
        webhooks.handle(await request.body(), request.headers)
    except WebhookVerificationError:
        raise HTTPException(status_code=401) from None
    return Response(status_code=204)

Verify the raw body

Signature verification runs over the request body exactly as it arrived. Re-serializing parsed JSON breaks the signature - always pass the raw bytes.

Events

EventModelSent when
interrupt.createdInterruptCreatedEventAn agent opened a new interrupt.
interrupt.answeredInterruptAnsweredEventEvery action request on an interrupt was decided.
interrupt.escalatedInterruptEscalatedEventAn interrupt was raised to the manager review queue.
session.startedSessionStartedEventAn agent registered a live session.
session.endedSessionEndedEventA live session ended - by the agent, a watcher, or the session timeout.
session.actionSessionActionEventA watcher fired a custom action against a live session.
anything newerUnknownEventThe event type postdates the installed SDK version.

Every event shares the same envelope: type, timestamp (the moment the lifecycle edge happened, not the delivery time) and data. Event models use snake_case attributes for the camelCase payload keys - event.data.action_requests, request.decided_by_name, and so on. Event types newer than the installed SDK parse into UnknownEvent instead of raising, so handlers keep working across SDK versions.

The session events are how Live View talks back to your agent. SessionEndedEvent carries a reason (SessionEndReason.agent, timeout or manual), so you can tell a run that finished on its own from one a watcher disconnected or one that timed out. SessionActionEvent carries the action name a watcher pressed - a custom action you defined - and triggered_by, the member who pressed it; what the action means is up to your agent:

from vigilator_py_sdk import SessionActionEvent, SessionEndedEvent, SessionEndReason

@webhooks.on("session.action")
def on_action(event: SessionActionEvent) -> None:
    if event.data.action == "pause":
        pause_run(event.data.external_id)  # your correlation id from start_session

@webhooks.on("session.ended")
def on_ended(event: SessionEndedEvent) -> None:
    if event.data.reason is SessionEndReason.manual:
        stop_run(event.data.external_id)  # a watcher pulled the plug

Return a 2xx quickly and offload slow work - failed deliveries are retried on a backoff schedule. For local testing and delivery tooling, see the webhooks guide.

API Reference

Client

Client(
    key: str,
    base_url: str = "https://api.vigilator.dev",
    retries: int = 5,
    backoff_factor: float = 0.5,
    timeout: httpx.Timeout | float = 5.0,
)

Prop

Type

create_interrupt(interrupt)

Creates an interrupt and returns it as an InterruptsPostResponse.

interruptAn InterruptsPostRequest: title, description, at least one ActionRequest in actionRequests, and optionally classificationId, externalId (your own correlation id, e.g. an agent run id) and messages (the conversation leading up to the interrupt).
ReturnsThe created interrupt, including its id - keep it to poll for the outcome.
RaisesUsageLimitError, PlanRequiredError, AddonRequiredError, WorkspaceLimitError on quota errors; APIError otherwise; VigilatorConnectionError if the API could not be reached.

get_interrupt(interrupt_id)

Fetches a single interrupt by id as an InterruptsIdGetResponse, including any decisions taken on its action requests.

interrupt_idId of the interrupt to fetch.
ReturnsThe interrupt as returned by the API.
RaisesNotFoundError if the interrupt does not exist; APIError on other error responses; VigilatorConnectionError if the API could not be reached.

start_session(name, *, external_id=None, messages=None)

Starts a live session and returns it as a SessionsPostResponse.

nameThe agent's name as shown in Live View, e.g. "billing-agent". 1-100 characters.
external_idYour own correlation id for the run, e.g. the run or thread id in your agent framework. Echoed back in every session webhook event.
messagesThe opening context as a list of Message, e.g. the user prompt that started the run. At most 200.
ReturnsThe created session, including its id - keep it for the append and end calls.
RaisesUsageLimitError when the organisation's agent hours are spent, PlanRequiredError / AddonRequiredError / WorkspaceLimitError on other quota errors; APIError otherwise; VigilatorConnectionError if the API could not be reached.

append_session_messages(session_id, messages)

Appends messages to a running session and returns it as a SessionsIdMessagesPostResponse.

session_idId of the session, as returned by start_session.
messagesThe messages to append, in conversation order, as a list of Message. 1-200 per call.
ReturnsThe session, including its most recent messages and the total messageCount.
RaisesNotFoundError if the session does not exist; APIError with code == "CONFLICT" (409) if the session has already ended; VigilatorConnectionError if the API could not be reached.

end_session(session_id)

Ends a session and returns it as a SessionsIdEndPostResponse. Ending an already-ended session is a no-op that returns the current state, so the call is safe to retry.

session_idId of the session, as returned by start_session.
ReturnsThe ended session.
RaisesNotFoundError if the session does not exist; APIError on other error responses; VigilatorConnectionError if the API could not be reached.

Input that breaks the API contract - an empty name, an empty message batch, more than 200 messages - raises pydantic.ValidationError before any request is sent.

close()

Closes the underlying HTTP connection pool. Called automatically when the client is used as a context manager.

WebhookHandler

WebhookHandler(secret: str, tolerance: float = 300.0)

Prop

Type

on(event_type)

Decorator that registers a callback for one event type. Callbacks receive the typed event. Unknown types are allowed, so callbacks can target event types newer than the installed SDK.

handle(body, headers)

Verifies a delivery, parses it, invokes the callbacks registered for its type (synchronously, in registration order), and returns the typed event.

construct_event(body, headers)

Verifies a delivery and returns the typed event without dispatching - use this if you prefer routing events yourself.

verify(body, headers)

Verifies the signature only, without parsing the body.

All three verification methods raise WebhookVerificationError on missing or malformed Svix headers, a timestamp outside the tolerance window, a signature mismatch, or a verified body that is not valid JSON.

Errors

All SDK errors inherit from VigilatorError:

ErrorMeaning
VigilatorConnectionErrorThe API could not be reached (network failure, timeout).
APIErrorThe API responded with an error status. Carries status, code, message and data.
UsageLimitError402 USAGE_LIMIT_REACHED: the organisation's usage quota is exhausted.
PlanRequiredError402 PLAN_REQUIRED: the feature requires a paid plan.
AddonRequiredError402 ADDON_REQUIRED: the feature requires an add-on.
WorkspaceLimitError403 WORKSPACE_LIMIT_REACHED: the workspace limit has been reached.
NotFoundError404 NOT_FOUND: the requested resource does not exist.
WebhookVerificationErrorA webhook delivery could not be verified.

UsageLimitError through NotFoundError subclass APIError, so except APIError catches them all.

On this page