Skip to content

Reference

Subagent policies and recovery

Start with the subagent overview to choose a lifecycle, then follow the in-memory attached, durable attached, or background worker guide. This reference covers advanced configuration shared by those guides.

For the default input and result contract, the declaration is optional:

import {
import Subagent
Subagent
} from "@yielded/agent";
import {
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
} from "./background-updates.ts";
// Before: an explicit declaration, with the default name and mappings.
const
const declaration: Subagent.SubagentDelegation<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, {
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}, ... 5 more ..., "error"> & {
...;
}
declaration
=
import Subagent
Subagent
.
make<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, {
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}, Struct<...>, Struct<...>, Never, never, never, Definition<...> & {
...;
}>(name: "hotel-researcher", options: Omit<...> & ... 1 more ... & {
...;
}): Subagent.SubagentDelegation<...> & {
...;
} (+1 overload)
export make

Expose one child Agent as an attached Effect AI Tool. The nonempty application name is preserved as both Tool name and delegation identity; no prefix is required. Parameters and result projections default to the child's input and result envelope. Throws when the name is empty. Nested Tool visibility follows the effective inherited grant.

make
("hotel-researcher", {
target: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}>, RunDispositionDeclaration<...> | undefined, unknown, Top | undefined> & Definition<...> & {
...;
}

The model-agnostic child Agent Definition this delegation targets (SUB-002).

target
:
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
});
const
const before: Readonly<{
tools: Subagent.BackgroundTools<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly output: Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>;
readonly budgetExhausted: Boolean;
}>, Never, {
readonly start: true;
readonly reportToParent: true;
}>;
toolkit: Toolkit<Subagent.BackgroundTools<"hotel-researcher", Struct<...>, Struct<...>, Never, {
readonly start: true;
readonly reportToParent: true;
}>>;
layer: Layer<...>;
}>
before
=
import Subagent
Subagent
.
function background<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly output: Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>;
readonly budgetExhausted: Boolean;
}>, Never, never, never, {
...;
}>(declaration: Declaration<...>, selected: {
...;
}): Readonly<...> (+1 overload)

Derive selected background Tools directly from a child Agent, using its ID as the delegation name and its input/output Schemas as the default contract. Pass an explicit Subagent.make declaration to customize the name, projections, grants, or policy bounds.

background
(
const declaration: Subagent.SubagentDelegation<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, {
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}, ... 5 more ..., "error"> & {
...;
}
declaration
, {
start: true
start
: true,
reportToParent: true
reportToParent
: true });
// After: the same generated hotel-researcher_start Tool and exact target.
const
const after: Readonly<{
tools: Subagent.BackgroundTools<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly output: Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>;
readonly budgetExhausted: Boolean;
}>, Never, {
readonly start: true;
readonly reportToParent: true;
}>;
toolkit: Toolkit<Subagent.BackgroundTools<"hotel-researcher", Struct<...>, Struct<...>, Never, {
readonly start: true;
readonly reportToParent: true;
}>>;
layer: Layer<...>;
}>
after
=
import Subagent
Subagent
.
function background<"hotel-researcher", Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, {
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}, {
...;
}>(target: Definition<...> & {
...;
}, selected: {
...;
}): Readonly<...> (+1 overload)

Derive selected background Tools directly from a child Agent, using its ID as the delegation name and its input/output Schemas as the default contract. Pass an explicit Subagent.make declaration to customize the name, projections, grants, or policy bounds.

background
(
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof UpdateError;
readonly failureMode: "return";
}, Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
, {
start: true
start
: true,
reportToParent: true
reportToParent
: true });

Use Subagent.make when you need another name, input/result projections, grants, or budgets. The host registers the original Agent definition; background configuration does not derive a replacement Agent. Explicit declarations can customize result projection; background reporting uses standard messages.

Subagent.make("research", { target: researcher }) is enough to declare delegation. researcher is an ordinary Agent Definition with input and output Schemas and an explicit toolkit.

The tool parameters use the child’s input Schema. The default result is { output: ChildOutput, budgetExhausted: boolean }. Identity input mapping works on decoded values, including transformed Schemas. Expected child failures become a bounded SubagentExecutionFailure with the error tag, without exposing the raw child error.

Set parameters and prepareInput when the parent’s request differs from the child’s input. Set success and projectResult when only part of the child output should be exposed. Set failure and mapChildFailure for application-specific errors. These customization points are independent. Missing mappings validate the default value against the selected Schema and fail with SubagentProjectionFailure if it does not fit.

For example, replace the walkthrough’s delegation.ts with this declaration to omit research notes from the parent result, expose a partial flag, and set explicit child limits:

delegation-custom.ts
import {
import Subagent
Subagent
} from "@yielded/agent";
import {
import Effect
Effect
,
import Schema
Schema
} from "effect";
import {
const Researcher: Definition<Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, undefined, undefined, undefined> & {
...;
}
Researcher
} from "./researcher.ts";
export class
class ResearchFailed
ResearchFailed
extends
import Schema
Schema
.
const TaggedError: <ResearchFailed, {}>(identifier?: string) => {
<Tag, Fields>(tag: Tag, fields: Fields, annotations?: Schema.Annotations.Declaration<ResearchFailed, readonly [Schema.TaggedStruct<Tag, Fields>]> | undefined): Schema.Class<ResearchFailed, Schema.TaggedStruct<Tag, Fields>, YieldableError>;
<Tag, S>(tag: Tag, schema: S, annotations?: Schema.Annotations.Declaration<ResearchFailed, readonly [Schema.Struct<{ [K in keyof ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])]: ({
readonly _tag: Schema.tag<Tag>;
} & S["fields"])[K]; }>]> | 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 ResearchFailed
ResearchFailed
>()("ResearchFailed", {
reason: Schema.String
reason
:
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
,
}) {}
export const
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
=
import Subagent
Subagent
.
make<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, Schema.Struct<...>, Schema.Struct<...>, typeof ResearchFailed, never, never, Definition<...> & {
...;
}>(name: "delegate_research_activities", options: Omit<...> & ... 1 more ... & {
...;
}): Subagent.SubagentDelegation<...> & {
...;
} (+1 overload)
export make

Expose one child Agent as an attached Effect AI Tool. The nonempty application name is preserved as both Tool name and delegation identity; no prefix is required. Parameters and result projections default to the child's input and result envelope. Throws when the name is empty. Nested Tool visibility follows the effective inherited grant.

