Skip to content

Extensions

Effect Workflows

@yielded/agent-workflow drives the durable agent runtime through upstream Effect Workflow. Supply a WorkflowEngine Layer, durable dispatch storage, and a host-owned repair trigger. The agent definitions, registrations, and durable recovery rules stay the same when you replace the engine Layer.

The shared host has no Node.js or Cloudflare dependency. Platform adapters supply storage and repair scheduling. The Node.js setup below uses SQLite and a single-process Cluster runner.

bun add @yielded/agent-workflow@beta effect

Keep framework packages at one release and use compatible Effect and provider packages.

Author workflows with Effect’s Workflow.make, toLayer, and execute. AgentWorkflow.execute runs a registered Agent as an Effect inside the handler and returns its Schema-decoded output. A pending Agent suspends the handler through Effect’s DurableDeferred; approval resolution and settlement resume it without keeping a polling fiber alive in the parent.

import {
import Agent
Agent
} from "@yielded/agent";
import {
import AgentWorkflow
AgentWorkflow
} from "@yielded/agent-workflow";
import {
import Schema
Schema
} from "effect";
import {
import Toolkit
Toolkit
} from "effect/ai";
import {
import Workflow
Workflow
} from "effect/workflow";
const
const triage: Agent.Definition<Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, "Classify the severity of the bug report.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "triage";
}
triage
=
import Agent
Agent
.
function make<"triage", Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, "Classify the severity of the bug report.", Toolkit.Toolkit<{}>, undefined>(id: "triage", options: Agent.DefinitionOptions<Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, "Classify the severity of the bug report.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly inputPrompt?: undefined;
readonly runDisposition?: undefined;
}): Agent.Definition<...> & {
...;
} (+3 overloads)

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

make
("triage", {
DefinitionOptions<String, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, "Classify the severity of the bug report.", Toolkit<{}>, undefined, undefined, undefined>.input: Schema.String
input
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
,
DefinitionOptions<String, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, "Classify the severity of the bug report.", Toolkit<{}>, undefined, undefined, undefined>.output: Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>
output
:
import Schema
Schema
.
function Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>(fields: {
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}): Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>

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
({
severity: Schema.Literals<readonly ["low", "high", "critical"]>
severity
:
import Schema
Schema
.
function Literals<readonly ["low", "high", "critical"]>(literals: readonly ["low", "high", "critical"]): Schema.Literals<readonly ["low", "high", "critical"]>

Creates a union schema from an array of literal values.

Example (Defining status codes)

import { Schema } from "effect"
const schema = Schema.Literals(["active", "inactive", "pending"])
Schema.decodeSync(schema)("active") // => "active"

@see ― Literal for a schema that represents a single literal.

@category ― constructors

@since ― 4.0.0

Literals
(["low", "high", "critical"]) }),
DefinitionOptions<String, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, "Classify the severity of the bug report.", Toolkit<{}>, undefined, undefined, undefined>.instructions: "Classify the severity of the bug report."
instructions
: "Classify the severity of the bug report.",
DefinitionOptions<String, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, "Classify the severity of the bug report.", Toolkit<{}>, undefined, undefined, undefined>.toolkit: Toolkit.Toolkit<{}>
toolkit
:
import Toolkit
Toolkit
.
const empty: Toolkit.Toolkit<{}>

An empty toolkit with no tools.

When to use

Use when you need an empty starting point for building toolkits or a default toolkit value that can be extended with merge.

@stability ― unstable

@category ― constructors

@since ― 4.0.0

empty
,
});
const
const Review: Workflow.Workflow<"Review", Schema.Struct<{
issueId: Schema.String;
report: Schema.String;
}>, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, ... 8 more ..., typeof WorkflowExecutionFailure]>>
Review
=
import Workflow
Workflow
.
const make: <"Review", {
issueId: Schema.String;
report: Schema.String;
}, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, typeof FenceRejected, ... 7 more ..., typeof WorkflowExecutionFailure]>>(tag: "Review", options: {
...;
}) => Workflow.Workflow<...>

Creates a durable workflow definition with schemas, annotations, and deterministic execution IDs derived from the workflow tag and idempotency key.

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
("Review", {
payload: {
issueId: Schema.String;
report: Schema.String;
}
payload
: {
issueId: Schema.String
issueId
:
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
,
report: Schema.String
report
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
},
success?: Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}> | undefined
success
:
const triage: Agent.Definition<Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, "Classify the severity of the bug report.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "triage";
}
triage
.
Definition<String, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, "Classify the severity of the bug report.", Toolkit<{}>, undefined, undefined, undefined>.output: Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>

