Python SDK
The Python SDK has the same surface as the JavaScript SDK; this page shows the Python calls and what differs.
Install
pip install introspection-sdk
pip install 'introspection-sdk[otel]' # feedback, track, identify
pip install 'introspection-sdk[arrow]' # Arrow exportsPython 3.11 or later. The client reads INTROSPECTION_TOKEN; see
Authentication.
Run a task
import asyncio
from introspection_sdk import AsyncIntrospectionClient
from introspection_sdk.schemas.agui import TextMessageContentEvent
async def main() -> None:
async with AsyncIntrospectionClient() as client:
runner = await client.runtimes("support-agent").run(identity={"user_id": "user_123"})
async with runner:
run = await runner.tasks.start(prompt="Summarize my open tickets.")
async for event in run.stream():
if isinstance(event, TextMessageContentEvent):
print(event.delta or "", end="")
follow_up = await runner.tasks.runs.create(
str(run.run.task_id), kind="prompt", prompt={"text": "Now draft the reply."}
)
print(await follow_up.text())
asyncio.run(main())run()takes keyword-onlyidentity(user_id,anonymous_id,conversation_id,tags,metadata),agent_name,ttl_seconds,caller, andscope. Who owns the task: Run a task for an end user.IntrospectionClientis the synchronous twin: drop theawaits, usefor, and close it withclient.shutdown().- In Jupyter,
awaitdirectly in a cell (noasyncio.run), or useIntrospectionClient. kind="steer"redirects a running turn.
tasks.create() takes title, prompt, agent_name, repositories,
metadata, conversation_metadata, idle_timeout_seconds, fork_share_id,
files, and tags; tasks.start(prompt=..., **kwargs) takes the same.
metadata={"model": "<provider>/<model-id>", "thinking_level": "high"}
overrides the root agent’s model for one task.
Continue and control a task
Reattach after a reload
task = await runner.tasks.get(task_id)
run_id = task.metadata.get("active_run_id") or task.metadata.get("latest_run_id")
async for event in runner.tasks.runs.stream(task_id, run_id):
render(event)There is no since cursor: a reattach replays the run, so render
idempotently.
Stream options and errors
stream() takes timeout (default 300.0 s), max_reconnects (5), and
backoff (0.5 s), with the same meaning as the
JavaScript options: when
timeout lapses only your stream raises; the run keeps going. RunFailedError
(do not retry) and StreamIncompleteError (the run settled: read the
conversation) are raised by the stream and by text(). HTTP calls retry twice
(429; 502/503/504 on GET) and time out after 30 seconds per request.
Answer an interrupt
from introspection_sdk import ResumeEntry
resumed = await runner.tasks.runs.resume(
task_id,
resume=[ResumeEntry(interrupt_id=interrupt.id, status="resolved", payload={"approved": True})],
)Take interrupt from event.outcome.interrupts on RUN_FINISHED. Payloads
and the recipe side: Ask the user.
Cancel
await run.cancel() # abort the turn, keep the sandbox warm
await run.cancel({"mode": "drain", "drain_within_seconds": 60})Files
from pathlib import Path
uploaded = await runner.files.upload(file=Path("spec.md"), file_type="upload")
run = await runner.tasks.start(prompt="Summarize spec.md.", files=[{"id": str(uploaded.id)}])files also has create_text, list, get, update, delete, download,
download_stream, and versions. list() filters only by name,
file_type, storage_path, tag, and metadata; for one task’s outputs use
introspection files list --filter.
Shares and tags: Sharing.
Conversations, events, and metrics
async for c in runner.conversations.list(limit=20, order="desc"):
print(c.id, c.cost.usd)
# Tool calls only, e.g. a submit_result tool's arguments (a JSON string)
async for item in runner.conversations.items.list(task_id, operation_name="execute_tool"):
print(item.attributes.gen_ai.tool.name, item.attributes.gen_ai.tool.call.arguments)
async for event in runner.events.list("introspection.feedback", conversation_id=task_id):
print(event.payload.name)
result = await runner.metrics.query({
"view": "conversations",
"metrics": [{"measure": "total_cost_usd", "aggregation": "sum"}],
"from_timestamp": "2026-07-01T00:00:00Z",
"to_timestamp": "2026-07-08T00:00:00Z",
})Every event family is typed, including AnnotationEvent and TrackEvent.
Exports: export_json, export_trajectory, export_arrow (needs [arrow]),
export_stream. A list returns a pager: iterate it, or await it for one page
(pager.page() on the sync client).
Feedback
from introspection_sdk import IntrospectionLogs
logs = IntrospectionLogs(service_name="support-api")
logs.feedback("thumbs_down", conversation_id=task_id, comments="Missed the refund policy", value=0)
logs.track("case_closed", {"source": "web"})
logs.shutdown()previous_response_id anchors feedback to one response; extra keyword
arguments are stored as properties. Name and sentiment rules:
Send feedback.
IntrospectionLogs keeps one token for its lifetime, so give it an API key.
Experiments
client.experiments has list, get, create(document),
update(experiment_id, document), and delete(experiment_id) (sync and
async); documents match
introspection experiments create.
client.experiments(experiment_id) returns a handle with run, start,
end, and cancel.
Environment variables
Same as JavaScript, except
INTROSPECTION_STRICT_TRACING.
INTROSPECTION_BASE_OTEL_URL defaults to https://otel.introspection.dev and
INTROSPECTION_SERVICE_NAME to introspection-client.
Errors
Every HTTP failure raises IntrospectionAPIError (status_code, code,
request_id, body, retry_after) or a subclass: AuthenticationError,
InsufficientScopeError (missing_capability), RunnerExpiredError,
NotFoundError, ConflictError, ValidationError, RateLimitError,
SandboxUnavailableError, NetworkError, plus RunFailedError and
StreamIncompleteError from streams.
Next
- Add to your app: the full integration.
- Authentication: API keys and service accounts.
- Task operations: statuses and cancellation.