Skip to content

Build agents

Tools & layers

Define tools and toolkits with Effect AI. Yielded Agent runs their native handlers under its scheduling, policy, and thread rules.

const Search = Tool.make("search", {
parameters: SearchQuery,
success: SearchResult,
failure: SearchUnavailable,
failureMode: "error",
dependencies: [SearchIndex],
});
const Tools = Toolkit.make(Search);
const ToolsLive = Tools.toLayer({
search: (query) => Effect.flatMap(SearchIndex, (_) => _.search(query)),
});

The tool declaration owns parameter, success, and failure schemas, approval, dependencies, failure mode, and preliminary results. The runtime decodes every model-generated tool call through that declaration.

Tool successes with a Schema.Void encoding, including the default, appear as JSON null in model history and programmatic broker results. Handler return types stay unchanged. A custom encoding to another JSON value takes precedence.

Decision from effect/ai defines typed assessments with an input Schema and named decisions. A DecisionModel supplies the evaluator through a provider Layer, such as Jev. Application code owns the next state, routing policy, and side effects.

flowchart LR
  accTitle: Decisions and application state transitions
  accDescr: Input and a Decision pass through a DecisionModel to produce typed answers and an application action. A provider Layer supplies the DecisionModel.
  input["input + Decision"] --> model["DecisionModel"] --> answers["typed answers"] --> action["application action"]
  provider["provider Layer"] --> model
Query Use it to
classify Select a named option and inspect its probability distribution
rate Rate input along ordered levels, allowing fractional scores
probability Estimate whether a proposition is true, from 0 to 1
import {
import Decision
Decision
,
import DecisionModel
DecisionModel
} from "effect/ai";
import {
import Effect
Effect
,
import Schema
Schema
} from "effect";
const
const TicketAssessment: Decision.Definition<Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>
TicketAssessment
=
import Decision
Decision
.
const make: <Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>(options: {
readonly input: Schema.Struct<{
readonly message: Schema.String;
}>;
readonly decisions: {
department: Decision.Classify<"billing" | "technical">;
};
}) => Decision.Definition<Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>

Creates a decision definition from an input schema and named decisions. DecisionModel.decide encodes the input with Schema.toCodecJson before calling the provider. Answer keys and types are inferred from the decisions. Throws if decisions is empty.

Example (Defining ticket triage)

import { Schema } from "effect"
import { Decision } from "effect/ai"
const Ticket = Schema.Struct({
subject: Schema.String,
body: Schema.String
})
const TicketTriage = Decision.make({
input: Ticket,
decisions: {
department: Decision.classify({
instructions: "Which team should handle this",
criteria: { billing: "payments", technical: "bugs" }
}),
urgent: Decision.probability({
instructions: "The message is time-sensitive",
criteria: { false: "No time pressure", true: "Needs action now" }
})
}
})

@see ― Definition for the returned shape

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
({
input: Schema.Struct<{
readonly message: Schema.String;
}>
input
:
import Schema
Schema
.
function Struct<{
readonly message: Schema.String;
}>(fields: {
readonly message: Schema.String;
}): Schema.Struct<{
readonly message: 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
({
message: Schema.String
message
:
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
}),
decisions: {
department: Decision.Classify<"billing" | "technical">;
}
decisions
: {
department: Decision.Classify<"billing" | "technical">
department
:
import Decision
Decision
.
const classify: <"billing" | "technical">(options: {
readonly instructions: string;
readonly criteria: {
readonly billing: string;
readonly technical: string;
};
}) => Decision.Classify<"billing" | "technical">

Creates a classification decision from labelled criteria. Throws if fewer than two labels are supplied.

Example (Choosing a department)

import { Decision } from "effect/ai"
const department = Decision.classify({
instructions: "Which team should handle this",
criteria: {
billing: "payments",
technical: "bugs",
sales: "pricing"
}
})

@see ― rate for an ordered scale

@see ― probability for a single yes or no likelihood

@stability ― unstable

@category ― constructors

@since ― 4.0.0

classify
({
instructions: string
instructions
: "Which team should handle this ticket?",
criteria: {
readonly billing: string;
readonly technical: string;
}
criteria
: {
billing: string
billing
: "Payments and refunds",
technical: string
technical
: "Bugs and outages" },
}),
},
});
const
const assess: Effect.Effect<"billing" | "technical", AiError, DecisionModel.DecisionModel>
assess
=
import Effect
Effect
.
const gen: <Effect.Effect<DecisionModel.DecideResponse<{
department: Decision.Classify<"billing" | "technical">;
}>, AiError, DecisionModel.DecisionModel>, "billing" | "technical">(f: () => Generator<Effect.Effect<DecisionModel.DecideResponse<{
department: Decision.Classify<"billing" | "technical">;
}>, AiError, DecisionModel.DecisionModel>, "billing" | "technical", never>) => Effect.Effect<"billing" | "technical", AiError, DecisionModel.DecisionModel> (+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 answers: Decision.Answers<{
department: Decision.Classify<"billing" | "technical">;
}>
answers
} = yield*
import DecisionModel
DecisionModel
.
const decide: <Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>(definition: Decision.Definition<Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>, options: DecisionModel.DecideOptions<Schema.Struct<{
readonly message: Schema.String;
}>>) => Effect.Effect<DecisionModel.DecideResponse<{
department: Decision.Classify<"billing" | "technical">;
}>, AiError, DecisionModel.DecisionModel>

Answers a decision definition using the current DecisionModel service. Encodes the input with Schema.toCodecJson, requiring the schema's encoding services. Explicit undefined fields become null; absent fields stay absent. Custom declarations need a JSON codec annotation or encoding fails. Returned answers and probability dictionaries have null prototypes.

Example (Triaging a ticket)

import { Effect, Schema } from "effect"
import { Decision, DecisionModel } from "effect/ai"
const TicketTriage = Decision.make({
input: Schema.String,
decisions: {
urgent: Decision.probability({
instructions: "The message is time-sensitive",
criteria: { false: "No time pressure", true: "Needs action now" }
})
}
})
const program = Effect.gen(function*() {
const { answers, usage } = yield* DecisionModel.decide(TicketTriage, {
input: "My card was charged twice, please fix this today"
})
return { probability: answers.urgent.probability, usage }
})

@see ― DecisionModel for the service this function requires

@stability ― unstable

@category ― decisions

@since ― 4.0.0

decide
(
const TicketAssessment: Decision.Definition<Schema.Struct<{
readonly message: Schema.String;
}>, {
department: Decision.Classify<"billing" | "technical">;
}>
TicketAssessment
, {
DecideOptions<Struct<{ readonly message: String; }>>.input: {
readonly message: string;
}
input
: {
message: string
message
: "Please refund my duplicate charge." },
});
return
const answers: Decision.Answers<{
department: Decision.Classify<"billing" | "technical">;
}>
answers
.
department: Decision.ClassifyAnswer<"billing" | "technical">
department
.
ClassifyAnswer<"billing" | "technical">.label: "billing" | "technical"
label
; // "billing" | "technical"
});

Provide TypeSafeDecisionModel.model("jev-latest") with its client Layer to run assess. The complete decision example shows provider setup, all three queries, and an application state transition.

The input Schema encodes the data sent to the provider, so include only data it should receive. Questions in one evaluation are independent; a question that depends on another answer needs a subsequent evaluation. Probabilities inform application thresholds and do not grant permission to act. Choose retry and timeout policies explicitly. See the reference for options, evidence, and errors.

A native Effect AI tool can run a fixed Jev assessment inside its handler. The language model chooses when to call the tool; Jev answers the questions defined by the handler.

The tool example declares the input and result schemas, exposes AiError failures, and uses Toolkit.toLayer to call the native DecisionModel. Supply TicketToolsLive with your other handlers and a configured decision model Layer when executing the tool.

A large registered catalogue can contain hundreds of tools even when a request needs only two. ToolDiscovery.make adds an ordinary discover_tools tool. Start with common tools pinned, then expose matching schemas after discovery. All tools retain their native Effect AI definitions and handlers; omitting selection configuration and discovery preserves eager exposure.

