Skip to Content
Platform
SDKsRust

Rust SDK

Run tasks against a deployed runtime from Rust and read what the agent did.

The Rust SDK has the same surface as the JavaScript SDK; this page shows the Rust calls and what differs.

Install

cargo add introspection-sdk futures serde_json cargo add tokio --features rt-multi-thread,macros
FeatureAdds
DefaultREST client (runtimes, runner, tasks, files, shares, conversations, events, metrics, experiments) and auth (from_service_account, token_exchange, authorization_code_token).
otelIntrospectionLogs for track, feedback, identify.
arrowArrow decoding for conversation and event reads.
testingIn-memory exporter and test helpers.

IntrospectionClient::new(ClientConfig::default()) reads INTROSPECTION_TOKEN and returns IntrospectionError::TokenRequired when nothing resolves. Other credentials: Authentication.

Run a task

use futures::StreamExt; use introspection_sdk::{ AgUiEvent, ClientConfig, IntrospectionAPIError, IntrospectionClient, RunRequest, RunnerIdentity, }; /// Applies one stream event to the assistant text read so far. A reconnect /// past the replay buffer yields a `MessagesSnapshot` whose assistant messages /// replace the text read before it; later deltas append to it. fn append_text(text: &mut String, event: AgUiEvent) -> Result<(), Box<dyn Error>> { match event { AgUiEvent::MessagesSnapshot(e) => { *text = e .messages .iter() .filter(|m| m.get("role").and_then(|r| r.as_str()) == Some("assistant")) .filter_map(|m| m.get("content").and_then(|c| c.as_str())) .collect(); } AgUiEvent::TextMessageContent(m) => text.push_str(&m.delta), AgUiEvent::TextMessageChunk(m) => text.push_str(m.delta.as_deref().unwrap_or_default()), AgUiEvent::RunError(e) => { return Err(IntrospectionAPIError::RunFailed { message: e.message, code: e.code, } .into()); } AgUiEvent::Custom(e) if e.name == "resume_gap" => { return Err(IntrospectionAPIError::StreamIncomplete( "The replay buffer lost output; read the conversation transcript".into(), ) .into()); } _ => {} } Ok(()) } let client = IntrospectionClient::new(ClientConfig::default())?; let runner = client .runtime("support-agent") .await? .run(RunRequest { identity: Some(RunnerIdentity { user_id: Some("user_123".into()), ..Default::default() }), ..Default::default() }) .await?; let handle = runner.tasks().start_prompt("Summarize the open tickets.").await?; println!("{}", handle.text().await?); let mut events = runner.tasks().start_prompt("Say hello.").await?.into_stream().await?; let mut text = String::new(); while let Some(event) = events.next().await { append_text(&mut text, event?)?; } println!("{text}");

RunRequest fields are all optional: identity (user_id, anonymous_id, conversation_id, tags, metadata), caller, agent_name, ttl_seconds, scope. With no identity the task belongs to your credential’s own member (an API key’s agent member); see Run a task for an end user. runner.tasks().start(&TaskCreate { .. }) takes the full option set (title, agent, metadata, files, repositories, tags); follow-ups use runner.tasks().runs.create(&task_id, &TaskRunCreate { .. }). Close with runner.close() and client.shutdown()?.

Continue and control a task

  • Reattach. Read active_run_id or latest_run_id from the task’s metadata, then runner.tasks().runs.stream(&task_id, &run_id).await?. There is no since cursor; a reattach replays the run.
  • Stream options. runs.stream_with(&task_id, &run_id, opts) and handle.stream_with(opts) take StreamOptions (max_reconnects 5, backoff 500 ms, timeout 300 s, emit_reconnect_events) and return the stream directly. Semantics and the retry table: JavaScript.
  • Interrupts. Read ids from the RUN_FINISHED outcome["interrupts"], then resume (Ask the user):
use introspection_sdk::{ResumeEntry, TaskCancelOptions, TaskRunResume}; runner .tasks() .runs .resume( &task_id, &TaskRunResume { resume: vec![ResumeEntry { interrupt_id, status: "resolved".into(), payload: Some(serde_json::json!({ "approved": true })), }], }, ) .await?; handle.cancel().await?; // abort the turn, keep the sandbox warm handle .cancel_with(&TaskCancelOptions::Drain { drain_within_seconds: Some(60), }) .await?;

Files and shares

use introspection_sdk::{FileType, FileUpload, TaskCreate, TaskFileRef}; let uploaded = runner .files() .upload(FileUpload::from_path("notes.txt").with_file_type(FileType::Upload)) .await?; runner .tasks() .start(&TaskCreate { prompt: Some("Summarize the attached notes.".into()), files: Some(vec![TaskFileRef { id: uploaded.id.to_string(), ..Default::default() }]), ..Default::default() }) .await?;

FileUpload::from_bytes, files().create_text, download, and download_stream cover the rest. runner.shares() has create (ShareCreate::new(resource_type, id) plus granted_member_id, granted_tag, visible_from), update (only visible_from), and delete. Rules: Sharing.

Conversations, events, and metrics

use introspection_sdk::{ConversationListParams, MetricSpec, MetricsQuery}; let mut pages = runner .conversations() .list(&ConversationListParams::default())?; while let Some(page) = pages.next_page().await? { for c in &page.records { println!("{} {}", c.id, c.usage.total_tokens); } } let result = runner .metrics() .query(&MetricsQuery { view: "conversations".into(), metrics: vec![MetricSpec::of("sum", "total_cost_usd")], lookback: Some("7d".into()), ..Default::default() }) .await?;
  • conversations().items.list/get read the transcript; export_json, export_trajectory, export_stream, and export_arrow (feature arrow) sit on conversations().
  • events().list(&EventListParams::new(IntrospectionEventName::Feedback))? returns typed Event variants (annotation too); pass IntrospectionEventName::Unknown("introspection.track".into()) for track events.
  • MetricsQuery.lookback is a client-side convenience lowered to absolute timestamps; rows are typed MetricRows, and truncated() says whether the row limit was hit.
  • Paginator offers next_page(), collect_all(max_pages), and Stream.

Feedback

use introspection_sdk::otel::{FeedbackOptions, IntrospectionLogs}; let logs = IntrospectionLogs::builder() .service_name("support-api") .build()?; { let _c = logs.set_conversation_id(&task_id); logs.feedback( "thumbs_up", FeedbackOptions::new().with_comments("Solved it"), ); } logs.shutdown()?;

Needs the otel feature. with_previous_response_id anchors feedback to one response. Name rules: Send feedback.

Experiments

client.experiments() has list, get, create, update, and delete (documents as in introspection experiments create). client.experiment(id, project) returns a handle with run, start, end, and cancel.

let created = client.experiments().create(&serde_json::json!({ "project": "my-project", "name": "candidate", "runtime": "support-agent", "arms": [{ "runtime_id": a, "arm_label": "baseline" }, { "runtime_id": b, "arm_label": "candidate" }], "goal_json": { "kind": "composite", "direction": "maximize", "components": [{ "source": "judge", "judge_id": judge_id, "weight": 1.0 }] } })).await?; client.experiment(created.id, "my-project").start().await?;

Environment variables and errors

Environment variables match JavaScript, except INTROSPECTION_LOG_LEVEL and INTROSPECTION_STRICT_TRACING. Calls return ApiResult<T> with IntrospectionAPIError: Http (accessors status(), code(), request_id(), body(), retry_after()), RunFailed and StreamIncomplete from streams, and Transport, Decode, Timeout, Io, InvalidConfig. Client setup returns IntrospectionError; IntrospectionLogs returns IntrospectionLogsError.

Next

Last updated on