Skip to content

Running agents

Agents do nothing by themselves – you run them with the Runner class or the run() utility.

Simple run
import { Agent, run } from '@openai/agents';
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant',
});
const result = await run(
agent,
'Write a haiku about recursion in programming.',
);
console.log(result.finalOutput);
// Code within the code,
// Functions calling themselves,
// Infinite loop's dance.

When you don’t need a custom runner, you can also use the run() utility, which runs a singletone default Runner instance.

Alternatively, you can create your own runner instance:

Simple run
import { Agent, Runner } from '@openai/agents';
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant',
});
// You can pass custom configuration to the runner
const runner = new Runner();
const result = await runner.run(
agent,
'Write a haiku about recursion in programming.',
);
console.log(result.finalOutput);
// Code within the code,
// Functions calling themselves,
// Infinite loop's dance.

After running your agent, you will receive a result object that contains the final output and the full history of the run.

When you use the run method in Runner, you pass in a starting agent and input. The input can either be a string (which is considered a user message), or a list of input items, which are the items in the OpenAI Responses API.

The runner then runs a loop:

  1. Call the current agent’s model with the current input.
  2. Inspect the LLM response.
    • Final output → return.
    • Handoff → switch to the new agent, keep the accumulated conversation history, go to 1.
    • Tool calls → execute tools, append their results to the conversation, go to 1.
  3. Throw MaxTurnsExceededError once maxTurns is reached.

Create a Runner when your app starts and reuse it across requests. The instance stores global configuration such as model provider and tracing options. Only create another Runner if you need a completely different setup. For simple scripts you can also call run() which uses a default runner internally.

The input to the run() method is an initial agent to start the run on, input for the run and a set of options.

The input can either be a string (which is considered a user message), or a list of input items, or a RunState object in case you are building a human-in-the-loop agent.

The additional options are:

OptionDefaultDescription
streamfalseIf true the call returns a StreamedRunResult and emits events as they arrive from the model.
contextContext object forwarded to every tool / guardrail / handoff. Learn more in the context guide.
maxTurns10Safety limit – throws MaxTurnsExceededError when reached.
signalAbortSignal for cancellation.

Streaming allows you to additionally receive streaming events as the LLM runs. Once the stream is started, the StreamedRunResult will contain the complete information about the run, including all the new outputs produces. You can iterate over the streaming events using a for await loop. Read more in the streaming guide.

If you are creating your own Runner instance, you can pass in a RunConfig object to configure the runner.

FieldTypePurpose
modelstring | ModelForce a specific model for all agents in the run.
modelProviderModelProviderResolves model names – defaults to the OpenAI provider.
modelSettingsModelSettingsGlobal tuning parameters that override per‑agent settings.
handoffInputFilterHandoffInputFilterMutates input items when performing handoffs (if the handoff itself doesn’t already define one).
inputGuardrailsInputGuardrail[]Guardrails applied to the initial user input.
outputGuardrailsOutputGuardrail[]Guardrails applied to the final output.
tracingDisabledbooleanDisable OpenAI Tracing completely.
traceIncludeSensitiveDatabooleanExclude LLM/tool inputs & outputs from traces while still emitting spans.
workflowNamestringAppears in the Traces dashboard – helps group related runs.
traceId / groupIdstringManually specify the trace or group ID instead of letting the SDK generate one.
traceMetadataRecord<string, any>Arbitrary metadata to attach to every span.

Each call to runner.run() (or run() utility) represents one turn in your application-level conversation. You choose how much of the RunResult you show the end‑user – sometimes only finalOutput, other times every generated item.

Example of carrying over the conversation history
import { Agent, AgentInputItem, run } from '@openai/agents';
let thread: AgentInputItem[] = [];
const agent = new Agent({
name: 'Assistant',
});
async function userSays(text: string) {
const result = await run(
agent,
thread.concat({ role: 'user', content: text }),
);
thread = result.history; // Carry over history + newly generated items
return result.finalOutput;
}
await userSays('What city is the Golden Gate Bridge in?');
// -> "San Francisco"
await userSays('What state is it in?');
// -> "California"

See the chat example for an interactive version.

The SDK throws a small set of errors you can catch:

All extend the base AgentsError class, which could provide the state property to access the current run state.

Here is an example code that handles GuardrailExecutionError:

Guardrail execution error
import {
Agent,
run,
GuardrailExecutionError,
InputGuardrail,
InputGuardrailTripwireTriggered,
} from '@openai/agents';
import { z } from 'zod';
const guardrailAgent = new Agent({
name: 'Guardrail check',
instructions: 'Check if the user is asking you to do their math homework.',
outputType: z.object({
isMathHomework: z.boolean(),
reasoning: z.string(),
}),
});
const unstableGuardrail: InputGuardrail = {
name: 'Math Homework Guardrail (unstable)',
execute: async () => {
throw new Error('Something is wrong!');
},
};
const fallbackGuardrail: InputGuardrail = {
name: 'Math Homework Guardrail (fallback)',
execute: async ({ input, context }) => {
const result = await run(guardrailAgent, input, { context });
return {
outputInfo: result.finalOutput,
tripwireTriggered: result.finalOutput?.isMathHomework ?? false,
};
},
};
const agent = new Agent({
name: 'Customer support agent',
instructions:
'You are a customer support agent. You help customers with their questions.',
inputGuardrails: [unstableGuardrail],
});
async function main() {
try {
const input = 'Hello, can you help me solve for x: 2x + 3 = 11?';
const result = await run(agent, input);
console.log(result.finalOutput);
} catch (e) {
if (e instanceof GuardrailExecutionError) {
console.error(`Guardrail execution failed: ${e}`);
// If you want to retry the execution with different settings,
// you can reuse the runner's latest state this way:
if (e.state) {
try {
agent.inputGuardrails = [fallbackGuardrail]; // fallback
const result = await run(agent, e.state);
console.log(result.finalOutput);
} catch (ee) {
if (ee instanceof InputGuardrailTripwireTriggered) {
console.log('Math homework guardrail tripped');
}
}
}
} else {
throw e;
}
}
}
main().catch(console.error);

When you run the above example, you will see the following output:

Guardrail execution failed: Error: Input guardrail failed to complete: Error: Something is wrong!
Math homework guardrail tripped