import {
import Agent
Agent
,
import ToolDiscovery
ToolDiscovery
,
import ToolExposure
ToolExposure
} from "@yielded/agent";
import {
import Effect
Effect
,
import Layer
Layer
,
import Schema
Schema
} from "effect";
import {
import Tool
Tool
,
import Toolkit
Toolkit
} from "effect/ai";
const
const GetRecord: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
GetRecord
=
import Tool
Tool
.
const make: <"get_record", Schema.Struct<{
readonly id: Schema.String;
}>, Schema.String, Schema.Never, undefined, []>(name: "get_record", options?: {
readonly description?: string | undefined;
readonly parameters?: Schema.Struct<{
readonly id: Schema.String;
}> | undefined;
readonly success?: Schema.String | undefined;
readonly failure?: Schema.Never | undefined;
readonly failureMode?: undefined;
readonly dependencies?: [] | undefined;
readonly needsApproval?: Tool.NeedsApproval<Schema.Struct<{
readonly id: 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
("get_record", {
description?: string | undefined

An optional description explaining what the tool does.

description
: "Read one record by its ID.",
parameters?: Schema.Struct<{
readonly id: Schema.String;
}> | undefined

Schema defining the parameters this tool accepts.

parameters
:
import Schema
Schema
.
function Struct<{
readonly id: Schema.String;
}>(fields: {
readonly id: Schema.String;
}): Schema.Struct<{
readonly id: 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
({
id: Schema.String
id
:
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
}),
success?: Schema.String | undefined

Schema for successful tool execution results.

success
:
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
,
})
.
Tool<"get_record", { readonly parameters: Struct<{ readonly id: String; }>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>.annotate<never, string | undefined>(tag: Key<never, string | undefined>, value: string | undefined): Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>

Add an annotation to the tool.

annotate
(
import ToolExposure
ToolExposure
.
const ToolNamespace: Reference<string | undefined>

Trusted grouping metadata; never inferred from a Tool's name.

ToolNamespace
, "records")
.
Tool<"get_record", { readonly parameters: Struct<{ readonly id: String; }>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>.annotate<never, boolean>(tag: Key<never, boolean>, value: boolean): Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>

Add an annotation to the tool.

annotate
(
import ToolExposure
ToolExposure
.
const PinnedTool: Reference<boolean>

Remains selected while eligible. Host visibility and inherited grants may hide an explicit pin without failing the Run; discovery, context rollover and required completion stay mandatory.

PinnedTool
, true);
const
const SearchRecords: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
SearchRecords
=
import Tool
Tool
.
const make: <"search_records", Schema.Struct<{
readonly title: Schema.String;
}>, Schema.$Array<Schema.String>, Schema.Never, undefined, []>(name: "search_records", options?: {
readonly description?: string | undefined;
readonly parameters?: Schema.Struct<{
readonly title: Schema.String;
}> | undefined;
readonly success?: Schema.$Array<Schema.String> | undefined;
readonly failure?: Schema.Never | undefined;
readonly failureMode?: undefined;
readonly dependencies?: [] | undefined;
readonly needsApproval?: Tool.NeedsApproval<...> | 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
("search_records", {
description?: string | undefined

An optional description explaining what the tool does.

description
: "Search records by title.",
parameters?: Schema.Struct<{
readonly title: Schema.String;
}> | undefined

Schema defining the parameters this tool accepts.

parameters
:
import Schema
Schema
.
function Struct<{
readonly title: Schema.String;
}>(fields: {
readonly title: Schema.String;
}): Schema.Struct<{
readonly title: 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
({
title: Schema.String
title
:
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
}),
success?: Schema.$Array<Schema.String> | undefined

Schema for successful tool execution results.

success
:
import Schema
Schema
.
Array<Schema.String>(self: Schema.String): Schema.$Array<Schema.String>
export Array

Defines a ReadonlyArray schema for a given element schema.

Example (Defining an array of strings)

import { Schema } from "effect"
const schema = Schema.Array(Schema.String)
Schema.decodeUnknownSync(schema)(["a", "b", "c"]) // => ["a", "b", "c"]

@category ― constructors

@since ― 4.0.0

Array
(
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
),
}).
Tool<"search_records", { readonly parameters: Struct<{ readonly title: String; }>; readonly success: $Array<String>; readonly failure: Never; readonly failureMode: "error"; }, never>.annotate<never, string | undefined>(tag: Key<never, string | undefined>, value: string | undefined): Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>

Add an annotation to the tool.

annotate
(
import ToolExposure
ToolExposure
.
const ToolNamespace: Reference<string | undefined>

Trusted grouping metadata; never inferred from a Tool's name.

ToolNamespace
, "records");
const
const discovery: Readonly<{
tool: Tool.Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<readonly [Schema.Codec<never, never, never, never>, typeof ToolDiscovery.ToolDiscoveryError]>;
readonly failureMode: "error";
}, ToolExposure.CurrentToolCatalog>;
toolkit: Toolkit.Toolkit<...>;
handlers: Layer.Layer<...>;
}>
discovery
=
import ToolDiscovery
ToolDiscovery
.
const make: <Schema.Never, never>(options?: ToolDiscovery.Options<Schema.Never, never>) => Readonly<{
tool: Tool.Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<...>;
readonly failureMode: "error";
}, ToolExposure.CurrentToolCatalog>;
toolkit: Toolkit.Toolkit<...>;
handlers: Layer.Layer<...>;
}>

Build one native discover_tools Tool and its singleton Toolkit/handler Layer. Include the Tool beside the application's existing native Tools. The engine owns catalogue authority and applies successful native selections after a complete Tool batch; the handler mutates no exposure state. Default search matches all case-insensitive whitespace-separated terms against names, descriptions, methods and namespace hints, ordered by catalogue id.

make
({
Options<Failure extends Schema.Top = Never, Requirements = never>.maxResults?: number | undefined

Maximum returned catalogue entries, default 8 and at most 64.

maxResults
: 8,
Options<Failure extends Schema.Top = Never, Requirements = never>.maxResultBytes?: number | undefined

Complete JSON-encoded result budget, including any notice: default 32768, minimum 256, maximum 262144 UTF-8 bytes. Within the first maxResults candidates, retain whole matches in rank order when they fit; skip oversized matches and try later candidates. Overflow returns a successful result with an actionable notice, never partial schemas.

maxResultBytes
: 32_768,
Options<Failure extends Schema.Top = Never, Requirements = never>.namespaceDescriptions?: Readonly<Record<string, string>> | undefined

Short category hints returned only for namespaces present in the visible catalogue.

namespaceDescriptions
: {
records: string
records
: "Record lookup and title search" },
});
export const
const agent: Agent.Definition<Schema.String, Schema.String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit.Toolkit<{
readonly get_record: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly search_records: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly discover_tools: Tool.Tool<...>;
}>, undefined, undefined, undefined> & {
...;
}
agent
=
import Agent
Agent
.
function make<"record-assistant", Schema.String, Schema.String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit.Toolkit<{
readonly get_record: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly search_records: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<...>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly discover_tools: Tool.Tool<...>;
}>, undefined>(id: "record-assistant", options: Agent.DefinitionOptions<...> & {
...;
}): Agent.Definition<...> & {
...;
} (+3 overloads)

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

make
("record-assistant", {
DefinitionOptions<String, String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit<{ readonly get_record: Tool<"get_record", { readonly parameters: Struct<...>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>; readonly search_records: Tool<...>; readonly discover_tools: Tool<...>; }>, undefined, undefined, undefined>.input: Schema.String
input
:
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<String, String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit<{ readonly get_record: Tool<"get_record", { readonly parameters: Struct<...>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>; readonly search_records: Tool<...>; readonly discover_tools: Tool<...>; }>, undefined, undefined, undefined>.output: Schema.String
output
:
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<String, String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit<{ readonly get_record: Tool<"get_record", { readonly parameters: Struct<...>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>; readonly search_records: Tool<...>; readonly discover_tools: Tool<...>; }>, undefined, undefined, undefined>.instructions: "Use discover_tools to find missing tools. Return the answer as a JSON string."
instructions
: "Use discover_tools to find missing tools. Return the answer as a JSON string.",
DefinitionOptions<String, String, "Use discover_tools to find missing tools. Return the answer as a JSON string.", Toolkit<{ readonly get_record: Tool<"get_record", { readonly parameters: Struct<...>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>; readonly search_records: Tool<...>; readonly discover_tools: Tool<...>; }>, undefined, undefined, undefined>.toolkit: Toolkit.Toolkit<{
readonly get_record: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly search_records: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly discover_tools: Tool.Tool<...>;
}>
toolkit
:
import Toolkit
Toolkit
.
const make: <[Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>, Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>, Tool.Tool<"discover_tools", {
...;
}, ToolExposure.CurrentToolCatalog>]>(tools_0: Tool.Tool<...>, tools_1: Tool.Tool<...>, tools_2: Tool.Tool<...>) => Toolkit.Toolkit<...>

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 GetRecord: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
GetRecord
,
const SearchRecords: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
SearchRecords
,
const discovery: Readonly<{
tool: Tool.Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<readonly [Schema.Codec<never, never, never, never>, typeof ToolDiscovery.ToolDiscoveryError]>;
readonly failureMode: "error";
}, ToolExposure.CurrentToolCatalog>;
toolkit: Toolkit.Toolkit<...>;
handlers: Layer.Layer<...>;
}>
discovery
.
tool: Tool.Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<readonly [Schema.Codec<never, never, never, never>, typeof ToolDiscovery.ToolDiscoveryError]>;
readonly failureMode: "error";
}, ToolExposure.CurrentToolCatalog>
tool
),
DefinitionOptions<InputSchema extends Schema.Top, OutputSchema extends Schema.Top, Instructions extends InstructionSource<InputSchema["Type"], unknown, unknown>, ToolkitValue extends Toolkit.Any, RunDispositionValue extends RunDispositionDeclaration<OutputSchema["Type"], Schema.Top> | undefined = undefined, InputPromptValue extends InputPromptSource<InputSchema["Type"], unknown, unknown> | undefined = undefined, UpdatesSchema extends Schema.Top | undefined = undefined>.toolExposure?: ToolExposure.Configuration | undefined
toolExposure
: {
Configuration.initialToolNames?: readonly string[] | undefined
initialToolNames
: [],
Configuration.maxTools?: number | undefined
maxTools
: 16,
Configuration.maxSchemaBytes?: number | undefined
maxSchemaBytes
: 65_536 },
});
export const
const Handlers: Layer.Layer<Tool.Handler<"discover_tools"> | Tool.HandlersFor<{
readonly get_record: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly search_records: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, never, never>
Handlers
=
import Layer
Layer
.
const merge: <never, never, Tool.HandlersFor<{
readonly get_record: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
readonly search_records: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, never, never, Tool.Handler<...>>(self: Layer.Layer<...>, that: Layer.Layer<...>) => Layer.Layer<...> (+3 overloads)

Merges this layer with another layer concurrently, producing a new layer with combined input, error, and output types.

When to use

Use to combine an existing Layer with another Layer or an array of layers while preserving pipeline style.

Details

This is a binary version of mergeAll that merges exactly two layers or one layer with an array of layers. The layers are built concurrently and their outputs are combined.

Example (Merging two layers)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
const dbLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((_msg: string) => Effect.void)
})
const mergedLayer = Layer.merge(dbLayer, loggerLayer)
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, mergedLayer)) // => "result"

@see ― mergeAll for merging several layers at once

@category ― zipping

@since ― 2.0.0

merge
(
import Toolkit
Toolkit
.
const make: <[Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>, Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>]>(tools_0: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>, tools_1: Tool.Tool<...>) => Toolkit.Toolkit<...>

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 GetRecord: Tool.Tool<"get_record", {
readonly parameters: Schema.Struct<{
readonly id: Schema.String;
}>;
readonly success: Schema.String;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
GetRecord
,
const SearchRecords: Tool.Tool<"search_records", {
readonly parameters: Schema.Struct<{
readonly title: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>
SearchRecords
).
Toolkit<{ readonly get_record: Tool<"get_record", { readonly parameters: Struct<...>; readonly success: String; readonly failure: Never; readonly failureMode: "error"; }, never>; readonly search_records: Tool<...>; }>.toLayer<{
get_record: ({ id }: {
readonly id: string;
}) => Effect.Effect<string, never, never>;
search_records: ({ title }: {
readonly title: string;
}) => Effect.Effect<string[], never, never>;
}, never, never>(build: {
get_record: ({ id }: {
readonly id: string;
}) => Effect.Effect<string, never, never>;
search_records: ({ title }: {
readonly title: string;
}) => Effect.Effect<string[], never, never>;
} | Effect.Effect<{
get_record: ({ id }: {
readonly id: string;
}) => Effect.Effect<string, never, never>;
search_records: ({ title }: {
readonly title: string;
}) => Effect.Effect<string[], never, never>;
}, never, never>): Layer.Layer<...>

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

toLayer
({
get_record: ({ id }: {
readonly id: string;
}) => Effect.Effect<string, never, never>
get_record
: ({
id: string
id
}) =>
import Effect
Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@category ― constructors

@since ― 2.0.0

succeed
(`Record ${
id: string
id
}`),
search_records: ({ title }: {
readonly title: string;
}) => Effect.Effect<string[], never, never>
search_records
: ({
title: string
title
}) =>
import Effect
Effect
.
const succeed: <string[]>(value: string[]) => Effect.Effect<string[], never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@category ― constructors

@since ― 2.0.0

succeed
([`Matching record: ${
title: string
title
}`]),
}),
const discovery: Readonly<{
tool: Tool.Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<readonly [Schema.Codec<never, never, never, never>, typeof ToolDiscovery.ToolDiscoveryError]>;
readonly failureMode: "error";
}, ToolExposure.CurrentToolCatalog>;
toolkit: Toolkit.Toolkit<...>;
handlers: Layer.Layer<...>;
}>
discovery
.
handlers: Layer.Layer<Tool.Handler<"discover_tools">, never, never>
handlers
,
);

The first request exposes get_record and discover_tools. A call such as discover_tools({ query: "search", namespace: "records" }) returns metadata and encoded parameter and success schemas; search_records becomes callable on the next turn. Provide handlers for the full registered toolkit as before: hidden schemas do not remove requirements from R.

Default search matches every whitespace-separated query term, ignoring case, against names, descriptions, methods and namespace hints, with deterministic catalogue-ID ordering. Namespaces come from ToolNamespace annotations or Code Mode’s allowlist, never from parsing a tool name. Namespace hints appear only on eligible matches. Queries are bounded to 512 characters and exact namespace filters to 128. The default considers at most eight matches and returns at most 32 KiB of complete encoded JSON; limits can rise to 64 matches and 256 KiB. maxResultBytes measures UTF-8 bytes of the whole discovery result, including metadata, schemas, toolNames, JSON escaping, and any recovery notice. It is a host budget, not a provider requirement; size it against the actual catalogue. The engine’s tool-result and exposed-schema limits apply separately.

When the first maxResults candidates exceed that byte budget, discovery retains complete matches in rank order whenever they fit, skipping larger matches and trying later candidates within that count. It returns a successful result with a notice advising a narrower search or namespace. Schemas are never cut, and omitted matches do not activate tools. If no match fits, the result is { toolNames: [], matches: [], notice: "..." }; the model can continue, but the empty selection clears non-pinned tools. If a single tool still cannot fit, the notice advises asking the host to increase maxResultBytes.

Byte overflow no longer emits ToolDiscoveryError with reason limit-exceeded. Invalid catalogues, invalid selected schemas, and custom-search failures still propagate as errors. Hosted matches include providerName and requiresHandler. Remote-only tools return null for application parameter/result schemas; discovery still selects their native declarations. Provider configuration stays with the host and is not returned in discovery documentation.

The optional Effect callback receives only the eligible catalogue, already filtered by the exact namespace. Return ranked Descriptor.id values. Every ID is validated before limiting results; unknown or duplicate IDs fail closed. Native tools and each Code Mode alias have separate IDs.

import {
import ToolDiscovery
ToolDiscovery
} from "@yielded/agent";
import {
import Context
Context
,
import Effect
Effect
,
import Schema
Schema
} from "effect";
class
class SearchUnavailable
SearchUnavailable
extends
import Schema
Schema
.
const TaggedError: <SearchUnavailable, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<SearchUnavailable, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<SearchUnavailable, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<SearchUnavailable, readonly [...]> | undefined): Schema.Class<...>;
}

Defines a schema-backed yieldable error class with an automatically populated _tag field.

When to use

Use to define typed errors that are schema validated, yielded in Effect.gen, and matched as tagged union members.

Example (Defining a tagged error class)

import { Effect, Schema } from "effect"
class NotFound extends Schema.TaggedError<NotFound>()("NotFound", {
id: Schema.Number
}) {}
const program = Effect.gen(function*() {
yield* new NotFound({ id: 42 })
})
const error = await Effect.runPromise(Effect.flip(program))
error._tag // => "NotFound"
error.id // => 42

@category ― constructors

@since ― 3.10.0

TaggedError
<
class SearchUnavailable
SearchUnavailable
>()("SearchUnavailable", {
message: Schema.String
message
:
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
,
}) {}
class
class SearchIndex
SearchIndex
extends
import Context
Context
.
const Service: <SearchIndex, {
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}>() => <Identifier, E, R, Args>(id: Identifier, options?: {
readonly make?: ((...args: Args) => Effect.Effect<{
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}, E, R>) | Effect.Effect<{
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}, E, R> | undefined;
} | undefined) => Context.ServiceClass<...> & ([...] extends [...] ? unknown : {
...;
}) (+2 overloads)

Creates a Context service key.

When to use

Use when you need to define a context service key for a dependency that must be provided by the surrounding context.

Details

Call Context.Service("Key") for a function-style key, or use the two-stage form Context.Service<Self, Shape>()("Key") for class-style service declarations. The returned key can be yielded as an Effect and passed to Context.make, Context.add, and the Context getter functions.

Gotchas

The string key is the runtime identity of the service. Reusing the same key string for unrelated services makes them occupy the same slot in a Context.

Example (Creating service keys)

import { Context } from "effect"
// Create a simple service
const Database = Context.Service<{
query: (sql: string) => string
}>("Database")
// Create a service class
class Config extends Context.Service<Config, {
port: number
}>()("Config") {}
// Use the services to create contexts
const db = Context.make(Database, {
query: (sql) => `Result: ${sql}`
})
const config = Context.make(Config, { port: 8080 })
Context.get(db, Database).query("SELECT 1") // => "Result: SELECT 1"
Context.get(config, Config).port // => 8080

@see ― Reference for service keys with default values

@category ― services

@since ― 4.0.0

Service
<
class SearchIndex
SearchIndex
,
{
readonly
rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>
rank
: (
query: string
query
: string,
catalogue: readonly ToolDiscovery.Descriptor[]
catalogue
:
interface ReadonlyArray<T>
ReadonlyArray
<
import ToolDiscovery
ToolDiscovery
.
class Descriptor

Metadata given to custom search only after host visibility and inherited grants are applied.

Descriptor
>,
) =>
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that lazily describes a workflow or job. The workflow requires some context R, and may fail with an error of type E, or succeed with a value of type A.

When to use

Use when you need to represent a lazy, composable workflow that can require services, fail with a typed error, or succeed with a typed value.

Details

Effect values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability.

To run an Effect value, you need a Runtime, which is a type that is capable of executing Effect values.

@category ― models

@since ― 2.0.0

Effect
<
interface ReadonlyArray<T>
ReadonlyArray
<string>,
class SearchUnavailable
SearchUnavailable
>;
}
>()("SearchIndex") {}
export const
const discovery: Readonly<{
tool: Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<Schema.String>;
}>;
readonly failure: Schema.Union<readonly [Schema.Codec<SearchUnavailable, {
readonly _tag: "SearchUnavailable";
readonly message: string;
}, never, never>, typeof ToolDiscovery.ToolDiscoveryError]>;
readonly failureMode: "error";
}, CurrentToolCatalog>;
toolkit: Toolkit<...>;
handlers: Layer<...>;
}>
discovery
=
import ToolDiscovery
ToolDiscovery
.
const make: <typeof SearchUnavailable, SearchIndex>(options?: ToolDiscovery.Options<typeof SearchUnavailable, SearchIndex>) => Readonly<{
tool: Tool<"discover_tools", {
readonly parameters: Schema.Struct<{
readonly query: Schema.NonEmptyString;
readonly namespace: Schema.optionalKey<Schema.NonEmptyString>;
}>;
readonly success: Schema.Struct<{
readonly toolNames: Schema.$Array<Schema.NonEmptyString>;
readonly matches: Schema.$Array<typeof ToolDiscovery.Match>;
readonly notice: Schema.optionalKey<...>;
}>;
readonly failure: Schema.Union<...>;
readonly failureMode: "error";
}, CurrentToolCatalog>;
toolkit: Toolkit<...>;
handlers: Layer<...>;
}>

Build one native discover_tools Tool and its singleton Toolkit/handler Layer. Include the Tool beside the application's existing native Tools. The engine owns catalogue authority and applies successful native selections after a complete Tool batch; the handler mutates no exposure state. Default search matches all case-insensitive whitespace-separated terms against names, descriptions, methods and namespace hints, ordered by catalogue id.

make
({
Options<typeof SearchUnavailable, SearchIndex>.failure?: typeof SearchUnavailable | undefined

Schema of the custom search's expected failures; defaults to Schema.Never.

failure
:
class SearchUnavailable
SearchUnavailable
,
Options<typeof SearchUnavailable, SearchIndex>.search?: ((request: {
readonly query: string;
readonly namespace?: string | undefined;
}, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<readonly string[], SearchUnavailable, SearchIndex>) | undefined

Rank unique Descriptor.id values, never tool names. Native tools and each Code Mode alias have distinct identities. Every returned id is checked before the result limit is applied. The catalogue is already filtered by the optional exact namespace and current authority. Services are captured with the handler Layer; temporary resources close after each search.

search
: (
request: {
readonly query: string;
readonly namespace?: string | undefined;
}
request
,
catalogue: readonly ToolDiscovery.Descriptor[]
catalogue
) =>
import Effect
Effect
.
const flatMap: <{
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}, never, SearchIndex, readonly string[], SearchUnavailable, never>(self: Effect.Effect<{
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}, never, SearchIndex>, f: (a: {
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}) => Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

When to use

Use when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

Example (Choosing flatMap syntax variants)

import { Effect, pipe } from "effect"
const output: Array<unknown> = []
const myEffect = Effect.succeed(1)
const transformation = (n: number) => Effect.succeed(n + 1)
const flatMappedWithPipe = pipe(myEffect, Effect.flatMap(transformation))
const flatMappedWithDataFirst = Effect.flatMap(myEffect, transformation)
const flatMappedWithMethod = myEffect.pipe(Effect.flatMap(transformation))
void output.push(Effect.runSync(Effect.all([
flatMappedWithPipe,
flatMappedWithDataFirst,
flatMappedWithMethod
])))
output // => [[2, 2, 2]]

Example (Sequencing dependent effects)

import { Data, Effect, pipe } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
await Effect.runPromise(finalAmount) // => 95

@see ― tap for a version that ignores the result of the effect.

@category ― sequencing

@since ― 2.0.0

flatMap
(
class SearchIndex
SearchIndex
, (
index: {
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}
index
) =>
index: {
readonly rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>;
}
index
.
rank: (query: string, catalogue: ReadonlyArray<ToolDiscovery.Descriptor>) => Effect.Effect<ReadonlyArray<string>, SearchUnavailable>
rank
(
request: {
readonly query: string;
readonly namespace?: string | undefined;
}
request
.
query: string
query
,
catalogue: readonly ToolDiscovery.Descriptor[]
catalogue
)),
});

Provide SearchIndex when building discovery.handlers. Its requirements remain in the Layer’s R, and declared failures remain in the tool’s E alongside ToolDiscoveryError. Search runs in a fresh Scope per invocation; failure, defect, timeout and interruption close acquired resources.

An existing ordinary readonly search tool can use the same contract: annotate it with ToolExposure.DiscoveryTool and return a decoded toolNames array containing registered native names. The runtime validates that selection before recording it. Discovery tools must use the ToolExecutionClass annotation from @yielded/agent/durable-step with value "readonly"; uncertain and orchestration tools have different durable settlement paths and are refused.

Host context and workflow state can use the same mechanism directly:

import {
const RunToolVisibility: Reference<VisibilityHook | undefined>

Optional host policy captured by durable runtimes; action authorization remains independent.

RunToolVisibility
,
class Selection

One run-scoped replacement, independent of search and of model-visible result text.

Selection
} from "@yielded/agent/tool-exposure";
import type {
interface RunOptions<HookError = never, HookRequirements = never>

Per-Run values and advanced integration hooks. ThreadHistory.layer retains history incrementally in memory, including completed updates before a failure or interruption. PersistentHistory.layer commits only successful Runs to a ThreadStore. Hook failures and requirements stay visible in the returned Stream / Effect through the generic parameters.

RunOptions
} from "@yielded/agent/run-options";
import {
import Effect
Effect
,
import Layer
Layer
} from "effect";
export const
const options: RunOptions<never, never>
options
:
interface RunOptions<HookError = never, HookRequirements = never>

Per-Run values and advanced integration hooks. ThreadHistory.layer retains history incrementally in memory, including completed updates before a failure or interruption. PersistentHistory.layer commits only successful Runs to a ThreadStore. Hook failures and requirements stay visible in the returned Stream / Effect through the generic parameters.

RunOptions
= {
RunOptions<never, never>.toolSelection?: Selection | undefined

Initial or canonically restored run-scoped native selection.

toolSelection
:
class Selection

One run-scoped replacement, independent of search and of model-visible result text.

Selection
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<Selection, { readonly toolNames: readonly string[]; }, readonly [Struct<{ readonly toolNames: $Array<String>; }>], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly toolNames: readonly string[];
}, options?: MakeOptions): Selection

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
toolNames: readonly string[]
toolNames
: ["search_records"] }),
};
export const
const VisibilityLive: Layer.Layer<never, never, never>
VisibilityLive
=
import Layer
Layer
.
const succeed: <never, VisibilityHook | undefined>(service: Key<never, VisibilityHook | undefined>, resource: VisibilityHook | undefined) => Layer.Layer<never, never, never> (+1 overload)

Constructs a layer that provides a single service from an already available value.

When to use

Use when you need a Layer that provides a service from an already constructed implementation without effectful acquisition.

Example (Creating a layer from a service implementation)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, DatabaseLayer)) // => "Query result: SELECT 1"