Canonical schema used to decode the final model output.

output
,
error?: Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, typeof FenceRejected, typeof LedgerError, typeof OperationDenied, typeof SettlementConflict, typeof ThreadNotMaterialized, typeof ThreadStoreError, typeof WorkflowDispatchError, typeof WorkflowAdmissionClosed, typeof WorkflowExecutionFailure]> | undefined
error
:
import AgentWorkflow
AgentWorkflow
.
const Error: Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, typeof FenceRejected, typeof LedgerError, typeof OperationDenied, typeof SettlementConflict, typeof ThreadNotMaterialized, typeof ThreadStoreError, typeof WorkflowDispatchError, typeof WorkflowAdmissionClosed, typeof WorkflowExecutionFailure]>

Schema for the exact typed failure channel of execute, suitable for Workflow.make({ error }).

Error
,
idempotencyKey: (payload: {
readonly issueId: string;
readonly report: string;
}) => string
idempotencyKey
: ({
issueId: string
issueId
}) =>
issueId: string
issueId
,
});
export const
const ReviewLive: Layer<never, never, WorkflowEngine | WorkflowAgentHost>
ReviewLive
=
const Review: Workflow.Workflow<"Review", Schema.Struct<{
issueId: Schema.String;
report: Schema.String;
}>, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, ... 8 more ..., typeof WorkflowExecutionFailure]>>
Review
.
Workflow<"Review", Struct<{ issueId: String; report: String; }>, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, Union<...>>.toLayer: <WorkflowEngine | WorkflowInstance | WorkflowAgentHost>(execute: (payload: {
readonly issueId: string;
readonly report: string;
}, executionId: string) => Effect<{
readonly severity: "low" | "high" | "critical";
}, AgentInputError | AgentOutputError | AdmissionConflict | AdmissionPolicyError | AppendConflict | BindingUnavailable | DigestError | DurableRuntimeFailpointError | ... 8 more ... | WorkflowExecutionFailure, WorkflowEngine | ... 1 more ... | WorkflowAgentHost>) => Layer<...>

Create a layer that registers the workflow and provides an effect to execute it.

toLayer
(({
report: string
report
}) =>
import AgentWorkflow
AgentWorkflow
.
const execute: <Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>>(agent: WorkflowAgent<Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>>, input: string, options: WorkflowExecuteOptions) => Effect<{
readonly severity: "low" | "high" | "critical";
}, AgentInputError | AgentOutputError | AdmissionConflict | ... 13 more ... | WorkflowExecutionFailure, WorkflowEngine | ... 1 more ... | WorkflowAgentHost>

Run a registered agent inside a native Workflow.toLayer handler. One stable name identifies one submission in that parent execution. Replays verify admission identity and decode the canonical output again. Pending work suspends through Effect DurableDeferred; neither suspension nor parent interruption aborts accepted agent work. Use the host's authorized abort. Provide WorkflowAgentHost at the application boundary, sharing the parent's WorkflowEngine. Pass the exact Agent Definition instance used in runtime registration; a same-ID copy fails with BindingUnavailable before input encoding or admission.

execute
(
const triage: Agent.Definition<Schema.String, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, "Classify the severity of the bug report.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "triage";
}
triage
,
report: string
report
, {
WorkflowExecuteOptions.name: string

Stable, nonempty step name, unique within one parent execution. Use item IDs in loops.

name
: "triage" }),
);
export const
const review: Effect<{
readonly severity: "low" | "high" | "critical";
}, AgentInputError | AgentOutputError | AdmissionConflict | AdmissionPolicyError | AppendConflict | BindingUnavailable | DigestError | DurableRuntimeFailpointError | FenceRejected | LedgerError | OperationDenied | SettlementConflict | ThreadNotMaterialized | ThreadStoreError | WorkflowDispatchError | WorkflowAdmissionClosed | WorkflowExecutionFailure, WorkflowEngine>
review
=
const Review: Workflow.Workflow<"Review", Schema.Struct<{
issueId: Schema.String;
report: Schema.String;
}>, Schema.Struct<{
readonly severity: Schema.Literals<readonly ["low", "high", "critical"]>;
}>, Schema.Union<readonly [typeof AgentInputError, typeof AgentOutputError, typeof AdmissionConflict, typeof AdmissionPolicyError, typeof AppendConflict, typeof BindingUnavailable, typeof DigestError, typeof DurableRuntimeFailpointError, ... 8 more ..., typeof WorkflowExecutionFailure]>>
Review
.
Workflow<"Review", Struct<{ issueId: String; report: String; }>, Struct<{ readonly severity: Literals<readonly ["low", "high", "critical"]>; }>, Union<...>>.execute: <false>(payload: {
readonly issueId: string;
readonly report: string;
}, options?: {
readonly discard?: false | undefined;
} | undefined) => Effect<{
readonly severity: "low" | "high" | "critical";
}, AgentInputError | AgentOutputError | AdmissionConflict | AdmissionPolicyError | AppendConflict | BindingUnavailable | DigestError | DurableRuntimeFailpointError | FenceRejected | ... 7 more ... | WorkflowExecutionFailure, WorkflowEngine>

