Skip to content

Build agents

Agent definitions

An agent definition contains schemas, instructions, a native Effect AI toolkit, and a finite policy. The application supplies model services and other dependencies when it runs the agent.

const definition = Agent.make("support-triage", {
input: SupportRequest,
output: Resolution,
instructions,
toolkit: SupportToolkit,
policy,
});
const run = AgentRuntime.run(definition, input).pipe(Effect.provide(ClaudeModel));

Definitions contain no provider client, database connection, mutable thread, or acquired resource. Reuse one definition across many runs. Effect Schema supplies the data types and runtime validation; provider wire schemas derive from those definitions.

Agent.make requires an ID, input and output schemas, instructions, and a toolkit. Declare ordinary limits as a plain policy object; Agent.make validates it and fills defaults. A complete AgentPolicy value is also accepted. Standalone defaults are 12 turns, 24 tool calls, 5 minutes, and tool concurrency 4. Other defaults follow AgentPolicy.make. Delegated children inherit omitted policy fields from their parent. Supply a partial object to inherit individual fields; AgentPolicy.make fills its own defaults before inheritance. Definitions retain the explicit fields as policyOverrides; policy holds their standalone values. It also accepts inputPrompt, completion, runDisposition, a description, and metadata.

Instructions may be static prompt input or a function of decoded input. That function may return an Effect. Its errors and service requirements become part of the run type.

Output Schemas use JSON final messages by default. For ordinary assistant text, wrap the complete Schema with Output.text. The Schema must encode as a string; its checks, transformations, and service requirements still apply.

import {
import Output
Output
} from "@yielded/agent";
import {
import Schema
Schema
} from "effect";
const
const Reply: Schema.String
Reply
=
import Output
Output
.
text<Schema.String>(schema: Schema.String): Schema.String
export text

Declare ordinary assistant text as an Agent's final-output wire format. Apply this to the complete output Schema after composing transformations. Its encoded type must be string; decoding, checks, transformations, and service requirements remain owned by that Schema. Text is preserved verbatim, including whitespace, quotes, and an empty reply when allowed. Required completion Tools still take precedence. Unmarked Schemas use JSON final output.

@example

output: Output.text(Schema.String.check(Schema.isMaxLength(20_000)))

text
(
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

Schema for string values. Validates that the input is typeof "string".

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
.
BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<string>, ...checks: Check<string>[]): Schema.String
check
(
import Schema
Schema
.
function isMaxLength(maxLength: number, annotations?: Schema.Annotations.Filter): Filter<{
readonly length: number;
}>

Validates that a value has at most the specified length. Works with strings and arrays.

Details

JSON Schema:

The bound must be finite; it is rounded down and clamped to zero. This check corresponds to maxItems for arrays. Strings use the same bound for maxLength, which cannot reject a string accepted by this check because a string has no more code points than UTF-16 code units.

Arbitrary:

During arbitrary generation, this applies a maxLength constraint to ensure generated strings or arrays have at most the required length.

@category ― validation

@since ― 4.0.0

isMaxLength
(20_000)));
// Pass Reply as Agent.make(..., { output: Reply, ... }).

The runtime instructs the model to reply without JSON wrapping and decodes that text directly. Whitespace, quotes, and empty replies are preserved. Use Schema.NonEmptyString when silence is invalid. Apply Output.text after composing the Schema. Required completion tools still take precedence; optional completion tools retain their own parameter contract. Durable records store the validated string as a JSON string value. Metadata does not select an output format. Once RunCompleted records validated output, recovery preserves that stored value without decoding it through a later output Schema.

The runtime normally sends the complete schema-encoded input as a JSON user message. Use inputPrompt to send a smaller or differently shaped value.

const definition = Agent.make("support-triage", {
input: Schema.Struct({ question: Schema.String, authorizationToken: Schema.String }),
output: Resolution,
instructions: "Answer the customer's question.",
inputPrompt: ({ question }) =>
Effect.succeed(Prompt.make([{ role: "user", content: [{ type: "text", text: question }] }])),
toolkit: SupportToolkit,
policy,
});

inputPrompt receives decoded input and returns native Effect AI Prompt.RawInput, directly or through an Effect. Strings become user messages. Prompts and message arrays keep their roles, parts, provider options, and multimodal content. Return an empty Prompt or message array to omit the input message. Its errors and service requirements join the run’s E and R.