make
("delegate_research_activities", {
description?: string | undefined

Model-visible description of the delegated capability.

description
: "Delegate activity research for one city and focus. Returns a shortlist.",
target: Definition<Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, RunDispositionDeclaration<...> | undefined, unknown, Schema.Top | undefined> & Definition<...> & {
...;
}

The model-agnostic child Agent Definition this delegation targets (SUB-002).

target
:
const Researcher: Definition<Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}>, undefined, undefined, undefined> & {
...;
}
Researcher
,
success?: Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly partial: Schema.Boolean;
}> | undefined
success
:
import Schema
Schema
.
function Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly partial: Schema.Boolean;
}>(fields: {
readonly activities: Schema.$Array<Schema.String>;
readonly partial: Schema.Boolean;
}): Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly partial: Schema.Boolean;
}>

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
({
activities: Schema.$Array<Schema.String>
activities
:
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
),
partial: Schema.Boolean
partial
:
import Schema
Schema
.
const Boolean: Schema.Boolean

Type-level representation of

Boolean

.

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

When to use

Use to validate values that are already JavaScript booleans.

@category ― models

@since ― 4.0.0

@see ― BooleanFromBit for a schema that decodes bit literals 0 or 1 into a boolean

@category ― schemas

@since ― 4.0.0

Boolean
,
}),
failure?: typeof ResearchFailed | undefined
failure
:
class ResearchFailed
ResearchFailed
,
failureMode?: "error" | undefined

Expected-failure resolution (SUB-033): "error" (default) fails the parent Tool batch; "return" contains the declared failure and the framework failure family as model-visible result data while ToolCallWaiting/SubagentDurabilityError stay in the error channel.

failureMode
: "error",
projectResult?: ((output: {
readonly activities: readonly string[];
readonly researchNotes: string;
}, context: Subagent.SubagentResultContext, parameters: {
readonly city: string;
readonly focus: string;
}) => Effect.Effect<{
readonly activities: readonly string[];
readonly partial: boolean;
}, ResearchFailed | Subagent.SubagentProjectionFailure, never>) | undefined
projectResult
: (
output: {
readonly activities: readonly string[];
readonly researchNotes: string;
}
output
, {
budgetExhausted: boolean
budgetExhausted
}) =>
import Effect
Effect
.
const succeed: <{
activities: readonly string[];
partial: boolean;
}>(value: {
activities: readonly string[];
partial: boolean;
}) => Effect.Effect<{
activities: readonly string[];
partial: boolean;
}, 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
({
activities: readonly string[]
activities
:
output: {
readonly activities: readonly string[];
readonly researchNotes: string;
}
output
.
activities: readonly string[]
activities
,
partial: boolean
partial
:
budgetExhausted: boolean
budgetExhausted
,
// researchNotes stays in the child's thread.
}),
policy?: Subagent.SubagentPolicy | undefined

Per-child ceilings. When omitted, reserve from the parent policy's shared delegation pool.

policy
:
import Subagent
Subagent
.
class SubagentPolicy
export SubagentPolicy

Finite delegation bounds declared by one Subagent capability. Structural limits are hard limits; token and cost caps are optional and enforced only as honestly as provider reporting allows.

SubagentPolicy
.
SubagentPolicy.make(input: Subagent.SubagentPolicyInput): Subagent.SubagentPolicy

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

make
({
maxChildren: number

Total invocation slots, including reserved descendants, available through this delegation budget.

maxChildren
: 2,
maxConcurrency: number

Concurrently executing children per parent Run.

maxConcurrency
: 2,
maxTurns: number

Model turns reserved for each child invocation.

maxTurns
: 4,
maxToolCalls: number

Tool Calls reserved for each child invocation.

maxToolCalls
: 4,
maxDuration: Input

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

maxDuration
: "30 seconds",
maxResultBytes?: number | undefined
maxResultBytes
: 4_096,
}),
});

Map child failures when constructing its handler Layer:

import {
import Subagent
Subagent
} from "@yielded/agent";
import {
import Layer
Layer
} from "effect";
import {
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
,
class ResearchFailed
ResearchFailed
} from "./delegation-custom.ts";
import {
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
} from "./node-agent.ts";
import {
const TravelToolsLive: Layer.Layer<Handler<"search_activities">, never, never>
TravelToolsLive
} from "./tools.ts";
const
const ResearchLive: Layer.Layer<Handler<"delegate_research_activities">, never, OpenAiClient | SubagentReservations>
ResearchLive
=
import Subagent
Subagent
.
function layer<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, Struct<...>, Struct<...>, typeof ResearchFailed, never, never, never, "error", never, never, undefined, undefined, undefined>(delegation: Subagent.SubagentDelegation<...> & {
...;
}, modelOrBinding?: undefined, options?: Subagent.SubagentRuntimeOptions<...> | undefined): Layer.Layer<...> (+1 overload)

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

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

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

layer
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
,
var undefined
undefined
, {
SubagentRuntimeOptions<typeof ResearchFailed, SubagentChildRunFailure<Struct<{ readonly city: String; readonly focus: String; }>, Struct<{ readonly activities: $Array<String>; readonly researchNotes: String; }>, ... 9 more ..., undefined>, never>.mapChildFailure?: ((failure: Subagent.SubagentChildRunFailure<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, never, never, ... 5 more ..., undefined>) => ResearchFailed) | undefined

Total mapping from every expected child Run failure to the declared Tool failure (SUB-028). The parameter type is the child Binding's complete expected failure union, so a mapping that covers only part of it is a compile error. Interruption stays interruption and defects stay defects; neither reaches this mapping. When omitted, failures become bounded SubagentExecutionFailure values.

mapChildFailure
: (
error: Subagent.SubagentChildRunFailure<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, never, never, ... 5 more ..., undefined>
error
) =>
class ResearchFailed
ResearchFailed
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<ResearchFailed, { readonly _tag: "ResearchFailed"; readonly reason: string; }, readonly [TaggedStruct<"ResearchFailed", { readonly reason: String; }>], { ...; }>, TaggedStruct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly reason: string;
readonly _tag?: "ResearchFailed" | undefined;
}, options?: MakeOptions): ResearchFailed

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
({
reason: string
reason
:
error: Subagent.SubagentChildRunFailure<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, never, never, ... 5 more ..., undefined>
error
.
_tag: "AiError" | "AgentInputDecodeError" | "AgentInputError" | "AgentOutputError" | "AgentPolicyError" | "ContextBudgetError" | "ContextOverflowError" | "CompactionError" | "MemoryRecallError" | "AgentToolAuthorizationCheckError" | "ModelProtocolError" | "AgentPersistenceCapacityError" | "AgentApprovalDenied" | "AgentToolAuthorizationDenied" | "AgentApprovalPending" | "AgentChildPending" | "ThreadHistoryError"
_tag
}),
}).
Pipeable.pipe<Layer.Layer<Handler<"delegate_research_activities">, never, Subagent.SubagentLayerRequirements<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 10 more ..., undefined>>, Layer.Layer<...>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <OpenAiClient, never, LanguageModel | ProviderName | ModelName>(that: Layer.Layer<LanguageModel | ProviderName | ModelName, never, OpenAiClient>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, OpenAiClient | Exclude<RIn2, LanguageModel | ProviderName | ModelName>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
),
import Layer
Layer
.
const provide: <never, never, Handler<"search_activities">>(that: Layer.Layer<Handler<"search_activities">, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, Handler<"search_activities">>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
const TravelToolsLive: Layer.Layer<Handler<"search_activities">, never, never>
TravelToolsLive
));