Execute the workflow with the given payload.

execute
({
issueId: string
issueId
: "123",
report: string
report
: "Login is broken" });

Register triage and its model in the durable runtime used by your host. Supply the resulting host Layer to ReviewLive with Layer.provideMerge, then provide that Layer to review. Share one WorkflowEngine Layer between the parent and host; a mismatched engine fails before admission. Multi-step handlers use ordinary Effect.gen and bounded Effect.all.

The step name must be nonempty, stable across replays, and unique within a parent execution. Use stable item IDs for repeated calls in a loop. The parent workflow identity, step name, deployment, and configured principal determine a private Thread and submission key. Reusing a step with changed input, Agent identity, or registered version declarations fails with an admission conflict rather than silently starting new work. Registered admission requires exactly one version for that Agent identity and the exact Agent Definition instance passed to registration. A same-ID copy or replacement fails with BindingUnavailable before input encoding or admission, including on replay. Import the same definition into registration and the workflow handler; there is no object identity persisted across restarts. Explicit host.submit remains available for versioned routing.

AgentWorkflow.Error is the Schema for the exact typed failure channel. Failed or aborted Agent settlements become WorkflowExecutionFailure; invalid output becomes AgentOutputError. Admission, authorization, and dispatch failures retain their original tags. Infrastructure failure can occur after admission; retries with the same step identity reconnect to the accepted work. Choose retry behavior with ordinary Effect combinators. Defects in the underlying durable driver retain its existing suspension and repair behavior.

Parent interruption, timeout, or shutdown detaches the parent; it does not abort accepted Agent work. Use the host’s authorized abort command to cancel the Agent. Native compensation does not undo external tool effects. Ordinary uncertain tools still require explicit resolution.

WorkflowAgentHost.layer(options) consumes these services through Layers:

Service Responsibility
DurableAgentRuntime, SubmissionLedger, and DurableRuntimeConfig Agent execution, admission, canonical history, recovery, and deployment identity
WorkflowEngine Native Workflow execution and persistence
WorkflowDispatchStore Durable dispatch intents retained until completion is verified
WorkflowRepairTrigger Startup and repeated repair after lost hints or host restarts

The runtime Layer owns executable registrations and their model, tool, instruction, and schema services. Supply those application services and the host’s Crypto service through ordinary Layer composition. The Workflow handler passes only a Thread ID to processThreadHead; it cannot replace captured model or tool services on an execution.

Keep deploymentId identical in both runtime and Workflow host options. The optional workflowName is a stable versioned prefix, defaulting to @yielded/agent/submission/v1. The native name appends /deployment/<length>:<deploymentId>. Keep one host registration per deployment, name, and engine. Changing that identity leaves the old dispatch obligations for their original host to repair.

The required principal is application-owned authority for AgentWorkflow.execute, never model-supplied input. Model and tool services remain captured by runtime registration; handler services cannot replace them. Input encoding and output decoding requirements remain visible in the execution Effect. Each result read rechecks authorization, including on workflow replay.

Operation Meaning
submit(agent, input, options) Admit durable work, persist its dispatch intent, and request native execution
awaitSettlement(receipt) Wait for the canonical terminal outcome
observe(receipt) Stream canonical records through the runtime’s authorization policy
submissionStatus(receipt) Read authorized pending or settled status without waiting
abort(command) Record authorized abort intent without replacing an existing Settlement
resolveApproval(command) Record an authorized approval decision for later processing
resolveUnknown(command) Record an authorized resolution of uncertain external work

