Skip to main content

Module verify

Module verify 

Source
Expand description

Client-side protocol verification.

The TypeScript SDK puts its verifier on the client, and that is the right instinct for a consumer: the events arrive from someone else’s process, and a stream that breaks the rules should produce one clear error rather than a confused UI. This module is that check, as an ordering state machine.

crate::client::Session runs it by default. Turn it off with SessionBuilder::verify when talking to a producer whose quirks you have decided to live with.

use ag_ui::client::verify::Verifier;
use ag_ui::Event;

let mut verifier = Verifier::new();
verifier.verify(&Event::run_started("thread-1", "run-1"))?;

// Content for a message that was never opened.
let orphan = Event::text_message_content("msg-1", "Hello");
assert!(verifier.verify(&orphan).is_err());

§The rules

  1. RUN_STARTED opens the stream, and does so exactly once. Only RAW and CUSTOM may precede it.
  2. RUN_FINISHED and RUN_ERROR close it. Nothing may follow.
  3. TEXT_MESSAGE_CONTENT and TEXT_MESSAGE_END require an open message with the same id, and TEXT_MESSAGE_START may not re-open an id that is already open.
  4. The same, for TOOL_CALL_* and for REASONING_MESSAGE_*.
  5. TOOL_CALL_RESULT may not answer a call that has not ended.
  6. STEP_FINISHED requires a matching STEP_STARTED, and step names do not nest with themselves.
  7. Everything open must be closed before RUN_FINISHED.
  8. An interrupt outcome must carry at least one interrupt — the one rule the type system cannot express, checked by RunOutcome::validate.
  9. A continuation, terminator or re-open that names a subagent must name the one that opened the entity — a message, a reasoning block or the message inside it, a tool call, an activity, or whatever a REASONING_ENCRYPTED_VALUE attaches to. One that names none is accepted: attribution is optional per event, and a bare continuation is what a pre-subagent producer sends. It does not hand the entity to the parent either: the first writer stays the owner.
  10. A tool call belongs to the message its parentMessageId names, so a TOOL_CALL_START tagged with one subagent while that message belongs to another is rejected; an untagged one inherits the message’s owner.
  11. Steps are scoped to the agent that opened them: a subagent cannot close the parent’s step, or a sibling’s, and the same name may be open under two owners at once.
  12. SUBAGENT_STARTED names an invocation that is neither active nor already finished in this run, and a parentSubagentRunId that was started. SUBAGENT_FINISHED and SUBAGENT_ERROR name an active one.
  13. Every started subagent is closed before RUN_FINISHED — not before RUN_ERROR, where an unclosed subagent is the expected shape.

What is deliberately not a rule: that one stream must close before the next opens. Everything here is keyed by id, exactly as the TypeScript verifier keys its activeMessages / activeToolCalls maps. Two messages may stream at once, two tool calls may stream at once, and a tool call may open inside the message that narrates it — which is what every provider doing parallel tool calls actually sends. Nor must an attributing subagentRunId have been announced: attribution without lifecycle events is a supported mode. Events outside these families (state, activity, raw, custom) are unordered and never close anything. A MESSAGES_SNAPSHOT seeds ownership from the messages it carries and is authoritative; the RUN_STARTED input echo seeds it too, for ids not yet recorded; and a TOOL_CALL_RESULT mints the tool message it names under its own attribution.

Structs§

Verifier
An ordering state machine for one run’s event stream.

Functions§

verify_all
Verifies a whole run in one call.