Update the parent’s instructions to read activities and check partial when using this custom result instead of the default { output, budgetExhausted } envelope.

Child terminal events and the projectResult context expose usage and delegatedUsage. Durable joins preserve verified totals across recovery; see usage accounting. For live child deltas or pricing, pass child.budget or child.estimateCostMicrousd to Subagent.layer.

Custom prepareInput receives context.source as "tool" or "programmatic". Only the tool variant contains context.toolCallId and context.parent.runId. A custom mapper receives bounded parent metadata, never the parent’s transcript.

Replace Subagent.define(name, options) with Subagent.make(name, options). The deprecated constructor has been removed. Keep existing names, including delegate_ names, when upgrading: the constructor migration preserves their durable identities.

Children inherit omitted policy fields from their parent’s resolved policy. Explicit child fields override those defaults, then delegation ceilings clamp turns, calls, duration, tokens, and cost. Use a partial policy object for selective overrides. A complete AgentPolicy.make(...) value already includes its defaults, so those fields count as explicit.

When delegation policy is omitted, the parent’s limits also set a shared delegation pool. Children reserve slices of that pool, not a fresh copy per invocation. Explicit SubagentPolicy retains the per-child limits below and derives aggregate caps by multiplying by maxChildren. Use parentCaps to set another aggregate pool, and share identical caps across all delegations in the parent Run. This pool accounts for child work; it is separate from the parent’s own model and tool-call counters. A global spending quota still needs a host-owned usage budget.

Ephemeral settlement refunds reported unused allocation. Durable settlement conservatively charges the reservation when usage is unavailable. Durable admission rejects a reservation that exceeds the shared pool, including its child-count and concurrency limits.

With the custom declaration above, parent, delegation, and child limits apply at different points:

Setting in this example Meaning
Parent maxToolCalls: 2 At most two ordinary delegation calls before finalization
Delegation maxChildren: 2 At most two child invocations in this parent run
Delegation maxConcurrency: 2 At most two children executing at once
Delegation maxToolCalls: 4 Four tool calls reserved for each child
Child maxToolCalls: 8 The child’s definition ceiling; a delegation cannot raise it
Delegation maxResultBytes: 4_096 Maximum encoded result returned to the parent

Add the following to the custom declaration above to let the parent request a smaller allowance:

parameters: Schema.Struct({
city: Schema.String,
focus: Schema.String,
maxCalls: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
}),
prepareInput: ({ city, focus }) => Effect.succeed({ city, focus }),
toolCallAllowance: {
default: 1,
fromParameters: ({ maxCalls }) => maxCalls,
},

A request for two calls gets two. A request for twenty gets four, the reservation ceiling in this example. If the child returns partial: true, the parent can delegate again with a larger request and forward its findings through the input. That starts a new child thread; it does not top up the first child. See delegation budgets.

The attached example fails the parent tool batch if the child fails. To make expected failures available to the parent model as result data, add failureMode: "return" to the declaration. In the custom declaration above, replace its explicit error mode:

failureMode: "error",
failureMode: "return",

The optional mapChildFailure maps a child run failure to the declared ResearchFailed Schema. With return mode, the parent can receive this result and choose another approach:

{ "_tag": "ResearchFailed", "reason": "AgentPolicyError" }

Expected delegation failures, such as denied admission or an invalid result projection, also become result data. Suspension and durability failures stay in the error channel. Defects and interruption retain their Effect meaning.

The attached example gives the child TravelTools and gives the parent only Research.tool. Adding a tool to the parent does not add it to the child.

To require approval before establishing the child, add this to Subagent.make:

failureMode: "error",
needsApproval: true,

Supply an approval handler for the request. This approves starting the child; its individual actions still need their own authorization. A narrower grant hides child tools outside its allowlist and rejects attempts to invoke them, including through the programmatic broker. It does not reject the whole child Toolkit.

Nested declarations are allowed. The default maxDepth: 1 keeps further launch tools hidden. Set a root-relative maxDepth such as 2 on the participating declarations to permit a child and grandchild, and include the permitted tool names across that subtree in the grant. Each Run still exposes only tools from its own Toolkit. Effective names, depth, and child lifetimes intersect at every level; a descendant cannot restore removed authority.

grant.childLifetimes controls children that the resulting worker may launch. For example, a root may start a background builder whose grant permits only ["attached"]; the builder can then attach scouts but cannot start background grandchildren. Omitting the field permits both lifetimes, subject to depth and budget. Inspection and cancellation tools remain usable at the depth ceiling when their names are allowed.

Reserve descendant slots explicitly with SubagentPolicy.descendantInvocations; omission reserves zero. The allocation covers the child’s own execution plus its descendants. Its own resolved policy for nested and background launches remains bounded by the parent and child policies, while the allocation may be larger to leave a remainder. Descendants reserve only that remainder after the child’s full own ceiling is deducted, across turns, calls, duration, tokens, cost, and result bytes. maxChildren includes the held descendant slots. Ephemeral subtrees also hold their possible concurrent child slots up front and charge the whole started subtree allocation at settlement.

A top-level attached delegation’s explicit pool remains separate from its parent’s own Run counters. At inherited depth one and deeper, attached and background descendants share the reserved subtree allowance. Background roots remain bounded by the source Thread’s host policy. A declaration with sufficient depth but no remaining slots or allocation fails before starting another child. Handoff remains unsupported.

Background descendants reserve against the exact native Run that funds their source input. A root programmatic start without a selected input defaults to independent worker-run funding; explicit source-subtree funding requires a selected owner for each input, including follow-ups. A host permits independent funding by supplying WorkerBudgetAuthorizer from @yielded/agent/worker-host and allowing the exact source, destination, and allowance. The default denies this permission. Request it from author-owned code:

const start = Subagent.start(Research, request, {
idempotencyKey,
budgetScope: "worker-run",
});
const tools = Subagent.background(Research, { start: true, budgetScope: "worker-run" });

The model cannot select the funding scope. The host checks it before every native input admission. This mode resolves the worker’s own Definition policy without inheriting its source’s execution ceiling. Declared delegation allocations bound the worker’s own work and its descendants. Tokens and cost have no cumulative ceiling unless explicitly configured. Keep finite turn, tool-call, duration, concurrency, and result bounds, and authorize the exact allocation at the host.

The first admission freezes the scope with the worker’s immutable source, grant, and depth. A later native logical Run receives the same configured allowance. Input joining an active Run shares that Run’s usage and deadline; a Receipt does not create budget credit. Retried delivery, replacement Attempts, owner eviction, and compaction retain the same Run journal. Changing history or application task identifiers never resets an active allowance.