@see ― sync for constructing layers from lazy values

@category ― constructors

@since ― 2.0.0

succeed
(
const RunToolVisibility: Reference<VisibilityHook | undefined>

Optional host policy captured by durable runtimes; action authorization remains independent.

RunToolVisibility
, {
VisibilityHook.visible: (request: VisibilityRequest) => Effect.Effect<ReadonlyArray<string>>
visible
: ({
toolNames: readonly string[]

Includes registered native names and explicitly advertised programmatic names.

toolNames
}) =>
import Effect
Effect
.
const succeed: <string[]>(value: string[]) => Effect.Effect<string[], never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@category ― constructors

@since ― 2.0.0

succeed
(
toolNames: readonly string[]

Includes registered native names and explicitly advertised programmatic names.

toolNames
.
ReadonlyArray<string>.filter(predicate: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): string[] (+1 overload)

Returns the elements of an array that meet the condition specified in a callback function.

@param ― predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.

filter
((
name: string
name
) =>
name: string
name
!== "delete_record")),
});

Pass these options to AgentRuntime.run, stream, or start. A context preparation hook may return toolSelection beside its prompt to replace the set before a new model request. Provide VisibilityLive around an ephemeral run or when constructing a durable runtime. The optional RunToolVisibility service defaults to no filter; durable hosts capture that choice, including absence, so worker callers cannot replace it. Resolve policy dependencies and setup failures in the host Layer, where their types remain visible. The policy operation returns eligible names; an empty list denies all tools.