A dispatch error or timeout can occur after admission. Retry the same input with the same idempotency key; accepted work remains recoverable. A receipt identifies work and grants no authorization by itself. Interrupting a waiter or observer only detaches that caller. Use abort for cancellation; native Workflow interruption does not implement durable abort. Approval and unknown-outcome resolutions resume through repair.

The shared Workflow host has no internal poll loop. Every host must provide a WorkflowRepairTrigger that invokes repair at startup and continues after lost hints and host restarts. It must stop invoking repair when its Scope closes. host.repair is also available for explicit bounded repair.

Each repair pass selects at most repairBatchSize accepted submissions and that many dispatch intents. Each scan and each item has the dispatchTimeoutMillis bound. Failed items leave their obligations intact while other items can advance. Dispatch intents remain until native success references the matching canonical Settlement, even if the submission ledger already settled. For AgentWorkflow.execute, the intent also retains the parent’s durable completion token. Repair delivers the canonical reference through DurableDeferred before removing the intent. Custom stores must atomically attach one token, preserve it on subsequent put calls, return the retained intent, and compare the entire intent before removal. A stale cleanup must fail instead of erasing a newly attached notification obligation. Admission, dispatch persistence, and native Workflow persistence are separate commits. Never enclose agent execution in a SQL transaction.

Pending status, an empty processing result, or Workflow suspension is not completion. Infrastructure failures suspend native execution; repeated repair drives recovery and resumes. Ordinary tools are not wrapped in Activities and retain the unknown-outcome rules described in durability.

executionConcurrency limits Attempts in this host Layer instance, not across a fleet. Within that host, only one recovery or processing pass runs for a Thread at a time. Each Attempt owns its resources and releases ownership and permits before native suspension. Closing the host Scope stops its repair trigger and closes acquired resources.

bun add @yielded/agent-platform-node@beta effect

This example reuses node-agent.ts from the Node.js guide, including its model client and registration versions.

This assembly uses ClusterWorkflowEngine with SingleRunner on one Node process. runnerStorage: "memory" keeps runner bookkeeping in memory; native messages and replies still persist in SQL. The dispatch store shares that SQL connection. Canonical agent history and the submission ledger use a separate SQLite file.

import {
class NodeDurableAgentRuntime

The DN Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint). layer(options) decodes the configuration, opens ONE SQLite database serving both the Thread Log and the Submission Ledger (so claims fence the same producer epochs), wires the Node wake scheduler with its ledger-scan fallback, exposes independent message delivery storage, wraps the ledger with the shutdown ownership drain, defaults the Tool reconciliation policy to the fail-closed ToolReconciler.uncertain (override via options.toolReconciler), and provides a ready DurableAgentRuntime on top. Storage compatibility is verified during construction: an incompatible database file fails the Layer with SqliteStorageCompatibilityError before anything is mutated (DEPLOY-008).

NodeDurableAgentRuntime
} from "@yielded/agent-platform-node/node-durable-agent-runtime";
import {
class NodeWorkflowRepairTrigger

A host-scoped startup and polling trigger. No ordinary Node agent worker is started.

NodeWorkflowRepairTrigger
,
class SqlWorkflowDispatchStore

Durable dispatch outbox over an application-supplied SqlClient. This adapter uses SQLite/PostgreSQL SQL syntax and is certified with SQLite. It does not own an engine or a database connection. Agent admission, dispatch persistence, and native Workflow storage are separate commits; the registered repair trigger closes those gaps. Stored version or shape mismatches fail typed and require an explicit data reset.

SqlWorkflowDispatchStore
,
} from "@yielded/agent-platform-node/node-workflow";
import {
class WorkflowAgentHost

Optional engine-independent host. Supply the upstream WorkflowEngine, the existing durable runtime and ledger, durable dispatch storage, and a host-owned repair trigger. Do not also start the ordinary Node worker loop. Waiter interruption only detaches; abort and resolutions retain the runtime's authorization and durable intent protocol.

WorkflowAgentHost
} from "@yielded/agent-workflow/workflow-agent-host";
import {
import NodeCrypto
NodeCrypto
} from "@effect/platform-node";
import {
import SqliteClient
SqliteClient
} from "@effect/sql-sqlite-node";
import {
import Layer
Layer
} from "effect";
import {
import ClusterWorkflowEngine
ClusterWorkflowEngine
,
import SingleRunner
SingleRunner
} from "effect/cluster";
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
,
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
planner
} from "./node-agent.ts";
const
const workflowDatabase: Layer.Layer<SqliteClient.SqliteClient | SqlClient, never, never>
workflowDatabase
=
import SqliteClient
SqliteClient
.
const layer: (config: SqliteClient.SqliteClientConfig) => Layer.Layer<SqliteClient.SqliteClient | SqlClient>