Independent funding is available only to root-created workers. Root, worker, and attached scout still have depths zero, one, and two. Set the worker grant’s childLifetimes to ["attached"] and maxDepth to 2 to allow scouts without another background generation. Reserve enough descendantInvocations and allocation beyond the worker’s own full ceiling for those scouts. Scouts share the immediate worker Run’s remaining allocation. Host active-worker, pending-input, concurrency, and worker-expiry limits apply across the source Thread. Set WorkerHostConfig.maxActiveWorkersPerSource to bound concurrent background workers separately from the root’s Tool execution concurrency; omission retains the prior concurrency ceiling.

When each source has an authorized concurrency preference, provide WorkerConcurrencyResolver from @yielded/agent/worker-host through an Effect Layer. It receives the immutable source, worker, principal, and explicitly selected canonical owner submission. Return Option.some({ maxActiveWorkersPerSource }) to narrow the fixed host ceiling, or Option.none() to retain it. The runtime resolves this limit inside the source reservation CAS loop, including retries after competing appends. Counted workers have at least one input awaiting canonical completion; queued inputs count, and steering an active worker needs no additional slot. An idle worker must acquire a slot before a later input. Lowering the ceiling, including to zero, does not cancel incumbents or reject replay of an established reservation. Temporarily unavailable authority must return WorkerError with reason unavailable; it must not silently choose a fallback. This operational limit never changes an established worker policy, delegation depth, retained-worker limit, or Run allowance.

Supply WorkerPolicyResolver from @yielded/agent/worker-host when immutable application input captures an execution policy separately from a finite, versioned Agent Definition. Provide its implementation through an Effect Layer and retain the Layer’s construction dependencies. The default returns Option.none(), preserving registered policy inheritance and overrides. Returning Option.some(policy) selects a complete policy without reapplying static overrides. Missing authority for an opted-in definition must fail explicitly, rather than returning the legacy fallback or loading mutable settings. Use WorkerError with reason unavailable when the same captured evidence can become available on retry.

Subagent.start prepares and encodes input before the caller-bound host resolves its target policy. Both source start and destination admission validate that exact initial input; destination validation also runs before replay returns an existing reservation. Explicit declaration limits still narrow the resolved policy. Construct a declaration per invocation from the same immutable capture when its allocation includes the worker’s own ceiling plus fixed attached-scout reserves. Reuse the exact registered target Definition, grant, and reporting projection; this does not require a dynamic registration graph or another compiled model Tool.

The initial admission stores the effective policy in the existing immutable worker origin. For RetainedWorker, a resolver may affirm origin.policy; returning a different policy fails. Later inputs, joined receipts, retries, and replacement Attempts never select a new worker policy. Application follow-up preparation must retain the original authorized capture and change only the intended task input. Receiving a new payload does not authorize changing its capture.

Root source resolution receives the exact explicitly selected owner Submission and the current binding for its Agent ID. It never selects the latest input. Programmatic callers can pass sourceSubmissionId to durableRuntime.workerHost; WorkerHostAuthorizer receives that locator for authorization. Worker and attached source policies continue to come from stored lineage. Inspection, listing, observation, and cancellation do not require resolving a source policy. A policy resolver cannot reconstruct a missing capture or change immutable worker authority. Executable selection uses the current binding for the retained Agent ID; it does not require historical binding versions.

A root conversation can admit a new registered Agent ID after an application upgrade. When an explicit owner Submission selects that Agent, worker creation and reporting use its current binding, while the original ThreadCreated record stays unchanged. Missing or ambiguous current bindings are rejected. Existing workers retain their original lineage and reporting evidence when the upgraded root sends follow-ups; worker and attached child Agent IDs cannot be replaced this way. Without an explicit owner Submission, programmatic hosts continue to use the thread’s original Agent.

Captured source reporting uses the initial owner binding and stores its existing reporting intent in the worker origin. A later input from another source revision does not replace that projection; terminal preparation validates the original owner retained by the first worker input reservation.

The background guide shows model-facing tools. Application code can also call Subagent.start, followUp, inspect, await, stop, and cancel with an authorized SubagentHost.

A programmatic start needs an idempotency key: a stable identifier for one intended input. If delivery is retried, reuse the same key and parameters so the host can recognize that input. Use a new key for a new input. Retained starts and follow-ups reuse their captured input before fresh preparation, even from another source Run. Current caller authorization still applies; changed declared parameters conflict. Native model tools derive their keys automatically.

import {
import Subagent
Subagent
} from "@yielded/agent";
import {
type IdempotencyKey = string & Brand<"@effect-agent/thread/IdempotencyKey">
const IdempotencyKey: Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/IdempotencyKey">

Caller-supplied deduplication key, scoped to one Thread and authenticated principal.

IdempotencyKey
} from "@yielded/agent/receipt";
import {
import Effect
Effect
,
import Schema
Schema
} from "effect";
import {
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
} from "./delegation.ts";
const
const program: Effect.Effect<{
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
readonly status: "refused";
readonly receipt: null;
readonly settlement: null;
readonly reason: string;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
} | {
...;
} | {
...;
}, Subagent.SubagentPrestartDenied | ... 1 more ... | Subagent.SubagentProjectionFailure, SubagentHost>
program
=
import Effect
Effect
.
const gen: <Effect.Effect<{
worker: {
readonly schemaVersion: 1;
readonly delegationId: string & Brand<"@effect-agent/core/DelegationId">;
readonly targetAgentId: string & Brand<"@effect-agent/core/AgentId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
} & Brand<"@effect-agent/Worker/delegate_research_activities">;
delivery: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<...>;
};
} | {
...;
} | {
...;
} | {
...;
} | {
...;
};
}, Subagent.SubagentPrestartDenied | ... 1 more ... | Subagent.SubagentProjectionFailure, SubagentHost> | Effect.Effect<...> | Effect.Effect<...>, {
...;
} | ... 4 more ... | {
...;
}>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

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

When to use

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

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

Example (Sequencing effects with generators)

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

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const {
const worker: {
readonly schemaVersion: 1;
readonly delegationId: string & Brand<"@effect-agent/core/DelegationId">;
readonly targetAgentId: string & Brand<"@effect-agent/core/AgentId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
} & Brand<"@effect-agent/Worker/delegate_research_activities">
worker
,
const delivery: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
readonly status: "accepted";
readonly receipt: Receipt;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
} | {
...;
}
delivery
} = yield*
import Subagent
Subagent
.
start<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly output: Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>;
readonly budgetExhausted: Schema.Boolean;
}>, Schema.Never, never, never>(declaration: Declaration<...>, parameters: {
...;
}, options: {
readonly idempotencyKey: IdempotencyKey;
readonly budgetScope?: WorkerBudgetScope;
readonly continuationOf?: WorkerRef;
}): Effect.Effect<...>
export start

Retain a first worker input. The same explicit key reuses its durable delivery identity.