The runtime builds the source from history, instructions, and projected input, then applies context preparation and compaction. Outgoing requests place system instructions and the output contract before the conversation; optional transient context and run status follow the conversation. See Context management.

Projection changes only model-visible input. Durable admission still stores the complete encoded input, and tool authorization receives it. Keep secrets out of instructions and apply separate disclosure rules to history, tool results, steering, and host context transforms.

Durable attempts may evaluate the projection again. Committed turns keep their recorded projected messages. Projection effects must tolerate another evaluation and must not assume exactly-once external execution.

A projection failure stops the run before the next model request. The runtime never falls back to the full input. Projection services, errors, interruption, and deadlines follow normal Effect semantics.

Models are execution requirements: use Effect.provide for a Run, Stream.provide for a stream, and Layer.provide for subagent handlers.

const
const program: Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, ThreadHistory | OpenAiClient>
program
=
import AgentRuntime
AgentRuntime
.
run<Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, never, never>(agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, input: string, options?: RunOptions<...> | undefined): Effect.Effect<...>
export run

Accept schema-encoded input, retaining runtime validation. Use runUnknown for external data.

run
(
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
...;
}
planner
, "Plan a weekend in Lisbon.").
Pipeable.pipe<Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <LanguageModel | ProviderName | ModelName, never, OpenAiClient>(layer: Layer.Layer<LanguageModel | ProviderName | ModelName, never, OpenAiClient>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, OpenAiClient | Exclude<R, LanguageModel | ProviderName | ModelName>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
),
);
const
const events: Stream.Stream<AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined, AgentRuntime.AgentRuntimeFailure<...>, ThreadHistory | OpenAiClient>
events
=
import AgentRuntime
AgentRuntime
.
stream<Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, never, never>(agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, input: string, options?: RunOptions<...> | undefined): Stream.Stream<...>
export stream

Accept schema-encoded input, retaining runtime validation. Use streamUnknown for external data.

stream
(
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
...;
}
planner
, "Plan a weekend in Lisbon.").
Pipeable.pipe<Stream.Stream<AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined, AgentRuntime.AgentRuntimeFailure<...>, AgentRuntime.AgentRuntimeRequirements<...>>, Stream.Stream<...>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Stream.Stream<...>): Stream.Stream<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const provide: <LanguageModel | ProviderName | ModelName, never, OpenAiClient>(layer: Layer.Layer<LanguageModel | ProviderName | ModelName, never, OpenAiClient> | Context<LanguageModel | ProviderName | ModelName>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, OpenAiClient | Exclude<...>> (+1 overload)

Provides a layer or context to the stream, removing the corresponding service requirements. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing stream requirements)

import { Console, Context, Effect, Layer, Stream } from "effect"
class Env extends Context.Service<Env, { readonly name: string }>()("Env") {}
const layer = Layer.succeed(Env)({ name: "Ada" })
const stream = Stream.fromEffect(
Effect.gen(function*() {
const env = yield* Effect.service(Env)
return `Hello, ${env.name}`
})
)
const withEnv = stream.pipe(Stream.provide(layer))
await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"]

@category ― providing services

@since ― 4.0.0

provide
(
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
),
);
const
const ResearchLive: Layer.Layer<Handler<"delegate_research_activities">, never, OpenAiClient | Handler<"search_activities"> | SubagentReservations>
ResearchLive
=
import Subagent
Subagent
.
function layer<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, Struct<...>, Struct<...>, Never, never, never, never, "error", never, never, undefined, undefined, undefined>(delegation: Subagent.SubagentDelegation<...> & {
...;
}, modelOrBinding?: undefined, options?: Subagent.SubagentRuntimeOptions<...> | undefined): Layer.Layer<...> (+1 overload)

Build the Toolkit handler Layer using the provided model requirement, or pass a native model / explicit child Binding as an override. Without an override, provide the model with Layer.provide; the handler captures it at construction. AutoModel resolves each new child's own Thread ID and projected first task. Share its selection store across parent Runs and child handler Layers.