Builds a layer from a node SQLite client configuration, providing both SqliteClient and the generic SqlClient service.

@category ― layers

@since ― 4.0.0

layer
({
SqliteClientConfig.filename: string
filename
: "./workflow.sqlite" });
const
const engine: Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>
engine
=
import ClusterWorkflowEngine
ClusterWorkflowEngine
.
const layer: Layer.Layer<WorkflowEngine, never, Sharding | MessageStorage>

Layer that provides WorkflowEngine.WorkflowEngine using the cluster workflow engine implementation.

Details

It requires cluster sharding and message storage, and also registers the durable clock entity used for workflow wakeups.

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
.
Pipeable.pipe<Layer.Layer<WorkflowEngine, never, Sharding | MessageStorage>, Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>>(this: Layer.Layer<WorkflowEngine, never, Sharding | MessageStorage>, ab: (_: Layer.Layer<WorkflowEngine, never, Sharding | MessageStorage>) => Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <SqlClient | Crypto, ConfigError, Sharding | MessageStorage | Runners>(that: Layer.Layer<Sharding | MessageStorage | Runners, ConfigError, SqlClient | Crypto>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, ConfigError | E2, SqlClient | Crypto | Exclude<RIn2, Sharding | MessageStorage | Runners>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import SingleRunner
SingleRunner
.
const layer: (options?: {
readonly shardingConfig?: Partial<ShardingConfig["Service"]> | undefined;
readonly runnerStorage?: "memory" | "sql" | undefined;
}) => Layer.Layer<Sharding | Runners | MessageStorage, ConfigError, SqlClient | Crypto>

Provides a SQL-backed single-node cluster for running durable entities and workflows.

When to use

Use to run durable cluster entities and workflows in a local, embedded, or small single-node process while keeping mailbox and reply state in SQL.

Details

The layer provides Sharding, Runners, and MessageStorage. It loads ShardingConfig from environment variables and overlays options.shardingConfig when provided. Message storage is always SQL-backed; runner storage is SQL-backed by default and switches to in-memory storage when runnerStorage is set to "memory".

Gotchas

  • Even when runnerStorage is "memory", message storage remains SQL-backed, so callers must still provide SqlClient and Crypto.Crypto (used to hash over-length message deduplication keys).
  • Runner communication and runner health are no-op services, so this layer is for single-process use rather than multi-runner coordination.

@see ― ShardingConfig.layerFromEnv for loading environment configuration before applying shardingConfig overrides

@see ― SqlMessageStorage.layer for the SQL-backed message storage that this layer provides

@see ― SqlRunnerStorage.layer for the default SQL-backed runner storage selected when runnerStorage is omitted or "sql"

@see ― RunnerStorage.layerMemory for the in-memory runner storage selected by runnerStorage: "memory"

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
({
runnerStorage?: "memory" | "sql" | undefined
runnerStorage
: "memory" })),
);
const
const infrastructure: Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, Crypto>
infrastructure
=
import Layer
Layer
.
const mergeAll: <[Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>, Layer.Layer<WorkflowDispatchStore, WorkflowDispatchError, SqlClient>]>(layers_0: Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>, layers_1: Layer.Layer<WorkflowDispatchStore, WorkflowDispatchError, SqlClient>) => Layer.Layer<...>

Combines all the provided layers concurrently, creating a new layer with merged input, error, and output types.

When to use

Use when you need to combine multiple independent layers.

Details

All layers are built concurrently, and their outputs are merged into a single layer.

If multiple merged layers depend on the same layer value, that dependency is shared by default. Reuse a named layer value when you want services to share the same resource, such as one database pool.

Example (Merging independent layers)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
const dbLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg)))
})
const mergedLayer = Layer.mergeAll(dbLayer, loggerLayer)
const program = Logger.use((logger) => logger.log("ready"))
Effect.runSync(Effect.provide(program, mergedLayer))
logs // => ["ready"]

@see ― merge for merging one layer with another layer or array