start
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
,
{
city: string
city
: "Lisbon",
focus: string
focus
: "food and walking" },
{
idempotencyKey: string & Brand<"@effect-agent/thread/IdempotencyKey">
idempotencyKey
:
import Schema
Schema
.
const decodeSync: <Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/IdempotencyKey">>(schema: Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/IdempotencyKey">, options?: ParseOptions) => (input: string, options?: ParseOptions) => string & Brand<"@effect-agent/thread/IdempotencyKey">

Decodes a typed input (the schema's Encoded type) against a schema synchronously, returning the decoded value or throwing a

SchemaError

for schema mismatches.

When to use

Use when you already have input typed as the schema's Encoded type and want schema mismatches to throw SchemaError synchronously.

Details

For unknown input use decodeUnknownSync. Only service-free schemas can be decoded synchronously. Options may be provided either when creating the decoder or when applying it; application options override creation options.

Gotchas

Non-schema failures may throw a runtime failure instead of SchemaError.

@see ― SchemaParser.decodeSync for the adapter that throws an Error whose cause is SchemaIssue.Issue

@category ― decoding

@since ― 4.0.0

decodeSync
(
const IdempotencyKey: Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/IdempotencyKey">

Caller-supplied deduplication key, scoped to one Thread and authenticated principal.

IdempotencyKey
)("lisbon-research-1") },
);
const
const current: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
readonly status: "accepted";
readonly receipt: Receipt;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
} | {
...;
}
current
= yield*
import Subagent
Subagent
.
inspect<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly output: Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>;
readonly budgetExhausted: Schema.Boolean;
}>, Schema.Never, never, never>(declaration: Declaration<...>, worker: {
...;
} & Brand<...>, message: MessageRef): Effect.Effect<MessageStatus, WorkerError, SubagentHost> (+2 overloads)
export inspect

Inspect a worker summary, retained MessageRef, or the result of one exact accepted Receipt.

inspect
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
,
const worker: {
readonly schemaVersion: 1;
readonly delegationId: string & Brand<"@effect-agent/core/DelegationId">;
readonly targetAgentId: string & Brand<"@effect-agent/core/AgentId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
} & Brand<"@effect-agent/Worker/delegate_research_activities">
worker
,
const delivery: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
readonly status: "accepted";
readonly receipt: Receipt;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
} | {
...;
}
delivery
.
message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
}
message
);
if (
const current: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
readonly status: "accepted";
readonly receipt: Receipt;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
} | {
...;
}
current
.
receipt: Receipt | null
receipt
=== null) return
const current: {
readonly status: "pending";
readonly receipt: null;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
}
current
;
return yield*
import Subagent
Subagent
.
await<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly output: Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>;
readonly budgetExhausted: Schema.Boolean;
}>, Schema.Never, never, never>(declaration: Declaration<...>, worker: {
...;
} & Brand<...>, receipt: Receipt): Effect.Effect<...>
export await

Wait for the exact Receipt. Interrupting this Effect never cancels accepted work.

await
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Schema.Struct<{
readonly city: Schema.String;
readonly focus: Schema.String;
}>, Schema.Struct<{
readonly activities: Schema.$Array<Schema.String>;
readonly researchNotes: Schema.String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Schema.Struct<{
readonly city: Schema.String;
}>;
readonly success: Schema.$Array<Schema.String>;
readonly failure: Schema.Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
,
const worker: {
readonly schemaVersion: 1;
readonly delegationId: string & Brand<"@effect-agent/core/DelegationId">;
readonly targetAgentId: string & Brand<"@effect-agent/core/AgentId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
} & Brand<"@effect-agent/Worker/delegate_research_activities">
worker
,
const current: {
readonly status: "accepted";
readonly receipt: Receipt;
readonly settlement: null;
readonly reason: string | null;
readonly message: {
readonly ownerThreadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly messageId: string & Brand<"@effect-agent/thread/IdempotencyKey">;
};
} | {
...;
} | {
...;
}
current
.
receipt: Receipt
receipt
);
});

Provide the facet obtained from durableRuntime.workerHost({ sourceThreadId, principal }) as SubagentHost. The source thread must already exist. Programmatic Subagent.await waits for an exact receipt; interruption or timeout stops only that waiter, leaving the worker running.

Subagent.start returns { worker, delivery }; Subagent.followUp returns the delivery directly. Both use MessageStatus from @yielded/agent/messaging, with one stable message: MessageRef. These Effects retain one intended input. Programmatic calls require an explicit, stable IdempotencyKey; native tools derive it from their actual invocation.

Status Evidence
pending The exact input is retained. Acceptance and execution are unconfirmed.
accepted receipt identifies the destination’s accepted input; completion is unconfirmed.
processed receipt and settlement identify its canonical completed, failed, or aborted outcome.
refused The destination rejected this input; reason identifies the refusal.
parked Automatic dispatch stopped; the exact identity and accepted receipt remain available.

Inspect delivery.message to follow the same operation without resending or driving delivery. parked with reason awaiting-settlement means the destination accepted the input and will notify its owner when it settles; approvals and other external waits schedule no status polls. The host’s existing bounded delivery driver owns retries and crash recovery. A recorded admission failure remains pending with a bounded reason, or becomes parked when its retry budget ends. Detailed diagnostics remain private in the delivery record. A real storage exception still fails with WorkerError; preserve the same key and parameters when retrying an uncertain response. Authorization and input-validation failures remain typed errors. Existing delivery rows keep their stored format and identity.

Subagent.start and Subagent.followUp check the retained command before running prepareInput. Reusing the same key and declared parameters/options in a later Run reuses that command’s first captured input, including a delivery still awaiting admission. Follow-ups preserve the original correction input and worker authority. Changed parameters, start grant, or funding scope conflict. Declaration policy and toolCallAllowance stay frozen with the first capture; changing that configuration requires a new command key. Current caller authorization still applies, including when preparation races another writer. Preparation must be free of external effects: competing first calls can prepare before the native outbox chooses one capture. Admission keeps its existing authority checks; a retained command is not permission for a fresh input. Directly prepared SubagentHost requests also compare the supplied input and reject changed captures.

const Research = Subagent.make("research", { target: researcher });
const tools = Subagent.background(Research, {
start: true,
followUp: true,
inspect: true,
list: true,
cancel: true,
});
// Add tools.toolkit to the parent Definition and tools.layer to its services.

The host chooses which native tools to expose. Each retains this declaration’s parameter and result Schemas. Programmatic code acquires a separately authorized facet with durableRuntime.workerHost({ sourceThreadId, principal }) and provides it as SubagentHost. The source Thread must already exist. No fabricated Run or Tool Call ID is needed. Successful receipt inspection decodes that input’s saved parameters and child output before applying projectResult. The optional summary: true background tool exposes a worker summary; projection services must be supplied to the handler Layer. Native start and follow-up tools require the platform Crypto service.

