@yielded/agent-platform-node stores thread history and pending work in SQLite.
A bounded worker pool executes registered agents and recovers work after a restart.
Save this as node-agent.ts. The model’s client reads OPENAI_API_KEY from the environment.
The version declarations identify the agent, model, and tools used by accepted work.
}>,"Plan a trip with one itinerary entry per day.",Toolkit.Toolkit<{}>,undefined>(id:"trip-planner",options:Agent.DefinitionOptions<Schema.String,Schema.Struct<{
readonlyitinerary:Schema.$Array<Schema.String>;
}>,"Plan a trip with one itinerary entry per day.",Toolkit.Toolkit<{}>,undefined,undefined,undefined>&{
readonlyinputPrompt?:undefined;
readonlyrunDisposition?:undefined;
}):Agent.Definition<...>&{
...;
}(+3overloads)
Validate an agent ID and return a shallowly frozen, model-agnostic definition.
make("trip-planner",{
DefinitionOptions<String,Struct<{ readonlyitinerary:$Array<String>; }>,"Plan a trip with one itinerary entry per day.",Toolkit<{}>,undefined,undefined,undefined>.input: Schema.String
input:
importSchema
Schema.
constString: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<{ readonlyitinerary:$Array<String>; }>,"Plan a trip with one itinerary entry per day.",Toolkit<{}>,undefined,undefined,undefined>.output: Schema.Struct<{
readonlyitinerary:Schema.$Array<Schema.String>;
}>
output:
importSchema
Schema.
functionStruct<{
readonlyitinerary:Schema.$Array<Schema.String>;
}>(fields:{
readonlyitinerary:Schema.$Array<Schema.String>;
}):Schema.Struct<{
readonlyitinerary:Schema.$Array<Schema.String>;
}>
Defines a struct schema from a map of field schemas.
Details
Each field value is a schema. Use
optionalKey
or
optional
to
mark fields as optional, and
mutableKey
to mark them as mutable.
The resulting schema's Type is a readonly object type with the fields'
decoded types. The Encoded form mirrors the field schemas' encoded types.
Declared fields may be inherited and are copied to own properties in the
output. The __proto__ field is accepted only when it is an own property.
Parsing does not guarantee that output keys retain their input order.
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<{ readonlyitinerary:$Array<String>; }>,"Plan a trip with one itinerary entry per day.",Toolkit<{}>,undefined,undefined,undefined>.instructions: "Plan a trip with one itinerary entry per day."
instructions:"Plan a trip with one itinerary entry per day.",
DefinitionOptions<String,Struct<{ readonlyitinerary:$Array<String>; }>,"Plan a trip with one itinerary entry per day.",Toolkit<{}>,undefined,undefined,undefined>.toolkit: Toolkit.Toolkit<{}>
toolkit:
importToolkit
Toolkit.
constempty: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.
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.
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(
importFetchHttpClient
FetchHttpClient.
constlayer:Layer.Layer<HttpClient,never,never>
Layer that provides an HttpClient implementation backed by the configured
Fetch function.
When to use
Use when an Effect program should execute HttpClient requests through the
platform fetch implementation, especially in browser, edge, or Node.js
runtimes with globalThis.fetch.
Details
The layer uses the current Fetch reference and optional RequestInit
service for each request. Request-specific method, headers, body, and abort
signal are supplied by the client and override matching RequestInit fields.
Gotchas
Fetch behavior comes from the runtime's implementation, so CORS, cookies,
redirects, abort handling, and streaming support can vary by platform. Stream
request bodies are sent as Web streams with duplex: "half", and any
content-length header is removed before calling fetch.
@see ― Fetch for supplying the fetch implementation used by this layer
@see ― RequestInit for default RequestInit options applied before request-specific fields
Use a persistent database path with one live host per SQLite file. Give each replacement host
incarnation a distinct producerId.
The automatic host holds SQLite’s exclusive connection lock for its entire Scope. Another host
fails startup, and independent readers cannot access the database while that connection is alive.
Use a local filesystem with working SQLite locks; do not replace or unlink a live database file.
New files are initialized in WAL mode; existing files must already use WAL mode.
The host option synchronous: "NORMAL" opts its connection into WAL NORMAL; the default
is FULL. NORMAL survives process crashes, but power loss or an OS crash can lose
acknowledged commits and cause external effects to repeat during recovery.
Managed storage rejects custom SQLite triggers because the host owns all journal and ownership mutations.
workerConcurrency limits concurrently processed threads and defaults to one.
The managed host dispatches wake hints through one bounded queue, coalescing repeated hints
for pending or active threads. A shared periodic ledger scan recovers missed hints.
The scan stops when the last subscriber leaves and restarts when another subscribes.
NodeDurableHost.layer checks storage, recovers pending work, and starts the worker pool when
the Layer is acquired. Save this as node-host.ts, replacing producerId for each process start:
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<String,Struct<{
readonlyitinerary:$Array<String>;
}>,"Plan a trip with one itinerary entry per day.",Toolkit<{}>,undefined,undefined,undefined>&{
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.
TypeScript infers the registration’s model, tool, instruction, and schema service requirements.
Provide those services to the Layer, as OpenAiLive does here. Node supplies Crypto.
Save this as node-main.ts and run it with Node’s TypeScript transform support:
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.
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.
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.
NodeDurableHost.run observes the existing pool; calling it again does not start more workers.
If a worker fails, admission closes and run fails with the original typed error or defect.
NodeRuntime.runMain then closes the host. Use run rather than Layer.launch(HostLive),
which does not observe background worker failures. Interrupting only an observer leaves the
pool running; closing the host’s Scope closes admission, stops and joins workers, releases
ownership, and closes storage and application services.
If the process also serves requests, race the server Effect with NodeDurableHost.run using
Effect.raceFirst, and provide the shared HostLive to that combined Effect. A worker failure
then stops the server too. Creating two separate host Layers for the same SQLite file is unsupported.
To drive the durable runtime through an injected WorkflowEngine, follow the
Effect Workflows guide. Its Node.js setup
uses SQLite and a single-process Cluster runner.
Use NodeDurableAgentRuntime.layerRegistered when you own execution, as in the
Workflow assembly. It captures registrations and acquires storage without starting workers.
layerWithBindings accepts precompiled ResolvedBinding values whose application Scope you own;
layer constructs an unregistered runtime for explicit admission and execution.
The service class’s existing NodeDurableHost.layerRegistered, layerStack, and layer
constructors remain available for manually managed hosts. Import the class from
@yielded/agent-platform-node/node-durable-host when using these APIs; their workers start only
when you run host.runResolvedWorkers. The module-level NodeDurableHost.layer shown above
owns worker startup and is the default for an application.
These manual assemblies retain lease-based recovery and do not acquire the automatic host’s
exclusive authority or retire claims on startup. They remain suitable for explicit runtime
composition; a producer name alone never permits reclaiming a live lease.
Registrations carry application version declarations. Update them when behavior changes,
including tool implementations that JSON cannot represent. Register one current binding per
stable agentId; queued and resumed work uses that binding without requiring historical agent
or toolbox versions. Accepted inputs and prepared deliveries retain their original identities
and payloads. digestDefinitions computes the digests for explicit submissions;
DurableWorkerBinding.make(agent, digests) accepts precomputed digests.
An unresolved tool effect remains a parked Unknown Outcome with its settlement obligation intact.
Later input in the same Thread can run without replaying that effect. Approval waits and joined
input still preserve their ordering barriers, and live ownership prevents another claim. Inspect
the parked operation through explainThread, then use authorized resolution or abort when needed.
Pass service layers in the options to NodeDurableHost.layer or NodeDurableAgentRuntime.layer:
Option
Service
Default
runContext
RunContextPreparation
No prompt transform or transient reference context
toolAuthorization
RunToolAuthorization
Allow all tool calls
toolReconciler
ToolReconciler
Keep unconfirmed tool outcomes unknown
Add these options to the host assembly above. Use
{ runContext: RunContextLive } for prompt preparation,
or { toolAuthorization: SearchOnlyLive } for a tool policy.
Use { toolReconciler: SupplierReconcilerLive } for supplier-backed recovery of unconfirmed tool
outcomes. Configure these services independently or together.
Select native compaction by providing its Layer
directly to the host, for example HostLive.pipe(Layer.provide(ContextCompactor.layerRollover)).
Without an injected ContextCompactor, the host uses the default pruning and summarization strategy.
The assembled layer retains each extension’s construction errors and application dependencies
in its error and requirement types. The host supplies Crypto.Crypto. Provide the remaining
dependencies through ordinary Layer.provide composition before running the application.
Let layer or layerStack infer the types from your options. When annotating reusable options,
NodeDurableAgentRuntimeOptions<ContextError, ContextRequirements, AuthorizationError, AuthorizationRequirements, ReconcilerError, ReconcilerRequirements>
preserves all three layers’ construction contracts.
The runtime captures services when the host layer is acquired. Keep their resources alive for
its Scope. Providing replacements around a later worker call does not change the captured services.
toolFailureObserver configures recovered tool failure reporting.
Closing the host’s Scope stops admission, releases ownership, and closes SQLite.
After abrupt process death, including SIGKILL, the operating system releases the automatic
host’s connection lock. Its replacement acquires that lock, checks storage compatibility, and
atomically fences and retires retained claims before ordinary recovery. It does not wait for
the old ownership lease. Startup, history validation, and provider work still take time.
Lease, renewal, and wake-scan defaults remain 30 seconds, 10 seconds, and 1 second.
Unconfirmed external tool outcomes require reconciliation or authorized resolution before replay.
Startup recovery must succeed for every Thread before admission or workers open. A retained-history
fault or recovery timeout fails host construction with RecoveryBlocked; accepted work stays pending.
Inspect host.startupRecovery, host.explain, host.verify, and host.scanObligations
for recovery status while the host is running. The managed host exposes ThreadReader for
canonical reads and MessageDeliveryStore for independent delivery obligations. Its SQLite
client, canonical writes, and submission ownership ports are private. Each active Attempt
owns a scoped storage session, so administrative writes and execution share one authority.
Application SQL clients and instrumentation stay outside that private storage context.
Use a manual runtime assembly when composing additional SQL adapters. Stop a managed host before
using the standalone admin:durable CLI or another database reader. See operations
for approvals, schedules, and backups.