Construction requirements carry the child Binding's full runtime needs and both projections; they are captured once via Effect.context so the per-call handler requirements stay exactly the Tool's declared engine dependencies. The handler dispatches on the engine-provided per-batch SubagentDurability service mode:

  • ephemeral (the explicit engine default when no durable coordinator supplied RunOptions.subagent): the S1 path unchanged — preflight, an in-process scoped child Run (SUB-011/012), stable lifecycle events, total-mapped expected child failures, and Scope-finalizer reservation settlement on every exit path.
  • durable (S2): the same fail-closed preflight and input projection, then idempotent establishment through the coordinator (spec §12 steps 2-9) with construction-fixed child Binding digests, encoded grant, and encoded allocation; the engine-owned waiting signal while the attached child is nonterminal; and, on re-entry with a settled child, output decoding, projectResult, and ONE atomic settlement join carrying the conservative accounting summary. Failed children join as the bounded SubagentExecutionFailure; no in-process child fiber ever starts.

layer
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
).
Pipeable.pipe<Layer.Layer<Handler<"delegate_research_activities">, never, Subagent.SubagentLayerRequirements<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 10 more ..., undefined>>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <OpenAiClient, never, LanguageModel | ProviderName | ModelName>(that: Layer.Layer<LanguageModel | ProviderName | ModelName, never, OpenAiClient>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, OpenAiClient | Exclude<RIn2, LanguageModel | ProviderName | ModelName>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
));

run, stream, and start require LanguageModel.LanguageModel, Model.ProviderName, and Model.ModelName. Native Effect model Layers provide all three; their client requirements remain visible until the application supplies them. Supply tool handlers and history alongside those clients. Keep the model Layer around both start and the detached run’s lifetime.

Subagent.layer captures the supplied model when its handler Layer is built. Use Layer.provideMerge(ModelLive) when an assembled Layer should expose that model to the parent too; the attached-subagent walkthrough shows the complete setup. AutoModel satisfies the same requirement and selects once on each thread’s first turn, including each new child.

The model Layer must have no construction error. Put fallible setup in the enclosing Layer or Effect. When constructing a service that needs to reuse a model, capture its client dependencies:

const
const captured: Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, ThreadHistory | OpenAiClient>
captured
=
import Effect
Effect
.
const gen: <Effect.Effect<Layer<LanguageModel | ProviderName | ModelName, never, never>, never, OpenAiClient> | Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<...>, ThreadHistory>, {
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const modelLayer: Layer<LanguageModel | ProviderName | ModelName, never, never>
modelLayer
= yield*
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
.
Model<"openai", LanguageModel, OpenAiClient>.captureRequirements: Effect.Effect<Layer<LanguageModel | ProviderName | ModelName, never, never>, never, OpenAiClient>

Returns a Layer with the requirements satisfied, using the current context.

captureRequirements
;
return yield*
import AgentRuntime
AgentRuntime
.
run<Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, never, never>(agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, input: string, options?: RunOptions<...> | undefined): Effect.Effect<...>
export run

Accept schema-encoded input, retaining runtime validation. Use runUnknown for external data.

run
(
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
planner
, "Plan a weekend in Lisbon.").
Pipeable.pipe<Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <LanguageModel | ProviderName | ModelName, never, never>(layer: Layer<LanguageModel | ProviderName | ModelName, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, LanguageModel | ProviderName | ModelName>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
const modelLayer: Layer<LanguageModel | ProviderName | ModelName, never, never>
modelLayer
),
);
});
type BeforeModel = Agent.DefinitionRequirements<typeof definition>;
type ExecutionRequirements = Agent.Requirements<typeof definition>;
type Failure = Agent.Failure<typeof definition>;

Selecting between several definitions produces the union of their errors and requirements. Provide every branch or narrow the selection before execution. Optional explicit bindings are covered in the API reference.

Keep Thread identities independent of deployment fingerprints. The original admission, input, principal, digests, idempotency key and Receipt remain immutable. Register one current executable per stable agentId and optionally declare each Tool’s replay version:

const registration = {
agent: definition,
model: modelLayer,
definitions: deploymentDefinitions,
continuity: {
versions: {
tools: { search: "search-command" },
},
},
};

When continuity.versions is supplied, include every current Tool. Each version is a JSON value; change it when handler meaning, durable Step codecs or names, external idempotency keys, authorization semantics, completion projection, or child/report protocols change. Registration hashes that version with the Tool’s schemas, failure mode, approval policy, execution class, kind, and completion or context-rollover role. Without explicit versions, it uses the Tool definitions declaration as the version. JSON Schema cannot detect changed handler code or codec transforms, so keep those declarations accurate.