For native tools, the durable runtime provides SubagentHost.forTool through Effect context. The interpreter supplies the actual Agent, Thread, Run, and Tool Call identity; the runtime refuses a binding from another Run. The reference defaults to an unavailable host and is not a RunOptions callback.

The worker identifies its continuing Thread; a MessageRef identifies one retained delivery; a Receipt identifies its accepted destination input. None grants access. Encode/decode workers with Subagent.Worker(Research). inspect(Research, worker) returns the same summary as discovery; passing a MessageRef reads delivery state, while passing a Receipt projects that exact input’s result. The native inspect tool takes { worker, message } or { worker, receipt }, exactly one. await takes the declaration, worker, and exact Receipt and can be interrupted without cancelling work. cancel targets only that Receipt and preserves JoinedToHost if it joined another input’s Run. Cancellation does not close the worker or cancel an entire work tree.

Inputs can join an active Run at a safe boundary or start a later Run. Callers use the same operation for both. At each safe steering boundary, the native worker drains up to 32 ready inputs in FIFO order before the next model request. An approved, retained Tool batch resumes before that boundary with its original arguments. Larger backlogs and inputs accepted after the drain still require the application’s accepted-versus-applied check before external actions.

Parent completion or abort leaves background work running; attached children retain their existing cancellation and join semantics. Worker provenance, authority, and reservations survive later coordinator Runs and host reconstruction. The admission ledger atomically prevents replacing an ordinary Thread lane with a worker lane or changing its origin.

Subagent.stop(Research, worker, { idempotencyKey }) permanently seals that worker’s inbox, including retained inputs that have not reached admission, queued steering, and later continuations. It waits for active ownership to release before acknowledging. Retry the same key after a timeout, interruption or lost acknowledgement; reusing a stop key for another worker conflicts. Storage failure leaves acknowledgement uncertain. Stop does not undo external actions, erase unresolved outcomes, stop independent descendants, or perform application-owned cleanup.

const stopped = yield * Subagent.stop(Research, worker, { idempotencyKey: stopCommandId });
// stopped: { worker, idempotencyKey: stopCommandId }
const snapshot = yield * Subagent.inspect(Research, worker);
const latestInstructionsApplied =
snapshot.acceptedInput !== null &&
snapshot.acceptedInput.messageId === snapshot.appliedInput?.messageId;

Worker summaries retain latestReceipt and expose these native facts:

Field Meaning
acceptedInput Exact latest destination Receipt and retained message ID. Acceptance does not mean application.
appliedInput Exact latest canonical input, its Run ID and canonical sequence.
run Latest actual Run, its host Receipt, canonical outcome and optional application disposition.
pendingDelivery One retained input still awaiting admission, if any. Inspect its message for delivery details.
watermark Consistent canonical sequence, accepted queue sequence, and source delivery version.

active includes queued input; starting can have no Receipt. Only an owner-issued worker stop produces stopping or stopped; an ordinary Receipt abort does not. These states let a parent distinguish an intentional stop from a recoverable failure, even when a completion report says aborted. stopping retains unsettled obligations behind the permanent fence; stopped has none. Follow-up after either state is refused with reason: "worker-stopped", including after restart. For reusable workers, idle and a completed Run do not mean the assignment is finished. Use the opt-in below when the library must enforce assignment completion.

Subagent.list(Research, { limit, after }) returns an indexed, bounded page of retained starts, including those not yet admitted. Read canonical history with Subagent.observe(Research, worker, { after }): this is a finite Stream through the tail captured at acquisition, using bounded storage pages. Pass its last sequence as the next cursor. Observation acquires no execution permit and does not cancel work when interrupted.

WorkerHostAuthorizer separates context, read, send, and control access and denies by default. WorkerHostConfig bounds active workers, pending inputs, and worker lifetime (defaults: source Tool concurrency, 8 pending ordinary inputs per worker, 24 hours). Completed inputs and idle workers retain their replay identities without consuming live capacity. An accepted input releases capacity only after canonical acknowledgement proves its effects resolved. A refused, never-admitted input remains charged while its receiver inbox is open; an authorized owner stop allows exact nonadmission closure. Unavailable receiver evidence leaves that work owed. Started allocations are not refunded. Execution concurrency is a separate host setting; waiting attached parents release their permits. Configure sufficient host capacity for conversational work and the chosen child concurrency. Idle workers own no execution resources.

Set runDisposition.workerLifecycle: "assignment" on the target Agent definition and select Worker.AssignmentDisposition (completed or waiting) from decoded output. The selector is pure; the existing canonical completion retains its encoded decision. This is an opt-in for new workers, supported by the native memory, SQLite and Cloudflare storage adapters.

Run result Assignment state
waiting Open for steering and another run.
completed, with latest accepted input applied Permanently completed.
completed, with newer unapplied accepted input Open; the newer input can run.
Failed or budget-exhausted run Permanently failed; queued inputs are aborted.
Aborted active run Permanently cancelled; queued inputs are aborted.
Receipt cancelled before starting a run Open; receipt cancellation alone does not finish the assignment.

Settlement installs the destination seal atomically before releasing ownership. Admission racing completion either wins and prevents stale completion, or is refused by the seal. The latest accepted input must belong to the completing run’s applied inputs; a completed model turn, tool side effect, or application note is insufficient. A lost attempt or suspended/unknown tool outcome is not a terminal run failure. Recovery retains unresolved external-effect evidence and never replays an ordinary tool blindly.

completed, failed and cancelled summary states cannot reopen after restart. New starts on that destination and later follow-ups return worker-stopped; identical admitted command replays still return their original receipt and outcome. Subagent.stop remains permanent and retains its stopping/stopped states; a later stop never replaces an existing assignment outcome. Stop from the owning caller, not inside the active child’s completion handler.

To continue a successfully completed assignment, pass continuationOf: previous.worker and a new idempotency key to Subagent.start. This creates a distinct worker and leaves the predecessor sealed. The host verifies its exact completed receipt and settlement before authorization and again on retained admission/replay. Pending, failed, cancelled, explicitly stopped, cross-source and cross-declaration predecessors are rejected. Policy, grant, budget, accounting scope and depth must match the predecessor, and its absolute expiry is never extended. Current authorization, funding and concurrency checks still apply; verified continuationOf evidence reaches each host hook. The current source run remains the caller. Applications must authorize the continuation’s scope and derive its captured task configuration from the predecessor, not from untrusted new input. Native update/completion admission uses authorizer access: "report" after verifying the frozen framework message. It must return that destination’s principal; worker followUp keeps access: "send" and may select its execution principal separately.

Use Subagent.background(Research, { start: true, reportToParent: true }) for a standard WorkerCompletion message, as shown in the background walkthrough. Its report has the same typed projected success or bounded failure as Subagent.WorkerReport; budgetExhausted also preserves exhaustion when an application projection omits it. The admitted parent input stays available to instructions and policy. The model receives the completion as a user message, so child output never becomes trusted instructions. Canonical UserInputRecorded.messageAdmission distinguishes framework completions from peer provenance through the InputMessage Schema. Inspect WorkerCompletion with its Schema before reading a completion; use MessageAdmission for peer messages.