Visibility controls eligibility. Exposure controls which eligible native schemas the model sees. Authorization, approval, budgets and resource checks still decide whether an action may execute. Visibility and inherited grants are applied before custom search receives any names or docs. Guessed native calls outside the original request exposure fail before handlers start. Code Mode also filters its sandbox method surface and denies hidden inner calls; resource authorization still belongs inside those handlers.

Selections last for one run and replace the non-pinned set; they do not accumulate. An empty successful selection clears it. If a batch contains several successful selections, the last in declaration order wins, regardless of completion order. No selection takes effect midway through a batch. Failed results retain the previous selection; ordinary tool error behavior still applies.

During working turns, eligible tools with explicit PinnedTool annotations stay exposed across selections. Host visibility and inherited grants may hide these common tools without failing the run. Discovery, required completion and context rollover tools are mandatory: excluding one causes a typed refusal. Exposed pins count toward the limits and never override eligibility. Optional completion is available in the final answer turn only when eligible; otherwise the model finishes with text. That turn may expose only the completion tool. The default exposure limits are 64 tools and 256 KiB of aggregate UTF-8 JSON declarations (names, descriptions and parameter schemas, including the current model’s schema transformation). Exceeding a limit fails with ModelProtocolError; there is no silent eviction beyond replacement.

The runtime records the actual request exposure with each canonical model response and each successful selection with its tool settlement, before result truncation. ToolCallSucceeded also carries toolSelection. Durable recovery and compaction restore this canonical metadata without searching again for a committed result. A crash before a result is committed follows the ordinary readonly recovery contract. Resumed calls retain their original exposure and recheck current eligibility before unfinished handlers run; already settled siblings remain canonical. Custom durability hooks must persist RunTurnResponse.toolExposure with the response. Version custom search semantics in your registration definitions as with other handler changes.

This provider-neutral API changes the native toolkit sent on subsequent calls. It does not use provider-specific deferred-tool references or promise a latency win: extra discovery rounds and provider prompt caching can outweigh smaller schemas. Measure common, uncommon and composed tasks against eager exposure before claiming a performance improvement.

The runtime validates the complete model response before starting any handler. It resolves tool names, validates parameters, checks budgets, and obtains approvals for executable calls in the whole batch.

It bounds both active call streams and handler execution by the resolved concurrency, using scoped child fibers and a finite Effect Semaphore. Pending calls do not allocate waiting stream fibers. Live progress follows actual completion order. Canonical history and the next model turn use declaration order. The model never sees a partial batch.

The default failureMode: "error" keeps a declared tool failure in the Effect error channel and fails the run. Declaring a failure Schema does not opt into recovery. Choose failureMode: "return" when the model should receive the failure as a tool result and decide what to do next:

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";
class
class SearchUnavailable
SearchUnavailable
extends
import Schema
Schema
.
const TaggedError: <SearchUnavailable, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<SearchUnavailable, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<SearchUnavailable, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<SearchUnavailable, readonly [...]> | undefined): Schema.Class<...>;
}

Defines a schema-backed yieldable error class with an automatically populated _tag field.

When to use

Use to define typed errors that are schema validated, yielded in Effect.gen, and matched as tagged union members.

Example (Defining a tagged error class)

import { Effect, Schema } from "effect"
class NotFound extends Schema.TaggedError<NotFound>()("NotFound", {
id: Schema.Number
}) {}
const program = Effect.gen(function*() {
yield* new NotFound({ id: 42 })
})
const error = await Effect.runPromise(Effect.flip(program))
error._tag // => "NotFound"
error.id // => 42

@category ― constructors

@since ― 3.10.0