Queued and resumed work selects the current binding by agentId. Historical Agent or toolbox digests do not gate execution. A committed continuation retains its original input and prompt; static instructions do not require a future input codec to accept that value. Input-dependent instructions still decode their required input. An incompatible current input Schema returns BindingUnavailable and leaves the original Receipt owed. Admission, lineage, and prepared delivery evidence remain exact and are never rewritten.

Recovery checks each pending operation against its recorded execution contract. A changed or removed mutating Tool that was never dispatched receives ToolUnavailable with execution: "not-executed", allowing the model to continue with current Tools. If an effect may have occurred, the call stays unknown unless reconciliation supplies proof. Absence of a prepared record does not prove that readonly work never ran. See operation recovery.

Hosts with deferred bindings can compile current metadata without acquiring executable services:

import { AgentRegistration } from "@yielded/agent";
import { Effect } from "effect";
const metadata = Effect.gen(function* () {
return yield* AgentRegistration.compileBindingContracts(definition, definitions, {
tools: { search: "search-command" },
});
});

The Effect returns { digests } and requires Crypto. Use those digests in the deferred binding. There is no historical manifest or Agent replay-version registry to maintain.

For a low-level binding without per-operation hashes, the Tool digest supplies the operation version. Direct processThread execution without a current registration uses deploymentId instead. Reusing that ID asserts unchanged handler, Durable Step, and idempotency semantics; unfinished mutations crossing deployments need registered per-operation versions or reconciliation.

Cloudflare maintenance persists bounded per-lane retries for missing current bindings and continues other eligible lanes. Reinstantiation retains the backoff; registering the missing Agent resumes the same Receipt. Parked unknown operations do not block later input in their Thread.

Durable maxDuration bounds each active Attempt using its original recorded allowance. Time spent evicted, waiting for a binding, or suspended does not consume execution time. An actual duration exhaustion is recorded before child cleanup and survives recovery. The Run’s start time and cumulative turn, Tool and cost accounting are unchanged; immutable worker authorization expiry still limits execution. Roll out the matching runtime and storage packages together before writing the new duration record; older runtimes cannot read it.

The main operations accept Agent.EncodedInput<typeof definition>. The runtime decodes that value before instructions run. For Schema.NumberFromString, callers pass a string and instructions receive a number.

Encode a decoded value with Schema.encodeEffect(definition.input). Use runUnknown, streamUnknown, or startUnknown for untrusted external data. Invalid input fails with AgentInputDecodeError before instructions or model execution.

Set completion when a successful tool result should become the agent’s output without another model turn. The projector receives decoded tool parameters and result:

import {
import Agent
Agent
} from "@yielded/agent";
import {
import Effect
Effect
,
import Schema
Schema
} from "effect";
import {
import Tool
Tool
,
import Toolkit
Toolkit
} from "effect/ai";
const
const Answer: Schema.Struct<{
readonly answer: Schema.String;
}>
Answer
=
import Schema
Schema
.
function Struct<{
readonly answer: Schema.String;
}>(fields: {
readonly answer: Schema.String;
}): Schema.Struct<{
readonly answer: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

@category ― constructors

@since ― 3.10.0

Struct
({
answer: Schema.String
answer
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

Schema for string values. Validates that the input is typeof "string".

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
});
const
const Complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
Complete
=
import Tool
Tool
.
const make: <"complete", Schema.Struct<{
readonly answer: Schema.String;
}>, Schema.Void, Schema.Never, undefined, []>(name: "complete", options?: {
readonly description?: string | undefined;
readonly parameters?: Schema.Struct<{
readonly answer: Schema.String;
}> | undefined;
readonly success?: Schema.Void | undefined;
readonly failure?: Schema.Never | undefined;
readonly failureMode?: undefined;
readonly dependencies?: [] | undefined;
readonly needsApproval?: Tool.NeedsApproval<Schema.Struct<{
readonly answer: Schema.String;
}>> | undefined;
} | undefined) => Tool.Tool<...>

Creates a user-defined tool with the specified name and configuration.

Details

This is the primary constructor for creating custom tools that AI models can call. The tool definition includes parameter validation, success/failure schemas, and optional service dependencies.

If a tool accepts no parameters but still needs an explicit empty object schema, use

EmptyParams

.

Example (Creating a tool without parameters)

import { Schema } from "effect"
import { Tool } from "effect/ai"
// Simple tool with no parameters
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Returns the current timestamp",
success: Schema.Number
})
GetCurrentTime.name // => "GetCurrentTime"

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
("complete", {
parameters?: Schema.Struct<{
readonly answer: Schema.String;
}> | undefined

Schema defining the parameters this tool accepts.

parameters
:
const Answer: Schema.Struct<{
readonly answer: Schema.String;
}>
Answer
,
success?: Schema.Void | undefined

Schema for successful tool execution results.

success
:
import Schema
Schema
.
const Void: Schema.Void

Type-level representation of

Void

.

Schema for a TypeScript void return value.

When to use

Use when you need to model the return value of a function, RPC, or endpoint whose result is intentionally ignored.

Details

Runtime parsing accepts any present value and discards it, producing undefined. The public decoded and encoded TypeScript representation remains void, so typed construction, decoding, and encoding APIs are still modeled as void.

@category ― models

@since ― 3.10.0

@see ― Undefined for a schema that matches only the exact undefined value.

@category ― schemas

@since ― 3.10.0

Void
});
const
const Tools: Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>
Tools
=
import Toolkit
Toolkit
.
const make: <[Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>]>(tools_0: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>) => Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>