Use Subagent.WorkerReport(Research.success) to decode a completion’s report into its declared result type, then map it to application state on the receiving side. Subagent.reporting, Subagent.reportingToWorker, custom reportToParent descriptors, and registration reporting arrays have been removed. Projection services are captured separately from per-Attempt services. Change registration versions when changing result projection behavior.

Use reportCompletion: (report) => boolean alongside reportToParent to omit intermediate Run outcomes, such as a worker waiting for an external operation. The predicate receives the canonical outcome and encoded result before result projection; decode successful output with the child’s output Schema. Return true for failure or cancellation outcomes when the parent must handle them. Omit the predicate to report every Run.

Returning false retains a WorkerReportRefused decision with reason filtered without preparing or delivering a parent message. It does not alter the child settlement, suppress explicit updates, or prevent a later Run from reporting its final result. The predicate must be pure: recovery reuses the committed decision, but a crash before that commit can reevaluate it. A thrown predicate records the same bounded defect refusal as a throwing projection.

Application-driven starts with standard reports must acquire the host facet with the exact sourceSubmissionId whose application input supplies parent context. Model tool calls already carry that identity. No current or latest input is guessed for a programmatic caller.

Standard reporting freezes its return address in the accepted worker origin. Progress and completion preparation use the child’s own admission, execution records, and delivery outbox; they do not read the parent journal, ledger, or current authorization state. The receiving host verifies the canonical delivery proof and current send permission before accepting the message, so revocation can refuse delivery without preventing the child from durably publishing its result.

Workers publish effects-resolved completion receipts in their own journals. The source reads those exact receipts when admitting more work and copies acknowledgements into its reservation batch under the source’s fence. An unresolved external operation cannot release that capacity.

Launch intent pins reporting before acceptance. Each actual child Run has one logical report, even when several steering Receipts join it; an input cancelled before any Run starts has no Run report. The declaration’s result projection produces a frozen PreparedInput before delivery insertion. It should be deterministic and free of external side effects: a crash before the canonical preparation decision commits can rerun it. Delivery retries never reproject a committed decision. Expected failure, defect, invalid output, or preparation timeout records a bounded refusal without replacing the child’s outcome. Preparation has its own Scope and a 5-second default timeout, configurable up to 30 seconds in WorkerHostConfig.

A standard report to a parent that is itself a background worker retains that parent’s original application input and declaration parameters. Its additional run is charged to the original ancestor allocation, with the same grants, lifetime, and resource ceilings. No extra nested-worker adapter is required. An attached destination has no independent continuing input lifetime; delivery to it is refused. An attached scout returns through its waiting parent’s tool result.

Report preparation decisions appear in authorized canonical worker history. Retained delivery records expose pending, accepted, processed, parked, and refused states through the host-owned MessageDeliveryStore. A child’s completion and its report’s processing remain separate facts.

Standard registration fingerprints, worker origins, prepared envelopes and delivery/refusal records remain unchanged by this API removal. Historical custom-report origins remain readable without a storage upgrade. Already prepared envelopes and delivery/refusal evidence remain intact; retries reuse the saved envelope. Unprepared custom intents record declaration-unavailable instead of running a retired mapper. Drain custom-report work with its original release before upgrading if delivery is required. This does not erase canonical history or resolve unknown external-action outcomes.

Agent.make(name, { updates: schema, ... }) declares intentional intermediate output independently of final output. Objects and tagged unions work, including transformed Schemas. The framework adds one native emit_update Tool with { value: schema } parameters. Success returns { emitted: true } after the Run accepts the update. Durable acceptance retains the canonical update and any prepared delivery; destination admission and parent processing are separate. Expected refusal is returned as a typed tool result, so the Agent can continue toward completion. The name is reserved when updates are declared; inherited tool grants still apply.

Application tool code can use AgentUpdates.emit(agent, value, { idempotencyKey }) instead. Declare AgentUpdates.Emitter as a dependency of that Tool. The key identifies one intended update within the Run: equal retries reuse it, and changed payloads fail with conflict. The emitter is available during an active tool batch and closes with its Scope. Native calls derive their keys from the actual Run and Tool Call.

Observe the same definition at the top level:

observe-updates.ts
import {
import AgentRuntime
AgentRuntime
,
import AgentUpdates
AgentUpdates
} from "@yielded/agent";
import {
import Effect
Effect
,
import Stream
Stream
} from "effect";
import {
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
} from "./background-updates.ts";
const
const updates: Stream.Stream<AgentUpdates.Update, AgentRuntime.AgentRuntimeFailure<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>
updates
=
import AgentRuntime
AgentRuntime
.
stream<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>(agent: Definition<...> & {
...;
}, input: NoInfer<{
...;
}>, options?: RunOptions<...> | undefined): Stream.Stream<...>
export stream

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

stream
(
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
, {
city: string
city
: "Johannesburg",
area: string
area
: "Rosebank",
sources: readonly {
readonly url: string;
readonly notes: string;
}[]
sources
: [],
}).
Pipeable.pipe<Stream.Stream<AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined, AgentRuntime.AgentRuntimeFailure<...>, AgentRuntime.AgentRuntimeRequirements<...>>, Stream.Stream<...>, Stream.Stream<...>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Stream.Stream<...>, bc: (_: Stream.Stream<...>) => Stream.Stream<...>): Stream.Stream<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const filter: <AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined, AgentUpdateEmitted>(refinement: Refinement<...>) => <E, R>(self: Stream.Stream<...>) => Stream.Stream<...> (+3 overloads)

Filters a stream to the elements that satisfy a predicate.

Example (Filtering stream values)

import { Effect, Stream } from "effect"
const program = Effect.gen(function*() {
const stream = Stream.make(1, 2, 3, 4).pipe(
Stream.filter((n) => n % 2 === 0)
)
const values = yield* Stream.runCollect(stream)
values // => [ 2, 4 ]
})
await Effect.runPromise(program)

@category ― filtering

@since ― 2.0.0