TaggedError
<
class SearchUnavailable
SearchUnavailable
>()("SearchUnavailable", {
message: Schema.String
message
:
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 Search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>
Search
=
import Tool
Tool
.
const make: <"search", Schema.Struct<{
readonly query: Schema.String;
}>, Schema.$Array<Schema.String>, typeof SearchUnavailable, "return", []>(name: "search", options?: {
readonly description?: string | undefined;
readonly parameters?: Schema.Struct<{
readonly query: Schema.String;
}> | undefined;
readonly success?: Schema.$Array<Schema.String> | undefined;
readonly failure?: typeof SearchUnavailable | undefined;
readonly failureMode?: "return" | undefined;
readonly dependencies?: [] | undefined;
readonly needsApproval?: Tool.NeedsApproval<...> | 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
("search", {
parameters?: Schema.Struct<{
readonly query: Schema.String;
}> | undefined

Schema defining the parameters this tool accepts.

parameters
:
import Schema
Schema
.
function Struct<{
readonly query: Schema.String;
}>(fields: {
readonly query: Schema.String;
}): Schema.Struct<{
readonly query: 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
({
query: Schema.String
query
:
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
}),
success?: Schema.$Array<Schema.String> | undefined

Schema for successful tool execution results.

success
:
import Schema
Schema
.
Array<Schema.String>(self: Schema.String): Schema.$Array<Schema.String>
export Array

Defines a ReadonlyArray schema for a given element schema.

Example (Defining an array of strings)

import { Schema } from "effect"
const schema = Schema.Array(Schema.String)
Schema.decodeUnknownSync(schema)(["a", "b", "c"]) // => ["a", "b", "c"]

@category ― constructors

@since ― 4.0.0

Array
(
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
),
failure?: typeof SearchUnavailable | undefined

Schema for tool execution failures.

failure
:
class SearchUnavailable
SearchUnavailable
,
failureMode?: "return" | undefined

The strategy used for handling errors returned from tool call handler execution.

Details

If set to "error" (the default), errors that occur during tool call handler execution will be returned in the error channel of the calling effect.

If set to "return", errors that occur during tool call handler execution will be captured and returned as part of the tool call result.

failureMode
: "return",
});
const
const tools: Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>
tools
=
import Toolkit
Toolkit
.
const make: <[Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>]>(tools_0: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>) => Toolkit.Toolkit<...>

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 Search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>
Search
);
const
const ToolsLive: Layer<Tool.Handler<"search">, never, never>
ToolsLive
=
const tools: Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>
tools
.
Toolkit<{ readonly search: Tool<"search", { readonly parameters: Struct<{ readonly query: String; }>; readonly success: $Array<String>; readonly failure: typeof SearchUnavailable; readonly failureMode: "return"; }, never>; }>.toLayer<{
search: () => Effect.Effect<never, SearchUnavailable, never>;
}, never, never>(build: {
search: () => Effect.Effect<never, SearchUnavailable, never>;
} | Effect.Effect<{
search: () => Effect.Effect<never, SearchUnavailable, never>;
}, never, never>): Layer<Tool.Handler<"search">, never, never>

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

toLayer
({
search: () => Effect.Effect<never, SearchUnavailable, never>
search
: () =>
import Effect
Effect
.
const fail: <SearchUnavailable>(error: SearchUnavailable) => Effect.Effect<never, SearchUnavailable, never>

Creates an Effect that represents a recoverable error.

When to use

Use to explicitly signal a recoverable error in an Effect.

Details

The error keeps propagating unless it is handled. You can handle tagged errors with functions like

catchTag

or

catchTags

.

Example (Creating a failed effect)

import { Data, Effect } from "effect"
class OperationFailedError extends Data.TaggedError("OperationFailedError")<{}> {}
// ┌─── Effect<never, OperationFailedError, never>
// ▼
const failure = Effect.fail(
new OperationFailedError()
)
Effect.runSync(Effect.flip(failure))._tag // => "OperationFailedError"

@see ― succeed to create an effect that represents a successful value.

@category ― constructors

@since ― 2.0.0

fail
(
class SearchUnavailable
SearchUnavailable
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<SearchUnavailable, { readonly _tag: "SearchUnavailable"; readonly message: string; }, readonly [TaggedStruct<"SearchUnavailable", { ...; }>], { ...; }>, TaggedStruct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly message: string;
readonly _tag?: "SearchUnavailable" | undefined;
}, options?: Schema.MakeOptions): SearchUnavailable

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
message: string
message
: "Try another source." })),
});
const
const researcher: Agent.Definition<Schema.String, Schema.String, "Search, then answer. Try another source if search is unavailable.", Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "researcher";
}
researcher
=
import Agent
Agent
.
function make<"researcher", Schema.String, Schema.String, "Search, then answer. Try another source if search is unavailable.", Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>, undefined>(id: "researcher", options: Agent.DefinitionOptions<Schema.String, Schema.String, ... 4 more ..., undefined> & {
...;
}): Agent.Definition<...> & {
...;
} (+3 overloads)

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

make
("researcher", {
DefinitionOptions<String, String, "Search, then answer. Try another source if search is unavailable.", Toolkit<{ readonly search: Tool<"search", { readonly parameters: Struct<{ readonly query: String; }>; readonly success: $Array<String>; readonly failure: typeof SearchUnavailable; readonly failureMode: "return"; }, never>; }>, undefined, undefined, undefined>.input: Schema.String
input
:
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<String, String, "Search, then answer. Try another source if search is unavailable.", Toolkit<{ readonly search: Tool<"search", { readonly parameters: Struct<{ readonly query: String; }>; readonly success: $Array<String>; readonly failure: typeof SearchUnavailable; readonly failureMode: "return"; }, never>; }>, undefined, undefined, undefined>.output: Schema.String
output
:
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<String, String, "Search, then answer. Try another source if search is unavailable.", Toolkit<{ readonly search: Tool<"search", { readonly parameters: Struct<{ readonly query: String; }>; readonly success: $Array<String>; readonly failure: typeof SearchUnavailable; readonly failureMode: "return"; }, never>; }>, undefined, undefined, undefined>.instructions: "Search, then answer. Try another source if search is unavailable."
instructions
: "Search, then answer. Try another source if search is unavailable.",
DefinitionOptions<String, String, "Search, then answer. Try another source if search is unavailable.", Toolkit<{ readonly search: Tool<"search", { readonly parameters: Struct<{ readonly query: String; }>; readonly success: $Array<String>; readonly failure: typeof SearchUnavailable; readonly failureMode: "return"; }, never>; }>, undefined, undefined, undefined>.toolkit: Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>
toolkit
:
const tools: Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>
tools
,
});
import Agent
Agent
.
const inspectTools: (agent: Agent.AnyDefinition | Agent.Any) => ReadonlyArray<Agent.ToolInspection>

Inspect registered native Tools in declaration order without acquiring handlers or a Model. This is the full Definition toolkit, before run-specific visibility or exposure filtering. failureMode is Effect AI's configured mode, including its "error" default. Handler-level recovery, programmatic invocation and Subagent containment can change where failures go; consult execution diagnostics for the actual route. No arguments, results or services are read.

inspectTools
(
const researcher: Agent.Definition<Schema.String, Schema.String, "Search, then answer. Try another source if search is unavailable.", Toolkit.Toolkit<{
readonly search: Tool.Tool<"search", {
readonly parameters: Schema.Struct<{
readonly query: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: typeof SearchUnavailable;
readonly failureMode: "return";
}, never>;
}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "researcher";
}
researcher
);
// [{ name: "search", failureMode: "return", requiresHandler: true }]

With failureMode: "return", invalid JSON arguments for a native application tool also produce a failed result containing Effect AI’s AiError with reason ToolParameterValidationError. For example, query: Schema.NonEmptyString rejects { query: "" } and lets the model submit a corrected query in the same run. The rejected call does not request approval, acquire execution authorization, or invoke the handler. It still counts toward tool-call and failure budgets and emits ToolCallFailed with failureHandling: "returned-to-model", without ToolCallStarted.

The default failureMode: "error", unknown tools, malformed non-JSON response data, and invalid provider-executed parameters remain fatal. Valid transforming parameter codecs still supply decoded values to handlers and encoded values to history.

Durable responses retain explicit rejection evidence tied to the original arguments. Recovery returns that failure without executing the rejected call; other recorded parameters still undergo strict validation and unfinished calls still require current authorization. Custom durability hooks must persist RunTurnResponse.toolParameterRejections with the response and restore it through RunTurnResume.toolParameterRejections. A failed result by itself cannot excuse corrupt parameters.

Agent.inspectTools accepts a Definition or Binding and reads its registered native toolkit without starting a run or acquiring services. It includes tools outside the current exposure. Provider-executed tools have requiresHandler: false; their results do not pass through a local handler’s failure mode.

Inspect configuration and execution separately. A handler can catch its own errors, the programmatic broker can contain an error-channel failure, and Subagent containment has its own policy. A returned failure may still be followed by a run failure from a sibling, a repeated-failure limit, or another budget.

Failure boundary Behavior
Declared handler error, "error" Propagates the original typed error and fails the model-declared call’s run.
Declared handler error, "return" Encodes a failed tool result for the model; the loop may continue.
Handler defect or interruption Stays a defect or interruption under either mode.
Invalid result encoding Still fails even with "return".
Invalid native application parameters, "return" Returns a failed result for the model without invoking that handler.
Invalid parameters, "error", or invalid provider-executed parameters Fails the run before application handlers start.
Unknown or unexposed tool Fails the run before application handlers start.

ToolCallFailed.failureMode reports the native configuration when known. Its failureHandling reports the actual route: propagated or returned-to-model. Older events may omit these fields; absence means unknown. returned-to-model records the result path; delivery still requires the complete batch to commit and another model call. A failure event terminates that call, not necessarily the run. Use the run’s terminal event and Effect exit to determine the overall outcome.

Application tool spans and terminal logs carry effect_agent.tool.failure_mode and, on failure, effect_agent.tool.failure_handling. Programmatic calls use returned-to-caller when the broker returns a failure outcome, including a captured error-channel failure. A propagated defect is still propagated. These attributes contain no error payloads. Interruption alone emits no terminal tool failure log or failure-handling classification. Returned failures can also be reported through the recovered tool failure observer.