@category ― zipping

@since ― 2.0.0

mergeAll
(
const engine: Layer.Layer<WorkflowEngine, ConfigError, SqlClient | Crypto>
engine
,
class SqlWorkflowDispatchStore

Durable dispatch outbox over an application-supplied SqlClient. This adapter uses SQLite/PostgreSQL SQL syntax and is certified with SQLite. It does not own an engine or a database connection. Agent admission, dispatch persistence, and native Workflow storage are separate commits; the registered repair trigger closes those gaps. Stored version or shape mismatches fail typed and require an explicit data reset.

SqlWorkflowDispatchStore
.
SqlWorkflowDispatchStore.layer: Layer.Layer<WorkflowDispatchStore, WorkflowDispatchError, SqlClient>
layer
).
Pipeable.pipe<Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, SqlClient | Crypto>, Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, Crypto>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, SqlClient | Crypto>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, SqliteClient.SqliteClient | SqlClient>(that: Layer.Layer<SqliteClient.SqliteClient | SqlClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, SqliteClient.SqliteClient | SqlClient>> (+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 workflowDatabase: Layer.Layer<SqliteClient.SqliteClient | SqlClient, never, never>
workflowDatabase
),
);
export const
const WorkflowLive: Layer.Layer<WorkflowAgentHost | WorkflowEngine | WorkflowDispatchStore, DigestError | WorkflowDispatchError | ConfigError | WorkflowHostConfigError | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError | NodeWorkflowRepairConfigError, never>
WorkflowLive
=
class WorkflowAgentHost

Optional engine-independent host. Supply the upstream WorkflowEngine, the existing durable runtime and ledger, durable dispatch storage, and a host-owned repair trigger. Do not also start the ordinary Node worker loop. Waiter interruption only detaches; abort and resolutions retain the runtime's authorization and durable intent protocol.

WorkflowAgentHost
.
WorkflowAgentHost.layer(options: WorkflowAgentHostOptions): Layer.Layer<WorkflowAgentHost, WorkflowHostConfigError, WorkflowEngine | DurableAgentRuntime | Crypto | WorkflowDispatchStore | DurableRuntimeConfig | SubmissionLedger | WorkflowRepairTrigger>

Drive the injected runtime, whose Layer owns executable registrations and their services.

layer
({
WorkflowAgentHostOptions.deploymentId: string
deploymentId
: "travel-planner",
WorkflowAgentHostOptions.principal: string

Application-owned identity for submissions from AgentWorkflow.execute.

principal
: "travel-planner-service",
WorkflowAgentHostOptions.executionConcurrency?: number | undefined

Concurrent Attempts within this host Layer instance, not a fleet-wide limit. Default 1.

executionConcurrency
: 4,
WorkflowAgentHostOptions.repairBatchSize?: number | undefined

Maximum entries per scan per repair invocation. Default 32.

repairBatchSize
: 32,
WorkflowAgentHostOptions.dispatchTimeoutMillis?: number | undefined

Bound each dispatch or scan, including a retrying native engine. Default 10000 ms.

dispatchTimeoutMillis
: 10_000,
}).
Pipeable.pipe<Layer.Layer<WorkflowAgentHost, WorkflowHostConfigError, WorkflowEngine | DurableAgentRuntime | Crypto | WorkflowDispatchStore | DurableRuntimeConfig | SubmissionLedger | WorkflowRepairTrigger>, Layer.Layer<WorkflowAgentHost, DigestError | WorkflowHostConfigError | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError, WorkflowEngine | ... 2 more ... | WorkflowRepairTrigger>, 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<...>, ef: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <OpenAiClient, DigestError | MessageDeliveryError | SqliteStorageInitializationError | NodePlatformConfigError, DurableAgentRuntime | SqliteClient.SqliteClient | SqlClient | Crypto | DurableRuntimeConfig | SubmissionLedger | ThreadStore | MessageDeliveryStore | WakeScheduler | ... 7 more ... | NodeDurableAgentRuntimeConfig>(that: Layer.Layer<...>) => <RIn2, E2, ROut2>(self: Layer.Layer<...>) => Layer.Layer<...> (+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
(
class NodeDurableAgentRuntime

The DN Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint). layer(options) decodes the configuration, opens ONE SQLite database serving both the Thread Log and the Submission Ledger (so claims fence the same producer epochs), wires the Node wake scheduler with its ledger-scan fallback, exposes independent message delivery storage, wraps the ledger with the shutdown ownership drain, defaults the Tool reconciliation policy to the fail-closed ToolReconciler.uncertain (override via options.toolReconciler), and provides a ready DurableAgentRuntime on top. Storage compatibility is verified during construction: an incompatible database file fails the Layer with SqliteStorageCompatibilityError before anything is mutated (DEPLOY-008).

NodeDurableAgentRuntime
.
NodeDurableAgentRuntime.layerRegistered<readonly [{
readonly agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
};
readonly model: Model<"openai", LanguageModel, OpenAiClient>;
readonly definitions: {
agent: string;
model: string;
tools: string;
};
}], never, never, never, never, never, never>(registrations: readonly [{
readonly agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
};
readonly model: Model<"openai", LanguageModel, OpenAiClient>;
readonly definitions: {
agent: string;
model: string;
tools: string;
};
}], options: NodeDurableAgentRuntimeOptions<...>): Layer.Layer<...>

