Skip to content

Subagents

Durable attached subagents

Run the parent and its child on a durable host:

durable-delegation-host.ts
import {
import Subagent
Subagent
} from "@yielded/agent";
import {
import NodeDurableHost
NodeDurableHost
} from "@yielded/agent-platform-node";
import {
const SubagentReservationsMemoryLive: Layer.Layer<SubagentReservations, never, never>

In-memory reservation ledger. All state lives in one Ref owned by the Layer's Scope; every transition is a single atomic Ref.modify.

SubagentReservationsMemoryLive
} from "@yielded/agent/subagent-reservations";
import {
import Layer
Layer
} from "effect";
import {
const Coordinator: Definition<Struct<{
readonly city: String;
}>, Struct<{
readonly itinerary: $Array<String>;
}>, string, Toolkit<{
readonly delegate_research_activities: Tool<"delegate_research_activities", {
readonly parameters: Struct<{
readonly city: String;
readonly focus: String;
}>;
readonly success: Struct<{
readonly output: Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>;
readonly budgetExhausted: Boolean;
}>;
readonly failure: Subagent.SubagentToolFailure<Never>;
readonly failureMode: "error";
}, AgentSpawner | ... 1 more ... | SubagentDurability>;
}>, undefined, undefined, undefined> & {
...;
}
Coordinator
} from "./coordinator.ts";
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
} from "./delegation.ts";
import {
const definitions: {
agent: string;
model: string;
tools: string;
}
definitions
,
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
,
const OpenAiLive: Layer.Layer<OpenAiClient, ConfigError, never>
OpenAiLive
} 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, SubagentReservations | OpenAiClient>
ResearchLive
=
import Subagent
Subagent
.
function layer<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, Struct<...>, Struct<...>, Never, never, never, never, "error", never, never, undefined, undefined, undefined>(delegation: Subagent.SubagentDelegation<...> & {
...;
}, modelOrBinding?: undefined, options?: Subagent.SubagentRuntimeOptions<...> | undefined): Layer.Layer<...> (+1 overload)

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

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

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

layer
(
const Research: Subagent.SubagentDelegation<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 5 more ..., "error"> & {
...;
}
Research
).
Pipeable.pipe<Layer.Layer<Handler<"delegate_research_activities">, never, Subagent.SubagentLayerRequirements<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, ... 10 more ..., undefined>>, Layer.Layer<...>, 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
),
);
export const
const HostLive: Layer.Layer<DurableAgentRuntime | MessageDeliveryStore | ThreadReader | NodeDurableAgentRuntimeConfig | (Crypto & DurableAgentRuntime) | (Crypto & MessageDeliveryStore) | (Crypto & ThreadReader) | (Crypto & NodeDurableAgentRuntimeConfig) | (SqlClient & DurableAgentRuntime) | (SqlClient & MessageDeliveryStore) | ... 6 more ... | NodeDurableHost.NodeDurableHost, ConfigError | ... 3 more ... | NodePlatformConfigError, never>
HostLive
=
import NodeDurableHost
NodeDurableHost
.
const layer: <readonly [{
readonly agent: Definition<Struct<{
readonly city: String;
}>, Struct<{
readonly itinerary: $Array<String>;
}>, string, Toolkit<{
readonly delegate_research_activities: Tool<"delegate_research_activities", {
readonly parameters: Struct<{
readonly city: String;
readonly focus: String;
}>;
readonly success: Struct<{
readonly output: Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>;
readonly budgetExhausted: Boolean;
}>;
readonly failure: Subagent.SubagentToolFailure<...>;
readonly failureMode: "error";
}, AgentSpawner | ... 1 more ... | SubagentDurability>;
}>, undefined, undefined, undefined> & {
...;
};
readonly model: Model<...>;
readonly definitions: {
...;
};
}, {
...;
}], never, never, never, never, never, never>(registrations: readonly [...], options: NodeDurableAgentRuntimeOptions<...>) => Layer.Layer<...>

Acquire a complete Node host and start one bounded, scoped worker pool after recovery. Own the SQLite file exclusively until the host and its storage close. A second connection fails construction; after process death the replacement retires abandoned claims before recovery, without waiting for their leases. Use this host's services for live inspection. Provide model, tool, instruction, and schema dependencies to this Layer. Reusing the Layer shares the same pool. A worker failure closes admission; observe it with run at the process boundary so the application exits and releases the host instead of remaining idle.