Represent an expected empty result as success with Option.none or an empty collection.

The agent policy sets the maximum concurrency. A run override can only reduce it.

const options = {
scheduling: toRunSchedulingHook(
{ mode: "sequential" },
(toolName) => toolName === "mutate_account",
),
};

Use sequential execution for mutating tools whose effects depend on order. Every other batch still has a finite concurrency limit.

Durable hosts provide RunToolScheduling from @yielded/agent/run-options when constructing the runtime. Its toolRequiresSequential predicate inserts barriers around those tools while independent neighboring calls run concurrently. The runtime captures this host choice across replacement attempts; a worker’s ambient reference cannot replace it. Ephemeral runs use the same reference unless RunOptions.scheduling is explicitly supplied.

Effect AI’s needsApproval marks a tool for approval. Yielded Agent turns its native request into a typed Effect service with stable run identity, normalized resource targets, a bounded preview, expiration, audit, and a deny or unresolved decision.

Approval occurs after parameter decoding and before any handler in the batch starts. The model cannot approve a tool call. Durable batches retain every required request before honoring decisions; a denial blocks the whole batch. Function-based predicates receive native readonly Effect AI history, including opaque tool values; leave that history unchanged. Approval decisions receive independent decoded arguments. Approval predicates accept non-JSON caller history, including undefined and Date values.

Use RunToolAuthorization to decide whether a native or programmatic application tool call may execute. Code Mode invokes the same policy for each inner call before reserving its budget or starting its handler. The request includes programmatic.parentToolCallId and programmatic.sequenceIndex; allowing the outer execution Tool does not grant permission to its inner Tools. This policy permits only the search tool:

import {
class RunToolAuthorization

Host action-time authority for native and programmatic application Tools. Implementations close over their dependencies at Layer construction and return a denial when execution is not authorized. A dependency or validation failure instead fails with AgentToolAuthorizationCheckError and retains its original Cause. Defects and interruption remain in the Effect Cause channel. Durable coordinators capture this service once and retain it across replacement Attempts. Ephemeral Runs also resolve this service at their Run boundary. A typed per-run RunOptions.toolAuthorization overrides it while retaining its own error and requirement channel.

RunToolAuthorization
} from "@yielded/agent/run-options";
import {
import Effect
Effect
,
import Layer
Layer
} from "effect";
export const
const searchOnly: RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>
searchOnly
=
class RunToolAuthorization

Host action-time authority for native and programmatic application Tools. Implementations close over their dependencies at Layer construction and return a denial when execution is not authorized. A dependency or validation failure instead fails with AgentToolAuthorizationCheckError and retains its original Cause. Defects and interruption remain in the Effect Cause channel. Durable coordinators capture this service once and retain it across replacement Attempts. Ephemeral Runs also resolve this service at their Run boundary. A typed per-run RunOptions.toolAuthorization overrides it while retaining its own error and requirement channel.

RunToolAuthorization
.
Service<RunToolAuthorization, RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>>.of(this: void, self: RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>): RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>
of
({
RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>.authorize: (request: RunToolAuthorizationRequest) => Effect.Effect<RunToolAuthorizationDecision, AgentToolAuthorizationCheckError, never>
authorize
: ({
call: RunToolCallDescriptor
call
}) =>
import Effect
Effect
.
const succeed: <{
_tag: "allowed";
reason?: undefined;
} | {
_tag: "denied";
reason: string;
}>(value: {
_tag: "allowed";
reason?: undefined;
} | {
_tag: "denied";
reason: string;
}) => Effect.Effect<{
_tag: "allowed";
reason?: undefined;
} | {
_tag: "denied";
reason: string;
}, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@category ― constructors

@since ― 2.0.0

succeed
(
call: RunToolCallDescriptor
call
.
RunToolCallDescriptor.toolName: string
toolName
=== "search"
? {
_tag: "allowed"
_tag
: "allowed" }
: {
_tag: "denied"
_tag
: "denied",
reason: string
reason
: "Only search is permitted." },
),
});
export const
const SearchOnlyLive: Layer.Layer<RunToolAuthorization, never, never>
SearchOnlyLive
=
import Layer
Layer
.
const succeed: <RunToolAuthorization, RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>>(service: Key<RunToolAuthorization, RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>>, resource: RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>) => Layer.Layer<RunToolAuthorization, never, never> (+1 overload)

Constructs a layer that provides a single service from an already available value.

When to use

Use when you need a Layer that provides a service from an already constructed implementation without effectful acquisition.

Example (Creating a layer from a service implementation)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, DatabaseLayer)) // => "Query result: SELECT 1"

@see ― sync for constructing layers from lazy values

@category ― constructors

@since ― 2.0.0

succeed
(
class RunToolAuthorization

Host action-time authority for native and programmatic application Tools. Implementations close over their dependencies at Layer construction and return a denial when execution is not authorized. A dependency or validation failure instead fails with AgentToolAuthorizationCheckError and retains its original Cause. Defects and interruption remain in the Effect Cause channel. Durable coordinators capture this service once and retain it across replacement Attempts. Ephemeral Runs also resolve this service at their Run boundary. A typed per-run RunOptions.toolAuthorization overrides it while retaining its own error and requirement channel.

RunToolAuthorization
,
const searchOnly: RunToolAuthorizationHook<AgentToolAuthorizationCheckError, never>
searchOnly
);

Provide SearchOnlyLive to AgentRuntime.run, stream, or start. A per-run toolAuthorization option overrides the provided policy and retains its own typed failures and service requirements. For durable execution, install SearchOnlyLive in the Node host, Cloudflare application, or custom runtime.

The policy receives run identity, encoded input, and the proposed call’s name, ID, parameters, and execution classification. Decode unknown input and parameters with the application’s schemas when checking resource access. Keep denial reasons safe to log.

The runtime checks each executable model-declared call after approval and before any handler in the batch starts. A denial fails with AgentToolAuthorizationDenied. If that durable Submission already has an abort intent, the runtime records the abort and settles it as aborted after joining attached children. Other failures retain their original handling. Recovery checks calls that still need execution; it reuses recorded results without executing or authorizing them again.

Return a denied decision only for an actual policy refusal. Its optional cause retains the original policy evidence privately; keep reason safe for model context. If storage, transport, or state validation prevents a check, fail with AgentToolAuthorizationCheckError from @yielded/agent/agent-error, supplying the tool identity, a safe message, and the original Effect cause. This distinct failure stops execution and is reported at the failed Run boundary. Do not convert interruption or defects into a denial.

FailureDiagnostic.Value and FailureDiagnostic.Cause from @yielded/agent/failure-diagnostic retain original local values and encode structured diagnostic data across private JSON boundaries. The projection preserves tags, messages, reason/code, stacks, nested causes and Effect failure kinds; it excludes arbitrary payload fields and redacts common credential forms. Diagnostics are private operator data, never a ready-made user message. Keep private payloads out of error text; applications can provide stricter text redaction to FailureDiagnostic.capture. Capture marks cycles and bounds explicitly rather than pretending to reconstruct the original error after transport. Use FailureDiagnostic.captureContext for bounded diagnostic correlation copies; shortened values end in [truncated], and the original identities remain in their owning records. Worker admission and message-delivery failures retain the same causal data: WorkerError.cause preserves live errors, and the delivery’s lastFailureDiagnostic survives retries and recovery. Retained worker inputs return MessageStatus with bounded delivery evidence, including pending retry and definite refusal, without exposing these diagnostics to the model. The delivery’s receipt and refusal/retry classification remain the authority for safe retry decisions. For tools using failureMode: "return", project these errors into a separate safe failure schema.

Omitting both the service and per-run hook allows calls without this additional host check. Durable hosts use RunToolAuthorization.allowAll by default. Install a policy before granting tools access to protected resources. Authenticate callers and authorize runtime operations as described in operations.

This hook does not authorize provider-executed calls. A denied Code Mode inner call returns a catchable ProgrammaticToolAuthorizationDenied outcome without consuming execution budget; other independent calls may already have completed. The broker also restricts calls to the eligible allowlist. Keep resource access checks inside handlers as appropriate for the application.

Process loss ends an active tool call in an ephemeral run. Durable hosts commit the model response and its normalized tool declarations before ordinary external effects. If the runtime cannot determine whether the effect happened, it records an Unknown Outcome and waits for an explicit resolution. It never replays the call automatically. See Persistence & durability.

Custom RunDurabilityHook implementations capture initial metadata in initialize and persist the interpreter’s RunTurnCommit facts through commitTurn. Handle Response, Settled, and Partial commits directly; partial commits retain closed siblings before child suspension. Public events and onHistory updates do not define durable commit boundaries. The required checkpoint Effect must propagate retained infrastructure failures before further execution or commits, including when no progress stream is observed.

McpClient.layer provides McpConnector over real transports. McpClient.McpHttpTransport.make speaks Streamable HTTP and needs HttpClient; McpClient.McpStdioTransport.make runs a local server process and needs ChildProcessSpawner, which NodeServices.layer supplies on Node.js. Both requirements stay in the Layer’s R.

import {
import Mcp
Mcp
,
import McpClient
McpClient
} from "@yielded/agent";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
import {
import Effect
Effect
,
import Layer
Layer
} from "effect";
const
const McpLive: Layer.Layer<Mcp.McpConnector, never, never>
McpLive
=
import McpClient
McpClient
.
const layer: <readonly [McpClient.McpServerTransport<HttpClient>]>(transports: readonly [McpClient.McpServerTransport<HttpClient>], options?: {
readonly clientInfo?: Implementation | undefined;
}) => Layer.Layer<Mcp.McpConnector, never, HttpClient>

McpConnector over real transports. Each connect acquires the transport in the caller's Scope, negotiates a protocol revision, lists tools within the request bounds, and returns dynamic Effect AI Tools plus the handler Layer that forwards their calls. The Layer requires the union of every transport's platform services.