Own typed executable registrations for every worker using this Node runtime.

layerRegistered
([{
agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
agent
:
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
planner
,
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
: "travel-planner",
NodeDurableAgentRuntimeOptions<ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never, ReconcilerError = never, ReconcilerRequirements = never>.producerId: string
producerId
: "workflow-worker-1",
}),
),
import Layer
Layer
.
const provideMerge: <Crypto, WorkflowDispatchError | ConfigError, WorkflowEngine | WorkflowDispatchStore>(that: Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, Crypto>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<WorkflowEngine | WorkflowDispatchStore | ROut2, WorkflowDispatchError | ... 1 more ... | E2, Crypto | Exclude<...>> (+3 overloads)

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

When to use

Use when you need to compose Layers while keeping both the constructed service and the dependency used to build it available.

Details

Prefer

provide

when the dependency should stay private.

Example (Providing dependencies while retaining services)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
// 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 and merge all services together
const allServicesLayer = userServiceLayer.pipe(
Layer.provideMerge(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now the resulting layer provides UserService, Database, AND Logger
const program = Effect.gen(function*() {
const userService = yield* UserService
const logger = yield* Logger // Still available!
const database = yield* Database // Still available!
const user = yield* userService.getUser("123")
yield* logger.log(`Found user: ${user.name}`)
return user
}).pipe(
Effect.provide(allServicesLayer)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123", "[LOG] Found user: DB: SELECT * FROM users WHERE id = 123"]

@see ― provide for keeping dependency services private

@category ― providing services

@since ― 2.0.0

provideMerge
(
const infrastructure: Layer.Layer<WorkflowEngine | WorkflowDispatchStore, WorkflowDispatchError | ConfigError, Crypto>
infrastructure
),
import Layer
Layer
.
const provide: <never, NodeWorkflowRepairConfigError, WorkflowRepairTrigger>(that: Layer.Layer<WorkflowRepairTrigger, NodeWorkflowRepairConfigError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, NodeWorkflowRepairConfigError | E2, Exclude<RIn2, WorkflowRepairTrigger>> (+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
(
class NodeWorkflowRepairTrigger

A host-scoped startup and polling trigger. No ordinary Node agent worker is started.

NodeWorkflowRepairTrigger
.
NodeWorkflowRepairTrigger.layer(options?: {
readonly interval?: Input;
}): Layer.Layer<WorkflowRepairTrigger, NodeWorkflowRepairConfigError>
layer
({
interval?: Input | undefined
interval
: "1 second" })),
import Layer
Layer
.
const provide: <never, never, Crypto>(that: Layer.Layer<Crypto, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, Crypto>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import NodeCrypto
NodeCrypto
.
const layer: Layer.Layer<Crypto, never, never>

Layer that provides the Node.js Crypto service implementation.

@category ― layers

@since ― 1.0.0

layer
),
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
),
);

Provide the remaining model, tool, instruction, and schema services to this Layer, then share it with the application effects that use WorkflowAgentHost. Its inferred types retain construction errors and application requirements. Acquiring it registers native execution and starts repair. Do not also start the ordinary NodeDurableHost worker loop for this deployment. NodeWorkflowRepairTrigger runs repair at startup and at the configured interval within the host Scope.

See the Node.js guide for registration and storage configuration, and runtime services for context preparation and tool authorization.

This SQL assembly is certified for a single Node process. Its runner configuration and concurrency limit do not establish multi-runner support.