layer
(
[
{
agent: Definition<Struct<{
readonly city: String;
}>, Struct<{
readonly itinerary: $Array<String>;
}>, string, Toolkit<{
readonly delegate_research_activities: Tool<"delegate_research_activities", {
readonly parameters: Struct<{
readonly city: String;
readonly focus: String;
}>;
readonly success: Struct<{
readonly output: Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>;
readonly budgetExhausted: Boolean;
}>;
readonly failure: Subagent.SubagentToolFailure<Never>;
readonly failureMode: "error";
}, AgentSpawner | ... 1 more ... | SubagentDurability>;
}>, undefined, undefined, undefined> & {
...;
}
agent
:
const Coordinator: Definition<Struct<{
readonly city: String;
}>, Struct<{
readonly itinerary: $Array<String>;
}>, string, Toolkit<{
readonly delegate_research_activities: Tool<"delegate_research_activities", {
readonly parameters: Struct<{
readonly city: String;
readonly focus: String;
}>;
readonly success: Struct<{
readonly output: Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>;
readonly budgetExhausted: Boolean;
}>;
readonly failure: Subagent.SubagentToolFailure<Never>;
readonly failureMode: "error";
}, AgentSpawner | ... 1 more ... | SubagentDurability>;
}>, undefined, undefined, undefined> & {
...;
}
Coordinator
,
model: Model<"openai", LanguageModel, OpenAiClient>
model
:
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
,
definitions: {
agent: string;
model: string;
tools: string;
}
definitions
},
{
agent: Definition<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}>, RunDispositionDeclaration<...> | undefined, unknown, Top | undefined> & Definition<...> & {
...;
}
agent
:
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
.
target: Definition<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}>, RunDispositionDeclaration<...> | undefined, unknown, Top | undefined> & Definition<...> & {
...;
}

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

target
,
model: Model<"openai", LanguageModel, OpenAiClient>
model
:
const ModelLive: Model<"openai", LanguageModel, OpenAiClient>
ModelLive
,
definitions: {
agent: string;
model: string;
tools: string;
}
definitions
},
],
{
NodeDurableAgentRuntimeOptions<ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never, ReconcilerError = never, ReconcilerRequirements = never>.filename: string
filename
: "./agents.sqlite",
NodeDurableAgentRuntimeOptions<ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never, ReconcilerError = never, ReconcilerRequirements = never>.deploymentId: string
deploymentId
: "attached-research",
NodeDurableAgentRuntimeOptions<ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never, ReconcilerError = never, ReconcilerRequirements = never>.producerId: string
producerId
: "worker-start-001",
NodeDurableAgentRuntimeOptions<ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never, ReconcilerError = never, ReconcilerRequirements = never>.workerConcurrency?: number | undefined

Default 1; bounded to 1..64.

workerConcurrency
: 4,
},
).
Pipeable.pipe<Layer.Layer<DurableAgentRuntime | MessageDeliveryStore | ThreadReader | NodeDurableAgentRuntimeConfig | (Crypto & DurableAgentRuntime) | (Crypto & MessageDeliveryStore) | (Crypto & ThreadReader) | (Crypto & NodeDurableAgentRuntimeConfig) | (SqlClient & DurableAgentRuntime) | (SqlClient & MessageDeliveryStore) | ... 6 more ... | NodeDurableHost.NodeDurableHost, DurableWorkerFailure | ... 2 more ... | NodePlatformConfigError, Handler<...> | ... 1 more ... | OpenAiClient>, Layer.Layer<...>, Layer.Layer<...>, Layer.Layer<...>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>, cd: (_: Layer.Layer<...>) => Layer.Layer<...>, de: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <SubagentReservations | OpenAiClient, never, Handler<"delegate_research_activities">>(that: Layer.Layer<Handler<"delegate_research_activities">, never, SubagentReservations | OpenAiClient>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, SubagentReservations | OpenAiClient | Exclude<RIn2, Handler<"delegate_research_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 ResearchLive: Layer.Layer<Handler<"delegate_research_activities">, never, SubagentReservations | OpenAiClient>
ResearchLive
),
import Layer
Layer
.
const provide: <never, never, SubagentReservations>(that: Layer.Layer<SubagentReservations, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, SubagentReservations>> (+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 SubagentReservationsMemoryLive: Layer.Layer<SubagentReservations, never, never>

In-memory reservation ledger. All state lives in one Ref owned by the Layer's Scope; every transition is a single atomic Ref.modify.

SubagentReservationsMemoryLive
),
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
),
import Layer
Layer
.
const provide: <never, ConfigError, OpenAiClient>(that: Layer.Layer<OpenAiClient, ConfigError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, ConfigError | E2, Exclude<RIn2, OpenAiClient>> (+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 OpenAiLive: Layer.Layer<OpenAiClient, ConfigError, never>
OpenAiLive
),
);

Each { agent, model, definitions } entry registers an agent with the host. Register both the coordinator and the exact child used by Research, so recovery can find them after a restart. definitions contains the code versions; SQLite stores accepted work and recorded results.

Save this as durable-delegation-host.ts. It reuses the attached example files and the provider setup from node-agent.ts.

delegation.ts
import {
import Subagent
Subagent
} from "@yielded/agent";
import {
const Researcher: Definition<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}>, undefined, undefined, undefined> & {
...;
}
Researcher
} from "./researcher.ts";
export const
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
=
import Subagent
Subagent
.
make<"delegate_research_activities", Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, {
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}, Struct<...>, Struct<...>, Never, never, never, 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", {
target: Definition<Struct<{
readonly city: String;
readonly focus: String;
}>, Struct<{
readonly activities: $Array<String>;
readonly researchNotes: String;
}>, ({ city, focus }: {
readonly city: string;
readonly focus: string;
}) => string, Toolkit<{
readonly search_activities: Tool<"search_activities", {
readonly parameters: Struct<{
readonly city: String;
}>;
readonly success: $Array<String>;
readonly failure: Never;
readonly failureMode: "error";
}, never>;
}>, RunDispositionDeclaration<...> | undefined, unknown, Top | undefined> & Definition<...> & {
...;
}

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

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