layer
([
import McpClient
McpClient
.
const McpHttpTransport: {
readonly make: (options: McpClient.McpHttpTransportOptions) => McpClient.McpServerTransport<HttpClient>;
}

Streamable HTTP transport for one remote MCP server; requires HttpClient.

McpHttpTransport
.
make: (options: McpClient.McpHttpTransportOptions) => McpClient.McpServerTransport<HttpClient>
make
({
McpHttpTransportOptions.serverId: string
serverId
: "docs",
McpHttpTransportOptions.url: string
url
: "https://mcp.example.com/mcp" }),
]).
Pipeable.pipe<Layer.Layer<Mcp.McpConnector, never, HttpClient>, Layer.Layer<Mcp.McpConnector, never, never>>(this: Layer.Layer<Mcp.McpConnector, never, HttpClient>, ab: (_: Layer.Layer<Mcp.McpConnector, never, HttpClient>) => Layer.Layer<Mcp.McpConnector, never, never>): Layer.Layer<Mcp.McpConnector, never, never> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+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
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));
const
const program: Effect.Effect<{
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}, Mcp.McpConnectionError | Mcp.McpToolkitMismatch | Mcp.McpDiscoveryLimitExceeded, Mcp.McpConnector | Scope | Crypto>
program
=
import Effect
Effect
.
const gen: <Effect.Effect<{
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}, Mcp.McpConnectionError | Mcp.McpToolkitMismatch | Mcp.McpDiscoveryLimitExceeded, Mcp.McpConnector | Scope | Crypto>, {
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}>(f: () => Generator<Effect.Effect<{
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}, Mcp.McpConnectionError | ... 1 more ... | Mcp.McpDiscoveryLimitExceeded, Mcp.McpConnector | ... 1 more ... | Crypto>, {
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}, never>) => 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 connection: {
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}
connection
= yield*
import Mcp
Mcp
.
const connectMcp: (request: Mcp.McpConnectionRequest) => Effect.Effect<{
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}, Mcp.McpConnectionError | Mcp.McpToolkitMismatch | Mcp.McpDiscoveryLimitExceeded, Mcp.McpConnector | Scope | Crypto>

Acquire a native connection in Scope, enforce a Clock-controlled timeout, then validate discovery before exposing its Toolkit.

connectMcp
(
import Mcp
Mcp
.
class McpConnectionRequest

Requested hard limits for an adapter-owned MCP connection and discovery response.

expectedToolkitSchemaDigest pins the tool contract an Agent was authored against: when a connector derives its Toolkit from live discovery, a server that adds, removes, or reshapes a tool fails closed instead of silently changing what the model can call.

McpConnectionRequest
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<McpConnectionRequest, { readonly serverId: string; readonly maxToolCount: number; readonly maxToolDescriptionBytes: number; readonly maxDiscoveryBytes: number; readonly connectTimeoutMillis: number; readonly expectedToolkitSchemaDigest?: string | undefined; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly serverId: string;
readonly maxToolCount: number;
readonly maxToolDescriptionBytes: number;
readonly maxDiscoveryBytes: number;
readonly connectTimeoutMillis: number;
readonly expectedToolkitSchemaDigest?: string | undefined;
}, options?: MakeOptions): Mcp.McpConnectionRequest

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
serverId: string
serverId
: "docs",
maxToolCount: number
maxToolCount
: 16,
maxToolDescriptionBytes: number
maxToolDescriptionBytes
: 1_024,
maxDiscoveryBytes: number
maxDiscoveryBytes
: 65_536,
connectTimeoutMillis: number
connectTimeoutMillis
: 5_000,
}),
);
// Merge `connection.toolkit` into the agent's toolkit and provide
// `connection.handlers` with the application's other tool handlers.
return
const connection: {
handlers?: Layer.Layer<Handler<string>, never, never> | undefined;
discovery: Mcp.McpDiscovery;
toolkit: Any;
}
connection
;
});

Mcp.connectMcp negotiates a protocol revision, lists tools within the request bounds, and returns dynamic Effect AI tools whose handlers forward tools/call. Provide the returned handlers Layer wherever the agent runs. The connection lives in the caller’s Scope; closing it ends the session or stops the process. Server-initiated requests such as sampling and elicitation are declined, and event streams are not resumed after a disconnect. A stdio env is added to the inherited environment.

Remote tools stay ordinary tools: they are uncertain by default, need approval and authorization like any other tool, and receive an Unknown Outcome after process loss. Set trustToolAnnotations on a transport to let the server’s readOnlyHint and idempotentHint choose the execution class. A tool with isError fails the call with McpToolCallFailed. Set expectedToolkitSchemaDigest on the request to reject a server whose tools changed since the agent was authored.

Remote servers are untrusted input. Bound their tool descriptions and results with the request limits and toolResultBounds, supply credentials through the transport headers or HttpClient, and keep local server commands under application control.

Subagent.make exposes a child agent as a tool with explicit input and result projections. The Subagents guide covers definitions, model requirements, budgets, authority, failure handling, and durable child recovery.

Use WebSearch.native to let the agent’s own model search and answer in the same call:

import {
import WebSearch
WebSearch
} from "@yielded/agent";
import {
import OpenAiTool
OpenAiTool
} from "@effect/ai-openai";
const
const SearchTools: Toolkit<{
readonly OpenAiWebSearch: ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>;
}>
SearchTools
=
import WebSearch
WebSearch
.
const native: <ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>>(options: Pick<...>) => Toolkit<...>

Place hosted search in the agent's own model call; no handler or search model Layer is needed. Citations stay in native text annotations and sources. Configure the provider tool at the host.

native
({
tool: ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>

Native upstream hosted search tool, for example OpenAiTool.WebSearch or AnthropicTool.WebSearch_20250305.

tool
:
import OpenAiTool
OpenAiTool
.
const WebSearch: <undefined>(args: {
readonly filters?: {
readonly allowed_domains?: readonly string[] | null;
} | null;
readonly user_location?: {
readonly type?: "approximate";
readonly country?: string | null;
readonly region?: string | null;
readonly city?: string | null;
readonly timezone?: string | null;
} | null;
readonly search_context_size?: "high" | "low" | "medium";
}) => ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>

Defines the OpenAI Web Search tool that enables the model to search the web for information.

When to use

Use to enable OpenAI provider-defined web search for a model response.

Details

The tool accepts optional filters, user location, and search context size. Results include status and, when available, action. Only completed succeeds; other statuses produce failure results. Narrow search sources by type to read url for URL sources or name for API sources.

@see ― WebSearchPreview for the preview web search provider tool

@stability ― unstable

@category ― tools

@since ― 4.0.0

WebSearch
({
search_context_size?: "low" | "medium" | "high" | undefined
search_context_size
: "medium" }),
});

Use SearchTools as the agent’s toolkit or merge it with application tools. Supply the agent’s normal model Layer; native search needs no handler or separate search model. The host fixes provider options. Citations remain in native assistant text annotations and source events; ask the model to include source URLs when projecting an answer through an application tool. Search content and citation URLs remain untrusted.

Hosted calls and results are journaled with the model response and replayed without local execution. Hosted configuration participates in replay contracts. web_search, web_search_preview, and file_search are annotated Tool.Readonly, allowing joined input to restart disposable calls; other hosted tools keep restart disabled unless the host explicitly annotates them read-only. A lost or cancelled read request may run again and incur another charge.

usage.webSearchCalls counts observed hosted web searches alongside tokens, excluding OpenAI page-open and in-page-find actions. The cost estimator receives the same per-call count as request.webSearchCalls; add the provider’s search fee there. Missing legacy counts or unobserved interrupted work do not establish zero cost. With the pinned OpenAI adapter and store: false, subsequent calls omit hosted call/results and retain URL citation annotations on assistant text. Full stateless reconstruction of hosted search items is an upstream Effect gap.

For a separately selected search model, keep the existing nested mode. WebSearch.tool is an ordinary application tool with { query } input and a text, sources, and token usage result. Its handler uses a separately supplied LanguageModel. Include WebSearch.tool in the agent’s toolkit, then provide this handler Layer:

import {
import WebSearch
WebSearch
} from "@yielded/agent";
import * as
import Gateway
Gateway
from "@yielded/agent-platform-cloudflare/cloudflare-ai-gateway";
import {
import OpenAiClient
OpenAiClient
,
import OpenAiLanguageModel
OpenAiLanguageModel
,
import OpenAiTool
OpenAiTool
} from "@effect/ai-openai";
import {
import Layer
Layer
,
import Redacted
Redacted
} from "effect";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const gateway: {
accountId: string;
gatewayId: string;
apiToken: Redacted.Redacted<string>;
}
gateway
= {
accountId: string
accountId
: "your-account",
gatewayId: string
gatewayId
: "your-gateway",
apiToken: Redacted.Redacted<string>
apiToken
:
import Redacted
Redacted
.
const make: <string>(value: string, options?: {
readonly label?: string | undefined;
}) => Redacted.Redacted<string>

Creates a Redacted wrapper for a sensitive value.

When to use

Use to wrap a sensitive value so normal string, JSON, and inspection output is redacted.

Details

The wrapper redacts string, JSON, and inspection output to reduce accidental disclosure. The original value remains retrievable with Redacted.value until the wrapper is wiped or becomes unreachable.

Example (Creating a redacted value)

import { Redacted } from "effect"
const API_KEY = Redacted.make("1234567890")
String(API_KEY) // => "<redacted>"

@category ― constructors

@since ― 3.3.0

make
("your-cloudflare-token"),
};
const
const SearchLive: Layer.Layer<Handler<"WebSearch">, never, never>
SearchLive
=
import WebSearch
WebSearch
.
const layer: <ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>>(options: WebSearch.Options<...>) => Layer.Layer<...>

Supply the search LanguageModel to this Layer, independently of the calling agent's model. The native provider owns search execution and response parsing. This handler makes one bounded request and projects Effect AI sources into Result. It never executes local tools. Defects and interruption propagate; expected failures become failed tool results. Search model usage is returned for host accounting, not silently charged to the parent Run budget.