filter
((
event: AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined
event
) =>
event: AgentUpdateEmitted | RunStarted | TurnStarted | ModelStarted | ModelRestarted | TextDelta | ReasoningDelta | ToolCallDeclared | ToolCallStarted | ToolProgress | ToolCallSucceeded | ToolCallFailed | ApprovalRequested | TurnCompleted | BudgetWarning | CompactionPerformed | ... 10 more ... | SubagentJoined
event
.
_tag: "AgentUpdateEmitted" | "RunStarted" | "TurnStarted" | "ModelStarted" | "ModelRestarted" | "TextDelta" | "ReasoningDelta" | "ToolCallDeclared" | "ToolCallStarted" | "ToolProgress" | "ToolCallSucceeded" | "ToolCallFailed" | "ApprovalRequested" | "TurnCompleted" | "BudgetWarning" | "CompactionPerformed" | "RunCompleted" | "RunFailed" | "RunInterrupted" | "RunSuspended" | "SubagentRequested" | "SubagentStarted" | "SubagentProgress" | "SubagentCompleted" | "SubagentFailed" | "SubagentInterrupted" | "SubagentJoined"
_tag
=== "AgentUpdateEmitted"),
import Stream
Stream
.
const map: <AgentUpdateEmitted, AgentUpdates.Update>(f: (a: AgentUpdateEmitted, i: number) => AgentUpdates.Update) => <E, R>(self: Stream.Stream<AgentUpdateEmitted, E, R>) => Stream.Stream<AgentUpdates.Update, E, R> (+1 overload)

Transforms the elements of this stream using the supplied function.

Example (Mapping stream values)

import { Effect, Option, Stream } from "effect"
const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i))
await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5]

@category ― mapping

@since ― 2.0.0

map
((
event: AgentUpdateEmitted
event
) =>
event: AgentUpdateEmitted
event
.
update: AgentUpdates.Update
update
),
);
// Provide the model and runtime services from the getting-started guide.
export const
const observe: Effect.Effect<void, AgentRuntime.AgentRuntimeFailure<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>
observe
=
import AgentUpdates
AgentUpdates
.
const observe: <NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>, AgentRuntime.AgentRuntimeFailure<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<...>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>(agent: BoundSource<...>, updates: Stream.Stream<...>) => Stream.Stream<...>

Decode an encoded update stream without hiding its failures or requirements.

observe
(
const HotelResearcher: Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}
HotelResearcher
,
const updates: Stream.Stream<AgentUpdates.Update, AgentRuntime.AgentRuntimeFailure<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<String>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>
updates
).
Pipeable.pipe<Stream.Stream<{
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}, AgentRuntime.AgentRuntimeFailure<Definition<Struct<{
readonly city: String;
readonly area: String;
readonly sources: $Array<Struct<{
readonly url: String;
readonly notes: String;
}>>;
}>, Struct<{
readonly hotels: $Array<String>;
readonly summary: String;
}>, string, Toolkit<{
readonly emit_update: Tool<"emit_update", {
readonly parameters: Struct<{
readonly value: NoInfer<TaggedStruct<"AreaConcern", {
readonly area: String;
readonly finding: String;
readonly sources: $Array<...>;
}>>;
}>;
readonly success: Struct<...>;
readonly failure: typeof AgentUpdates.UpdateError;
readonly failureMode: "return";
}, AgentUpdates.Emitter>;
}>, undefined, undefined, NoInfer<TaggedStruct<...>>> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>, Effect.Effect<...>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const runForEach: <{
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}, void, never, never>(f: (a: {
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}) => Effect.Effect<void, never, never>) => <E, R>(self: Stream.Stream<{
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}, E, R>) => Effect.Effect<void, E, R> (+1 overload)

Runs the provided effectful callback for each element of the stream.

Example (Running an effect for each value)

import { Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3)
const values: Array<string> = []
const program = Effect.gen(function*() {
yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`)))
})
await Effect.runPromise(program)
values // => ["Processing: 1", "Processing: 2", "Processing: 3"]

@category ― destructors

@since ― 2.0.0

runForEach
((
finding: {
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}
finding
) =>
import Effect
Effect
.
const log: (...message: ReadonlyArray<any>) => Effect.Effect<void>

Logs one or more messages using the default log level.

Example (Logging at the default level)

import { Effect, Logger } from "effect"
const output: Array<unknown> = []
const program = Effect.gen(function*() {
const result = 2 + 2
yield* Effect.log("Result:", result)
return result
})
const logger = Logger.make<unknown, void>(({ logLevel, message }) => {
void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`)
})
const runnable = Effect.provide(program, Logger.layer([logger]))
void output.push(Effect.runSync(runnable))
output // => ["Info: Result: 4", 4]

@category ― logging

@since ― 2.0.0

log
(
finding: {
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}
finding
.
area: string
area
,
finding: {
readonly _tag: "AreaConcern";
readonly area: string;
readonly finding: string;
readonly sources: readonly string[];
}
finding
.
finding: string
finding
)),
);

AgentUpdates.decode and observe decode through the Agent’s update Schema and preserve its required decoding services. Runtime events contain encoded values; they never assert encoded payloads into application types. Durable canonical history retains AgentUpdateEmitted records, including the exact source definition versions. Authorized Subagent.observe readers can decode those records and their updates with the same APIs.

Use reportUpdate: (update) => boolean alongside reportToParent to select which updates notify the parent. Decode update.value with the child’s update Schema. The predicate must be pure: its decision is frozen at first acceptance and never reevaluated on replay. Returning false keeps the update available to observers without parent delivery or a parent model run. Completion reports are unaffected. Omit the predicate to deliver every update.

With reportToParent: true, acceptance commits the finding and a frozen parent delivery envelope before acknowledging it. The child continues without waiting for destination admission, parent processing, or a user decision. The parent receives a WorkerUpdate as untrusted user-message content, retaining its original application input for instructions and policy. Custom reportToParent mappings keep their existing terminal-completion behavior; they are optional.

Each worker Thread orders update deliveries and completion reports across its Runs. A successor waits until the predecessor has a destination Receipt or a conclusive refusal. A parked predecessor without a Receipt blocks later delivery until recovery; a refusal remains observable instead of silently discarding the finding. Waiting for a predecessor does not spend admission attempts. Destination admission and processing remain separate states, and no external side effect is promised to execute exactly once.

The runtime defaults to 32 updates and 16 KiB of total encoded update payload per Run. RunOptions.updates configures these limits. Durable acceptance rechecks canonical counts after a restart. The delivery store separately bounds pending update work per worker (default: 32). maxPendingUpdatesPerOwner configures that partition; updates cannot consume the ordinary capacity used for terminal reports. Capacity refusal happens before a new update is accepted. A parent that is itself a worker has separate update input quotas: WorkerHostLimits.maxPendingUpdateInputsPerWorker defaults to 32. Temporary pending or active-worker limits retry delivery; permanent budget exhaustion refuses it. Parent admissions still obey inherited budgets, grants, lifetime, and host limits.

Retained findings and pending delivery survive parent completion or abort, dropped wake hints, restart, and eviction. Recovery inserts missing outbox rows from canonical envelopes and retries the same logical delivery without rebuilding its payload. A lost acknowledgement can therefore leave a retained finding even when the caller did not see success. Reuse its key when reconciling. If ownership is lost before the native Tool result is recorded, the Tool call remains unresolved under the ordinary recovery rules. Recovery delivers the retained update without replaying the Tool; explicit cancellation can then settle the worker and deliver its separate aborted report. Accepted updates remain distinct from the eventual completed, failed, or aborted outcome.