The declaration is the same for ephemeral and durable attached execution. Put Research.tool in the parent’s toolkit. The host supplies durability; no different subagent constructor is needed.

import {
import NodeDurableHost
NodeDurableHost
} from "@yielded/agent-platform-node";
import {
import NodeRuntime
NodeRuntime
} from "@effect/platform-node";
import {
import Effect
Effect
} from "effect";
import {
const HostLive: Layer<DurableAgentRuntime | MessageDeliveryStore | ThreadReader | NodeDurableAgentRuntimeConfig | (Crypto & DurableAgentRuntime) | (Crypto & MessageDeliveryStore) | (Crypto & ThreadReader) | (Crypto & NodeDurableAgentRuntimeConfig) | (SqlClient & DurableAgentRuntime) | (SqlClient & MessageDeliveryStore) | ... 6 more ... | NodeDurableHost.NodeDurableHost, DurableWorkerFailure | ... 3 more ... | ConfigError, never>
HostLive
} from "./durable-delegation-host.ts";
import NodeRuntime
NodeRuntime
.
const runMain: <BindingUnavailable | DurableWorkerFailure | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError | ConfigError, void>(effect: Effect.Effect<void, BindingUnavailable | DurableWorkerFailure | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError | ConfigError, never>, options?: {
readonly disableErrorReporting?: boolean | undefined;
readonly teardown?: Teardown | undefined;
}) => void (+1 overload)

Helps you run a main effect with built-in error handling, logging, and signal management.

When to use

Use to run a Node.js application's main Effect with structured error handling, log management, interrupt support, or advanced teardown capabilities.

Details

This function launches an Effect as the main entry point, setting exit codes based on success or failure, handling interrupts (e.g., Ctrl+C), and optionally logging errors. By default, it logs errors and uses a "pretty" format, but both behaviors can be turned off. You can also provide custom teardown logic to finalize resources or produce different exit codes.

The optional configuration object can include:

  • disableErrorReporting: Turn off automatic error logging.
  • teardown: Provide custom finalization logic.

@category ― running

@since ― 4.0.0

runMain
(
import NodeDurableHost
NodeDurableHost
.
const run: Effect.Effect<void, BindingUnavailable | DurableWorkerFailure, NodeDurableHost.NodeDurableHost>

Supervise the host's existing workers without starting another pool. Use with Effect.provide(HostLive) and NodeRuntime.runMain; race it with a server Effect when the same process also serves requests. Unlike Layer.launch, this observes worker failures.

run
.
Pipeable.pipe<Effect.Effect<void, BindingUnavailable | DurableWorkerFailure, NodeDurableHost.NodeDurableHost>, Effect.Effect<void, BindingUnavailable | DurableWorkerFailure | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError | ConfigError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, BindingUnavailable | DurableWorkerFailure, NodeDurableHost.NodeDurableHost>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <DurableAgentRuntime | MessageDeliveryStore | ThreadReader | NodeDurableAgentRuntimeConfig | (Crypto & DurableAgentRuntime) | (Crypto & MessageDeliveryStore) | (Crypto & ThreadReader) | (Crypto & NodeDurableAgentRuntimeConfig) | (SqlClient & DurableAgentRuntime) | (SqlClient & MessageDeliveryStore) | ... 6 more ... | NodeDurableHost.NodeDurableHost, DurableWorkerFailure | ... 3 more ... | ConfigError, never>(layer: Layer<...>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+5 overloads)

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

Example (Providing dependencies with a layer)

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

@category ― providing services

@since ― 2.0.0

provide
(
const HostLive: Layer<DurableAgentRuntime | MessageDeliveryStore | ThreadReader | NodeDurableAgentRuntimeConfig | (Crypto & DurableAgentRuntime) | (Crypto & MessageDeliveryStore) | (Crypto & ThreadReader) | (Crypto & NodeDurableAgentRuntimeConfig) | (SqlClient & DurableAgentRuntime) | (SqlClient & MessageDeliveryStore) | ... 6 more ... | NodeDurableHost.NodeDurableHost, DurableWorkerFailure | ... 3 more ... | ConfigError, never>
HostLive
)));

Use the Node.js setup to submit Coordinator with { city: "Lisbon" }. The Cloudflare host supports the same attached lifecycle.

The parent’s next model call waits until its current tool batch settles. Children can run concurrently; the waiting parent releases its execution slot for other work.

After a restart, recovery reconnects to the same child and restores recorded results, policy, and budget reservations. Uncertain admission does not launch a replacement child. Register one current binding per stable Agent ID. Pending delegation operations retain their original replay contract, while child identity, lineage, and accepted delivery evidence remain unchanged.

Aborting the parent propagates cancellation to its children and joins their terminal outcomes. Cancellation cannot undo external effects. See recovery details and failure handling.

To keep the parent responding while its child runs, use durable background subagents.