Agent
The Agent module enables your application to communicate with an AI Agent built in the ART Agent Builder. It allows you to send prompts, receive strongly typed responses as a stream of events, and respond to the agent whenever it requests input during task execution.
Prerequisites
- The ADK is installed, authenticated, and connected. See Installation if you haven't set this up yet.
- An agent has been created and deployed using the Agent Builder, and you have its
agentId.
Workflow
Each agent communicates over its own dedicated channel. The ADK manages this channel internally and exposes three core classes, allowing you to work with strongly typed objects instead of raw network frames.
| Class | Description |
|---|---|
Agent | Represents a single AI agent identified by its agentId. Reuse the same Agent instance for all conversations with that agent. |
AgentThread | Represents a single conversation with an agent. Each thread has a unique threadId that is automatically included with every request, enabling the server to maintain conversation state. |
Run | Represents a single prompt–response cycle within a thread. It manages the lifecycle of a request and provides access to the agent's final response when the run completes. |
Quick start
The following example shows the basic workflow for sending a prompt to an agent and handling the response:
// 1. Get the agent and start a conversation.
Agent *agent = [self.adk agent:@"your-agent-id"];
// 2. Start a thread
AgentThread *thread = [agent thread];
NSLog(@"Conversation started: %@", thread.threadId);
// 3. Run a prompt and wait for the final response.
[thread run:@"Plan a 3-day trip to Goa"
replyId:nil
completion:^(Run *run, NSError *error) {
if (error || !run) {
NSLog(@"Failed to start run: %@", error);
return;
}
}];
// 4.Listen for agent events
[thread listen:^(AgentEventEnvelope *envelope) {
switch (envelope.kind) {
...
}
}];
This example illustrates the complete interaction workflow with an agent. The following sections describe each step in detail, including event processing, handling human-input requests, and managing errors.
1. Connect to an agent
Get an agent instance from the connected Adk instance. The channel subscription is established lazily — it is established automatically the first time the agent is used, and reused after that:
Agent *agent = [self.adk agent:@"your-agent-id"];
2. Start a thread
An AgentThread represents a single conversation with an agent. Every message sent through the thread automatically includes its unique threadId, enabling the server to maintain conversation context and associate responses with the correct thread:
AgentThread *thread = [agent thread];
NSLog(@"Conversation started: %@", thread.threadId);
3. Run a prompt
Call [AgentThread run:replyId:completion:] to send a prompt to the agent. The method returns a Run, which represents the lifecycle of a single prompt–response interaction. A run begins when the prompt is submitted, progresses as the agent processes the request, and completes when the agent returns its final response:
[thread run:@"Plan a 3-day trip to Goa"
replyId:nil
completion:^(Run *run, NSError *error) {
if (error || !run) {
NSLog(@"Failed to start run: %@", error);
return;
}
}];
The following sequence diagram shows the complete lifecycle of a run when the agent does not request human input.
4. Listen for agent events
While [AgentThread run:replyId:completion:] provides the final AgentEventKindOutput for a run, many applications also need to observe intermediate events, such as progress updates or requests for additional user input. To receive these events, register a listener using [AgentThread listen:]:
[thread listen:^(AgentEventEnvelope *envelope) {
switch (envelope.kind) {
case AgentEventKindOutput: {
AgentOutput *output = [envelope asOutput];
NSLog(@"Agent: %@", output.message);
break;
}
case AgentEventKindError: {
AgentError *error = [envelope asError];
NSLog(@"Error [%@]: %@", error.code, error.message);
break;
}
case AgentEventKindHumanInput: {
HumanInputRequest *request = [envelope asHumanInput];
NSLog(@"Agent asks: %@", request.prompt);
break;
}
case AgentEventKindWait: {
AgentWait *wait = [envelope asWait];
NSLog(@"Waiting for agent: %@", wait.waitingForAgentId);
break;
}
case AgentEventKindPlannerCorrection: {
PlannerCorrection *correction = [envelope asPlannerCorrection];
NSLog(@"Planner correction: %@", correction.reason);
break;
}
case AgentEventKindUnknown: {
UnknownAgentEvent *unknown = [envelope asUnknown];
NSLog(@"Unknown event %@: %@", unknown.event, unknown.content);
break;
}
}
}];
Event catalog
Every event payload implements EnvelopeMeta, which provides the following routing metadata:
threadId— The conversation to which the event belongs.refId— The unique identifier of this event.agentId— The identifier of the agent that emitted the event.replyTo— TherefIdof the event to which this event is responding.
These fields may contain empty strings if the server does not populate them.
| Event | Accessor | Type | Meaning |
|---|---|---|---|
AgentEventKindOutput | asOutput | AgentOutput | Final response from the agent |
AgentEventKindError | asError | AgentError | Error returned by the agent |
AgentEventKindHumanInput | asHumanInput | HumanInputRequest | Agent requires user input |
AgentEventKindWait | asWait | AgentWait | Agent is waiting for another agent |
AgentEventKindPlannerCorrection | asPlannerCorrection | PlannerCorrection | Planner requested a correction |
AgentEventKindUnknown | asUnknown | UnknownAgentEvent | Raw event not modelled by the ADK |
The run lifecycle
A Run progresses through a small set of well-defined states during its lifetime. Understanding these states clarifies when [AgentThread run:replyId:completion:] completes successfully with the final AgentEventKindOutput, and when it throws an exception.
Diagnostics with trace
While a Run is in progress, the agent may emit trace frames containing diagnostic and telemetry information, such as heartbeats, checkpoints, and deadlock-detection signals. Trace frames are delivered separately from the normal event stream and are intended for observability, progress reporting, and debugging:
[thread listenTrace:^(id frame) {
NSLog(@"%@", frame);
}];
Error handling
A Run can complete successfully or fail. When awaiting run.done(), handle the two possible failure types separately, as each represents a different class of error:
[run doneWithCompletion:^(AgentOutput * _Nullable answer, NSError * _Nullable error) {
if (error) {
if ([error.domain isEqualToString:AgentError]) {
NSLog(@"The run failed: [%ld] %@",
(long)error.code,
error.localizedDescription);
} else if ([error.domain isEqualToString:StateError]) {
NSLog(@"This run was replaced by a newer one.");
} else {
NSLog(@"Unexpected error: %@", error.localizedDescription);
}
return;
}
NSLog(@"%@", answer.message);
}];
Error codes
| Code | Meaning |
|---|---|
HUMAN_INPUT_TIMEOUT | The user did not respond to a human_input_request before its timeout expired |
TRANSPORT_ERROR | The underlying WebSocket connection failed while the Run was in progress. The ADK converts this transport failure into an AgentError so that run.done() fails consistently with other runtime errors |
RUN_REJECTED | The run was rejected before producing a terminal event |
Complete example
- (void)talkToAgent:(Adk *)adk {
Agent *agent = [adk agent:@"travel-planner"];
AgentThread *thread = [agent thread];
[thread listen:^(AgentEventEnvelope *envelope) {
switch (envelope.kind) {
case AgentEventKindOutput:
NSLog(@"%@", [envelope asOutput].message);
break;
case AgentEventKindHumanInput: {
HumanInputRequest *request = [envelope asHumanInput];
[thread run:@"Budget 50k"
replyId:request.refId
completion:^(Run *run, NSError *error) {
if (error) {
NSLog(@"%@", error);
}
}];
break;
}
case AgentEventKindError: {
AgentError *error = [envelope asError];
NSLog(@"[%@] %@", error.code, error.message);
break;
}
default:
break;
}
}];
[thread run:@"Plan a 3-day trip to Goa"
replyId:nil
completion:^(Run *run, NSError *error) {
if (error) {
NSLog(@"%@", error);
}
}];
}