layer
({
Options<ProviderDefined<"openai.web_search", "OpenAiWebSearch", { readonly args: Struct<{ readonly filters: optionalKey<Union<readonly [Struct<{ readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>; }>, Null]>>; readonly user_location: optionalKey<Union<readonly [Struct<{ readonly type: optionalKey<Literal<"approximate">>; readonly country: optionalKey<Union<readonly [String, Null]>>; readonly region: optionalKey<Union<readonly [String, Null]>>; readonly city: optionalKey<Union<readonly [String, Null]>>; readonly timezone: optionalKey<Union<readonly [String, Null]>>; }>, Null]>>; readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>; }>; readonly parameters: Struct<{ readonly action: optionalKey<Union<readonly [Struct<{ readonly type: Literal<"search">; readonly query: optionalKey<String>; readonly queries: optionalKey<$Array<String>>; readonly sources: optionalKey<$Array<Union<readonly [Struct<{ readonly type: Literal<"url">; readonly url: String; }>, Struct<{ readonly type: Literal<"api">; readonly name: String; }>]>>>; }>, Struct<{ readonly type: Literal<"open_page">; readonly url: optionalKey<Union<readonly [String, Null]>>; }>, Struct<{ readonly type: Literal<"find_in_page">; readonly url: String; readonly pattern: String; }>]>>; }>; readonly success: Struct<{ readonly action: optionalKey<Union<readonly [Struct<{ readonly type: Literal<"search">; readonly query: optionalKey<String>; readonly queries: optionalKey<$Array<String>>; readonly sources: optionalKey<$Array<Union<readonly [Struct<{ readonly type: Literal ...: ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>

Native upstream hosted search tool, for example OpenAiTool.WebSearch or AnthropicTool.WebSearch_20250305.

tool
:
import OpenAiTool
OpenAiTool
.
const WebSearch: <undefined>(args: {
readonly filters?: {
readonly allowed_domains?: readonly string[] | null;
} | null;
readonly user_location?: {
readonly type?: "approximate";
readonly country?: string | null;
readonly region?: string | null;
readonly city?: string | null;
readonly timezone?: string | null;
} | null;
readonly search_context_size?: "high" | "low" | "medium";
}) => ProviderDefined<"openai.web_search", "OpenAiWebSearch", {
readonly args: Struct<{
readonly filters: optionalKey<Union<readonly [Struct<{
readonly allowed_domains: optionalKey<Union<readonly [$Array<String>, Null]>>;
}>, Null]>>;
readonly user_location: optionalKey<Union<readonly [Struct<{
readonly type: optionalKey<Literal<"approximate">>;
readonly country: optionalKey<Union<readonly [String, Null]>>;
readonly region: optionalKey<Union<readonly [String, Null]>>;
readonly city: optionalKey<Union<readonly [String, Null]>>;
readonly timezone: optionalKey<Union<readonly [String, Null]>>;
}>, Null]>>;
readonly search_context_size: optionalKey<Literals<readonly ["low", "medium", "high"]>>;
}>;
readonly parameters: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
}>;
readonly success: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literal<"completed">;
}>;
readonly failure: Struct<{
readonly action: optionalKey<Union<readonly [Struct<{
readonly type: Literal<"search">;
readonly query: optionalKey<String>;
readonly queries: optionalKey<$Array<String>>;
readonly sources: optionalKey<$Array<Union<readonly [Struct<{
readonly type: Literal<"url">;
readonly url: String;
}>, Struct<{
readonly type: Literal<"api">;
readonly name: String;
}>]>>>;
}>, Struct<{
readonly type: Literal<"open_page">;
readonly url: optionalKey<Union<readonly [String, Null]>>;
}>, Struct<{
readonly type: Literal<"find_in_page">;
readonly url: String;
readonly pattern: String;
}>]>>;
readonly status: Literals<("failed" | "in_progress" | "incomplete" | "searching")[]>;
}>;
readonly failureMode: "error";
}, false>

Defines the OpenAI Web Search tool that enables the model to search the web for information.

When to use

Use to enable OpenAI provider-defined web search for a model response.

Details

The tool accepts optional filters, user location, and search context size. Results include status and, when available, action. Only completed succeeds; other statuses produce failure results. Narrow search sources by type to read url for URL sources or name for API sources.

@see ― WebSearchPreview for the preview web search provider tool

@stability ― unstable

@category ― tools

@since ― 4.0.0

WebSearch
({
search_context_size?: "low" | "medium" | "high" | undefined
search_context_size
: "medium" }),
Options<T extends AnyProviderDefined = AnyProviderDefined>.timeoutMillis?: number | undefined

One model request, with no automatic retries; defaults to 30 seconds.

timeoutMillis
: 30_000,
Options<T extends AnyProviderDefined = AnyProviderDefined>.maxOutputBytes?: number | undefined

Maximum encoded result size, including citations and usage; defaults to 32 KiB.

maxOutputBytes
: 32 * 1024,
}).
Pipeable.pipe<Layer.Layer<Handler<"WebSearch">, never, LanguageModel>, Layer.Layer<Handler<"WebSearch">, never, OpenAiClient.OpenAiClient>, Layer.Layer<Handler<"WebSearch">, never, HttpClient>, Layer.Layer<Handler<"WebSearch">, never, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<Handler<"WebSearch">, never, LanguageModel>) => Layer.Layer<Handler<"WebSearch">, never, OpenAiClient.OpenAiClient>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>, cd: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <OpenAiClient.OpenAiClient, never, LanguageModel | ProviderName | ModelName>(that: Layer.Layer<LanguageModel | ProviderName | ModelName, never, OpenAiClient.OpenAiClient>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, OpenAiClient.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
(
import OpenAiLanguageModel
OpenAiLanguageModel
.
const model: (model: (string & {}) | OpenAiLanguageModel.Model, config?: Omit<{
readonly metadata?: {
readonly [x: string]: string;
} | undefined;
readonly top_logprobs?: number | undefined;
readonly temperature?: number | undefined;
readonly top_p?: number | undefined;
readonly user?: string | undefined;
readonly prompt_cache_key?: string | undefined;
readonly prompt_cache_options?: {
readonly mode?: "explicit" | "implicit" | undefined;
readonly ttl?: "30m" | undefined;
} | undefined;
... 17 more ...;
readonly useItemReferences?: boolean | undefined;
}, "model">) => Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>

Creates an OpenAI model descriptor that can be provided with Effect.provide.

When to use

Use when you want an OpenAI language model value that carries provider and model metadata and can be supplied directly to an Effect program.

@see ― layer for creating a LanguageModel.LanguageModel layer directly

@see ― make for constructing the language model service effectfully

@stability ― unstable

@category ― constructors

@since ― 4.0.0

model
("openai/gpt-6-luna", {
max_output_tokens?: number | undefined
max_output_tokens
: 2_048,
store?: boolean | undefined
store
: false,
}),
),
import Gateway
Gateway
.
const provide: <OpenAiClient.OpenAiClient, never, HttpClient>(clientLayer: (options: Gateway.ClientOptions) => Layer.Layer<OpenAiClient.OpenAiClient, never, HttpClient>, options: Gateway.RouteOptions) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, HttpClient | Exclude<RIn2, OpenAiClient.OpenAiClient>>

Provide a Gateway-configured upstream client directly in a Layer pipeline. Pass the client's layer factory, or a callback adding client-specific options. The factory's errors and remaining services (such as HttpClient) stay visible. Model selection and resource ownership remain with the supplied upstream Layers.

@example

AnthropicLanguageModel.model("claude-haiku-4-5").pipe(
Gateway.provide(AnthropicClient.layer, { ...options, provider: "anthropic" }),
)

provide
(
import OpenAiClient
OpenAiClient
.
const layer: (options: OpenAiClient.Options) => Layer.Layer<OpenAiClient.OpenAiClient, never, HttpClient>

Creates a layer for the OpenAI client with the given options.

When to use

Use when you already have explicit Options values, such as an API key or custom API URL, and want to provide OpenAiClient as a Layer.

@see ― make for constructing the client service effectfully

@see ― layerConfig for loading client settings from Config

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
, { ...
const gateway: {
accountId: string;
gatewayId: string;
apiToken: Redacted.Redacted<string>;
}
gateway
,
RestOptions.protocol: "responses" | "chat-completions" | "messages"

Matches the paths appended by the upstream Effect client.

protocol
: "responses" }),
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+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
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
),
);

Load real gateway credentials from your host configuration or secret store. For Anthropic, select AnthropicTool.WebSearch_20250305({ maxUses: 3 }), provide an AnthropicLanguageModel Layer, and use Gateway.provide(AnthropicClient.layer, { ...gateway, provider: "anthropic" }). Direct provider clients work too. See the Cloudflare guide for gateway configuration.

Each invocation makes one model request, without handler retries. The host fixes the backend, native search options, deadline (1–300,000 ms), and encoded result limit (1–1,048,576 bytes). Queries are bounded to 8,192 characters and results to 64 source citations. A missing completed search, provider error, invalid result, or exceeded limit returns WebSearchFailure. Defects and interruption propagate; timeout interrupts the in-flight request. Search results and source URLs remain untrusted, and a citation grants no permission to fetch it. No provider payload or credential is included in the tool result. Model-call telemetry remains upstream Effect AI’s; the wrapper adds the WebSearch.search span without logging queries or responses itself.

Search is separately billed. Returned token counts use null when unavailable and are not added to the parent Run’s model usage or spending limit. Configure provider output limits and host billing controls. Both modes work with Gateway client configuration. The ordinary WebSearch tool remains uncertain for recovery: an unresolved call is not replayed automatically after ownership loss.

Use WebCapture.make, WebCapture.makeScrape, or WebCapture.makeExtract to expose authorized page capture as Effect AI Tools. The browser guide shows how to supply capture and crawl adapters, take screenshots, and open scoped interactive passes, including Live View and handoff.

Choose a Worker binding or a Node-safe REST adapter in capture and crawl. Structured extraction requires explicit Workers AI authorization and accounting.

The interactive browser walkthrough covers Layer setup, network policies, bounded actions, and session cleanup.

Code Mode lets an agent write bounded JavaScript that calls an allowlisted set of read-only Tools through an isolated executor. Sandbox execution covers structured process requests and the trusted local adapter.