Creates a new toolkit from the specified tools.

Details

This is the primary constructor for creating toolkits. It accepts multiple tools and organizes them into a toolkit that can be provided to AI language models.

Example (Creating a toolkit)

import { Effect, Schema } from "effect"
import { Tool, Toolkit } from "effect/ai"
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Get the current timestamp",
success: Schema.Number
})
const GetWeather = Tool.make("get_weather", {
description: "Get weather information",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const toolkit = Toolkit.make(GetCurrentTime, GetWeather)
const ready = toolkit.pipe(Effect.provide(toolkit.toLayer({
GetCurrentTime: () => Effect.succeed(0),
get_weather: () => Effect.succeed({ temperature: 20, condition: "clear" })
})))
Object.keys((await Effect.runPromise(ready)).tools) // => ["GetCurrentTime", "get_weather"]

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
(
const Complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
Complete
);
export const
const Definition: Agent.Definition<Schema.Struct<{
readonly question: Schema.String;
}>, Schema.Struct<{
readonly answer: Schema.String;
}>, "Answer the question using the complete tool.", Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, undefined, undefined, undefined> & {
...;
}
Definition
=
import Agent
Agent
.
function make<"answer-question", Schema.Struct<{
readonly question: Schema.String;
}>, Schema.Struct<{
readonly answer: Schema.String;
}>, "Answer the question using the complete tool.", Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, undefined>(id: "answer-question", options: Agent.DefinitionOptions<Schema.Struct<{
readonly question: Schema.String;
}>, ... 5 more ..., undefined> & {
...;
}): Agent.Definition<...> & {
...;
} (+3 overloads)

Validate an agent ID and return a shallowly frozen, model-agnostic definition.

make
("answer-question", {
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<{ readonly complete: Tool<...>; }>, undefined, undefined, undefined>.input: Schema.Struct<{
readonly question: Schema.String;
}>
input
:
import Schema
Schema
.
function Struct<{
readonly question: Schema.String;
}>(fields: {
readonly question: Schema.String;
}): Schema.Struct<{
readonly question: Schema.String;
}>

Defines a struct schema from a map of field schemas.

Details

Each field value is a schema. Use

optionalKey

or

optional

to mark fields as optional, and

mutableKey

to mark them as mutable.

The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

Example (Defining a basic struct)

import { Schema } from "effect"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
email: Schema.optionalKey(Schema.String)
})
// { readonly name: string; readonly age: number; readonly email?: string }
type Person = typeof Person.Type
Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

@category ― constructors

@since ― 3.10.0

Struct
({
question: Schema.String
question
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

Schema for string values. Validates that the input is typeof "string".

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
}),
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<{ readonly complete: Tool<...>; }>, undefined, undefined, undefined>.output: Schema.Struct<{
readonly answer: Schema.String;
}>
output
:
const Answer: Schema.Struct<{
readonly answer: Schema.String;
}>
Answer
,
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<...>, undefined, undefined, undefined>.instructions: "Answer the question using the complete tool."
instructions
: "Answer the question using the complete tool.",
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<{ readonly complete: Tool<...>; }>, undefined, undefined, undefined>.toolkit: Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>
toolkit
:
const Tools: Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>
Tools
,
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<{ readonly complete: Tool<...>; }>, undefined, undefined, undefined>.policy?: Partial<Readonly<Omit<{
readonly runStatus: "appended" | "off";
readonly maxTurns: number;
readonly maxToolCalls: number;
readonly maxDuration: Duration;
readonly toolConcurrency: number;
readonly repeatedFailureLimit: number;
readonly onExhaustion: "final-answer" | "fail";
readonly completionReserveTokens: number;
readonly toolResultBounds: ToolResultBounds;
readonly compaction: CompactionPolicy;
readonly tokenBudget?: number | undefined;
readonly costBudgetMicrousd?: number | undefined;
readonly contextTokenLimit?: number | undefined;
readonly restartOnJoinedInput?: boolean | undefined;
readonly modelRetries?: number | undefined;
}, "runStatus" | ... 5 more ... | "compaction"> & {
...;
}>> | undefined
policy
: {
maxTurns?: number | undefined
maxTurns
: 3,
maxToolCalls?: number | undefined
maxToolCalls
: 3,
maxDuration?: Input | undefined

Finite, positive wall-clock duration accepted in any Effect Duration input form.

maxDuration
: "30 seconds",
},
DefinitionOptions<Struct<{ readonly question: String; }>, Struct<{ readonly answer: String; }>, "Answer the question using the complete tool.", Toolkit<...>, undefined, undefined, undefined>.completion?: (Agent.CompletionToolDeclaration<{
readonly answer: string;
}, void, {
readonly answer: string;
}> & {
readonly tool: "complete";
}) | undefined
completion
: {
tool: "complete"
tool
: "complete",
CompletionToolDeclaration<Parameters = unknown, Result = unknown, Output = unknown>.required?: boolean | undefined

Require native Tool use on every model Turn and this Tool for final completion.

required
: true,
CompletionToolDeclaration<{ readonly answer: string; }, void, { readonly answer: string; }>.project: (input: Agent.CompletionProjectionInput<{
readonly answer: string;
}, void>) => {
readonly answer: string;
}
project
: ({
parameters: {
readonly answer: string;
}
parameters
}) =>
parameters: {
readonly answer: string;
}
parameters
,
},
});
export const
const ToolsLive: Layer<Tool.Handler<"complete">, never, never>
ToolsLive
=
const Tools: Toolkit.Toolkit<{
readonly complete: Tool.Tool<"complete", {
readonly parameters: Schema.Struct<{
readonly answer: Schema.String;
}>;
readonly success: Schema.Void;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>
Tools
.
Toolkit<{ readonly complete: Tool<...>; }>.toLayer<{
complete: () => Effect.Effect<void, never, never>;
}, never, never>(build: {
complete: () => Effect.Effect<void, never, never>;
} | Effect.Effect<{
complete: () => Effect.Effect<void, never, never>;
}, never, never>): Layer<Tool.Handler<"complete">, never, never>

Converts a toolkit into a Layer containing handlers for each tool in the toolkit.

toLayer
({
complete: () => Effect.Effect<void, never, never>
complete
: () =>
import Effect
Effect
.
const void: Effect.Effect<void, never, never>
export void

Returns an effect that succeeds with void.

@category ― constructors

@since ― 2.0.0

void
});

Provide ToolsLive with the model and history services when running this definition. The output schema validates the projected value. With required: true, each turn must call a tool and completion must use the named tool. Without it, valid final assistant JSON may also complete the run. Exhaustion policy controls the last available turn.

A completion tool must be the only application call, after any other needed application tool results arrive. Completed provider work, such as hosted web search with a terminal result, may appear in the same response and still counts toward run budgets. When a model mixes a completion tool with other application calls, the engine rejects those application calls before any handler starts and returns one ModelProtocolError result per rejected call. Provider results are retained. The next ordinary turn can correct the declaration. Each rejected call counts toward maxToolCalls and repeatedFailureLimit; model usage, turns, cost, and duration remain charged to the same run. There is no separate retry allowance. A batch of three rejected calls can therefore exhaust the default repeated-failure limit immediately. Budget exhaustion still controls finalization, and an invalid finalization batch fails without another correction.

This correction also applies to completionFromTools. Provider calls without terminal results, context rollover mixed with other calls, and invalid application batches recovered as pending work still fail: pending durable calls cannot safely be treated as unexecuted. Recovery permits one completion call alongside recorded terminal provider results, using recorded application results when available and never replaying an uncertain ordinary side effect. Finalization after budget exhaustion still permits only the advertised completion tool, with no provider calls. Completed rejections are retained atomically with their failed results and survive recovery without replay. Ordinary valid tool batches keep their configured concurrency. ToolCallFailed events identify each rejected call by name and ID; turn events show correction attempts and the terminal run event reports their outcome.

completion decides how a run finishes. runDisposition labels its successful output for durable readers.

Use completionFromTools for an action whose committed result can satisfy the whole request, while retaining a separate completion tool for ordinary replies. Each declaration names a tool and provides a pure projector returning Option.some(output) or Option.none():

completionFromTools: [{
tool: "complete_action",
project: ({ parameters, result }) =>
parameters.wholeRequestSatisfied && result.status === "committed"
? Option.some(`Created [${result.name}](${result.href}).`)
: Option.none(),
}],

Import Option from effect. The tool’s schemas define the parameters and result in this example. Only opt in tools with an explicit whole-request contract; completing the first step of a larger request must not end the run. Return None for pending approval, partial, or otherwise insufficient results. A declared tool failure also continues normally. Invalid output or a throwing projector fails the run, rather than treating an unconfirmed result as success.

These action tools must be the only call in their batch and obey ordinary tool, turn, and token budgets. They cannot execute in the reserved finalization turn; the existing completion tool retains that role. Authorization, approvals, cancellation, and durable tool replay rules remain unchanged. Recovery re-evaluates the projector from canonical parameters and results, so it must be deterministic and perform no effects. Tool names must be distinct across completion declarations.

Use runDisposition when durable readers need an application-defined classification in addition to the framework settlement outcome.

const RunDisposition = Schema.Literal("application-complete");
const definition = Agent.make("support-triage", {
input: SupportRequest,
output: Resolution,
instructions,
toolkit: SupportToolkit,
policy,
runDisposition: {
schema: RunDisposition,
fromOutput: (resolution) => resolution.runDisposition,
},
});

The selector receives decoded output and may return undefined. The schema validates and encodes any returned value. Invalid output fails with AgentRunDispositionError.

Only an ordinary completed durable run stores the encoded disposition on SubmissionSettled.runDisposition. Budget exhaustion, failure, abort, and incomplete recovery store none. Parse it with the same application schema. Do not infer completion from prose or tool success.

The string passed to Agent.make becomes a branded AgentId and contributes to durable identity and definition digests. Renaming it creates a new identity. Before 1.0, stored data has no migration promise.

For a reusable validated policy value, import the Schema declaration directly:

import {
class AgentPolicy

Schema class for finite agent run limits.

AgentPolicy
} from "@yielded/agent/agent-policy";
class AgentPolicy

Schema class for finite agent run limits.

AgentPolicy
.
AgentPolicy.make(input: AgentPolicyInput): AgentPolicy

Normalize and validate finite policy bounds, throwing on invalid input.

make
({
maxTurns: number
maxTurns
: 12,
maxToolCalls: number
maxToolCalls
: 24,
maxDuration: Input

Finite, positive wall-clock duration accepted in any Effect Duration input form.

maxDuration
: "5 minutes",
toolConcurrency: number
toolConcurrency
: 4,
repeatedFailureLimit?: number | undefined

Non-negative repeated-failure bound; defaults to 3.

repeatedFailureLimit
: 3,
tokenBudget?: number | undefined
tokenBudget
: 80_000,
costBudgetMicrousd?: number | undefined
costBudgetMicrousd
: 2_000_000,
onExhaustion?: "final-answer" | "fail" | undefined

Turn/Tool-Call/token exhaustion resolution; defaults to "final-answer".

onExhaustion
: "final-answer",
});

Turns, tool calls, duration, and concurrency need positive finite bounds. Token and cost budgets are optional because some models do not report enough usage data.

The default onExhaustion: "final-answer" allows one constrained final answer for turn, tool call, or token exhaustion. The result uses finishReason: "budget-exhausted". See Budgets & bounded autonomy for all exhaustion rules and Context management for tool result bounds, run status, context limits, and compaction.