Streaming

Consume Server-Sent Events from chat and pentest execution, follow agent activity, and attach files to a streaming request.

Real-time work in Rank is delivered over Server-Sent Events (SSE). Two methods open a stream:

  • client.ai.chat.stream(...) — send a prompt to an agent, or launch a pentest, and stream the response as it is produced.
  • client.pentests.stream(pentest_id) — reconnect to a pentest that is already running.

Both return a Stream on the synchronous client and an AsyncStream on the asynchronous one. The object is both a context manager and an iterator of ServerSentEvent, and it closes itself automatically when a terminal event (complete, error or cancelled) arrives.

Sending a chat request

client.ai.chat.stream has a single required argument, user_prompt. The remaining arguments select the flow:

  • General chat — pass agent_id (and optionally chat_id to persist context).
  • Pentest, automatic — pass pentest_id with mode="automatic".
  • Pentest, guided — pass pentest_id with mode="guided" and phase_id.
ArgumentTypeNotes
user_promptstrThe message to send. Required.
agent_idintAgent to use (required for general chat).
agent_type"general" | "pentest"Inferred automatically when pentest_id is set.
chat_idintPersists messages and conversation context across modes.
pentest_idintRequired for pentest modes.
mode"guided" | "automatic"Pentest only.
phase_idintRequired for guided mode.
contextlistExplicit conversation context (ChatMessage objects or dicts).
file / filesattachment(s)A single attachment, or a list. Mutually exclusive.

Sync vs. async iteration

The synchronous client uses a plain with / for. The asynchronous client requires you to await the stream(...) call first, then use async with / async for.

The ServerSentEvent shape

Every iteration yields a ServerSentEvent. These are the properties you read:

PropertyTypeDescription
event.typestrEvent type identifier (content, complete, agent_event, …).
event.contentstrText content of the event.
event.errorstr | NoneError message (only for error events).
event.metadatadictExtra fields: progress %, vulnerability counts, elapsed time, etc.
event.timestampfloat | NoneEvent timestamp.
event.raw_datastrRaw JSON string before parsing.
event.is_agent_eventboolTrue when the event carries an AgentEvent payload.
event.agent_eventAgentEvent | NoneParsed AgentEvent (only when type == "agent_event").
event.event_typestr | NoneShortcut to agent_event.event_type.

Common transport event types you will see, depending on the flow: content, queued, ready, phase_start, phase_complete, phase_retry, processing_vulnerabilities, vulnerabilities_complete, agent_event, complete, error, cancelled and reconnected.

The golden rule: content vs. agent_event

This is the single most important thing to get right when rendering a stream:

Only event.type == "content" builds the assistant’s answer. Events with event.type == "agent_event" describe what the agent is doing — thinking, calling tools, spawning sub-agents — and are meant for an activity / timeline view. Never concatenate agent_event text into the answer body.

If you only care about the final answer, handle content and ignore everything else. To also render the live activity, branch on event.is_agent_event and read the typed payload from event.agent_event.

The AgentEvent payload

When event.is_agent_event is True, event.agent_event returns a typed AgentEvent:

FieldTypeDescription
event_typestrSub-event identifier (e.g. thinking, tool_call, agent_finished).
agent_idint | str | NoneNumeric agent ID, "orchestrator", or None.
parent_agent_idint | NoneParent agent ID for sub-agents.
instance_idstrUnique per-execution key — group an instance’s events by this.
depthint0 = top-level agent, 1 = sub-agent.
iterationintCurrent iteration of the agent loop.
timestampfloatUnix timestamp.
datadictEvent-specific payload (read the keys relevant to event_type).

Every sub-event name is available as a constant on the class — AgentEvent.TOOL_CALL, AgentEvent.THINKING, AgentEvent.SUBAGENT_SPAWN, AgentEvent.AGENT_FINISHED, and so on — so you can compare against constants instead of raw strings. Sub-agent text arrives as agent_event with event_type == "text_chunk" and depth > 0: it is activity, not the answer.

Full agent_event reference

The harness emits one agent_event per step. You only need to handle the ones your UI cares about; unknown event_type values are safe to ignore. For the mechanics behind these events, see Agent harness & long-running tasks.

Agent loop (a single agent working through its ReAct loop):

event_typeConstantKey data fields
agent_startAGENT_STARTmodel, agent_id, mission, max_iterations, tools
planPLANplan_text
iteration_startITERATION_STARTiteration, max_iterations, tokens_used, stagnation
thinkingTHINKINGcontent, streaming
tool_callTOOL_CALLtool_name, tool_args
tool_resultTOOL_RESULTtool_name, result, duration_ms, skipped, cached, blocked
interpretationINTERPRETATIONcontent
context_compactionCONTEXT_COMPACTIONsummarized_steps, summary_chars
shared_contextSHARED_CONTEXTsummary_length, summary_preview
nudgeNUDGEnudge_count, unused_tools
progressPROGRESSiteration, tools_used, tokens_used
iteration_completeITERATION_COMPLETEiteration_prompt, iteration_response, token counts
text_chunkTEXT_CHUNKcontent (sub-agents only, depth > 0)
subagent_spawnSUBAGENT_SPAWNsubagent_id, mission, depth
subagent_completeSUBAGENT_COMPLETEsubagent_id, result_preview, iterations
agent_finishedAGENT_FINISHEDstop_reason, iterations, input_tokens, output_tokens, elapsed_s

Orchestration (automatic pentests, where agent_id is "orchestrator"):

event_typeConstantKey data fields
orchestration_startORCHESTRATION_STARTagents, phase, total_agents
orchestration_statusORCHESTRATION_STATUSalive_agents, completed_agents, elapsed_time
agent_status_changeAGENT_STATUS_CHANGEagent_id, status, reasoning
consolidation_startCONSOLIDATION_STARTagents_completed
consolidation_heartbeatCONSOLIDATION_HEARTBEATelapsed_seconds, message
consolidation_completeCONSOLIDATION_COMPLETEinput_tokens, output_tokens
orchestration_completeORCHESTRATION_COMPLETEphase, total_agents, successful, failed
orchestration_cancelledORCHESTRATION_CANCELLEDmessage

Browser agent (interactive web navigation): browser_agent_start (BROWSER_AGENT_START) with mission, target_url, max_iterations. Its tool_call/tool_result events mask credentials.

The stop_reason carried by agent_finished is one of goal_reached, max_iterations, timeout, budget, stagnation or cancelled, each available as a constant (AgentEvent.STOP_GOAL_REACHED, AgentEvent.STOP_MAX_ITERATIONS, and so on).

Launching and reconnecting to a pentest

A pentest stream carries the same content and agent_event items plus pentest-specific transport events such as phase_start, phase_complete and vulnerabilities_complete. Launch one with client.ai.chat.stream(..., pentest_id=...), and reattach to a running one with client.pentests.stream(pentest_id).

Attachments

Pass a single attachment with file or several with files (the two are mutually exclusive). Each attachment can be a file-like object, raw bytes, a (filename, content) tuple, or a (filename, content, content_type) tuple. Supported extensions are PDF, JSON, PNG, JPEG, WEBP and GIF, with a limit of 10 files, 30 MB per file and 50 MB combined per request.

Once you can read a stream, see Errors & pagination for handling failures, or the Cookbook for a full streaming UI recipe.