Skip to content

Platforms

Cloudflare

@yielded/agent-platform-cloudflare stores each thread and its pending work in a SQLite-backed Durable Object. RPC calls and alarms drive execution and recovery. See Cloudflare storage for database ownership and adapter composition.

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

Keep framework packages at one release and add your model provider.

The Node-safe @yielded/agent-platform-cloudflare/cloudflare-ai-gateway subpath configures upstream Effect clients in Workers, Durable Objects, Node, or Bun. Gateway.provide supplies the client directly in a Layer pipeline; model selection, tools, response decoding, streaming, and typed provider errors stay with upstream Effect AI. Use the configured client for primary agents, subagents, compaction models, WebSearch, or embeddings supported by its provider.

Two endpoint families have different credentials and model names:

Helper Authentication Model names
Gateway.rest({ accountId, gatewayId, apiToken, protocol }) Cloudflare API token with Workers AI Read permission Provider-qualified, such as openai/gpt-6-luna
Gateway.provider({ accountId, gatewayId, provider, apiToken }) cf-aig-authorization; optionally a separate provider key Native provider name, such as gpt-6-luna

For provider-native routing with stored keys or Unified Billing, pass the upstream client’s layer factory and your resolved gateway configuration:

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

Creates a Redacted wrapper for a sensitive value.

When to use

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

Details

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

Example (Creating a redacted value)

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

@category ― constructors

@since ― 3.3.0

make
("your-cloudflare-token"),
};
const
const ModelLive: Layer.Layer<LanguageModel | ProviderName | ModelName, never, never>
ModelLive
=
import OpenAiLanguageModel
OpenAiLanguageModel
.
const model: (model: (string & {}) | OpenAiLanguageModel.Model, config?: Omit<{
readonly metadata?: {
readonly [x: string]: string;
} | undefined;
readonly top_logprobs?: number | undefined;
readonly temperature?: number | undefined;
readonly top_p?: number | undefined;
readonly user?: string | undefined;
readonly prompt_cache_key?: string | undefined;
readonly prompt_cache_options?: {
readonly mode?: "explicit" | "implicit" | undefined;
readonly ttl?: "30m" | undefined;
} | undefined;
... 17 more ...;
readonly useItemReferences?: boolean | undefined;
}, "model">) => Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>

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

When to use

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

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

@see ― make for constructing the language model service effectfully

@stability ― unstable

@category ― constructors

@since ― 4.0.0

model
("gpt-6-luna").
Pipeable.pipe<Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>, Layer.Layer<LanguageModel | ProviderName | ModelName, never, HttpClient>, Layer.Layer<LanguageModel | ProviderName | ModelName, never, never>>(this: Model<...>, ab: (_: Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>) => Layer.Layer<LanguageModel | ProviderName | ModelName, never, HttpClient>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Gateway
Gateway
.
const provide: <OpenAiClient.OpenAiClient, never, HttpClient>(clientLayer: (options: Gateway.ClientOptions) => Layer.Layer<OpenAiClient.OpenAiClient, never, HttpClient>, options: Gateway.RouteOptions) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, HttpClient | Exclude<RIn2, OpenAiClient.OpenAiClient>>

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

@example

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

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

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

When to use

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

@see ― make for constructing the client service effectfully

@see ― layerConfig for loading client settings from Config

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
, {
...
const gateway: {
accountId: string;
gatewayId: string;
apiToken: Redacted.Redacted<string>;
}
gateway
,
ProviderOptions.provider: string

Cloudflare's provider path, such as openai, anthropic, google-ai-studio, or perplexity-ai.

provider
: "openai",
}),
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

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

When to use

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

Details

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

Gotchas

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

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

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

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
),
);

Supply real credentials from your host configuration or secret store. For account REST routing, replace provider with protocol: "responses" and use a provider-qualified model name. Gateway.provide preserves client initialization errors and remaining dependencies, including HttpClient. The upstream Layers retain their normal resource lifetimes.

For custom client options, pass a factory such as Gateway.provide((options) => OpenAiClient.layer({ ...options, apiKey }), route). The lower-level Gateway.provider and Gateway.rest helpers return apiUrl and transformClient for direct client construction or raw HTTP requests.

Omit the provider apiKey when the gateway supplies a stored key. Use layer here: provider layerConfig can load a provider API key from the environment when its apiKey option is omitted. An unauthenticated provider gateway can omit apiToken when sending its own provider key.

rest selects protocol: "responses" for OpenAiClient, "messages" for AnthropicClient, or "chat-completions" for a compatible client. It sends cf-aig-gateway-id and sets the correct base path, including Anthropic’s separately appended /v1. This uses Cloudflare’s account REST API. The native provider helper also accepts other provider path names, including google-ai-studio, google-vertex-ai, perplexity-ai, and parallel. Supply the provider’s matching upstream client or Effect HttpClient request format; additional provider path components belong after apiUrl. Routing does not translate request bodies or make unsupported models compatible.

Cloudflare’s web search support varies by provider. This repository exercises OpenAI and Anthropic hosted search through their pinned Effect clients. xAI uses Responses search; Alibaba requires its own chat request flag; Gemini requires native grounding; Perplexity and Parallel use provider-native APIs. Those can use the same Gateway transport but are not interchangeable native WebSearch backends here.

Client configuration validates account, gateway, and provider path segments. Requests must stay inside that endpoint; Fetch redirects are disabled to prevent credential forwarding. Custom HTTP transports must also avoid following redirects internally. Gateway authorization is redacted in HTTP telemetry and returned request/error headers, and provider authentication is preserved. Gateway logging and caching follow gateway settings or headers supplied by the host; no automatic retries, fallback models, or cache overrides are added.

Compose agent registrations and application services as a layer, then pass it to ThreadObject.make. This example expects OPENAI_API_KEY and a THREADS Durable Object namespace in the generated Cloudflare.Env.

import {
import Agent
Agent
} from "@yielded/agent";
import {
import ThreadObject
ThreadObject
} from "@yielded/agent-platform-cloudflare";
import {
class DefinitionDigestInput

Schema-owned replay inputs whose individual definitions are incorporated into digests.

DefinitionDigestInput
} from "@yielded/agent/records";
import {
import OpenAiClient
OpenAiClient
,
import OpenAiLanguageModel
OpenAiLanguageModel
} from "@effect/ai-openai";
import {
import Config
Config
,
import Layer
Layer
,
import Schema
Schema
} from "effect";
import {
import Toolkit
Toolkit
} from "effect/ai";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const TravelPlanner: Agent.Definition<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "travel-planner";
}
TravelPlanner
=
import Agent
Agent
.
function make<"travel-planner", Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined>(id: "travel-planner", options: Agent.DefinitionOptions<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, ... 4 more ..., undefined> & {
...;
}): Agent.Definition<...> & {
...;
} (+3 overloads)

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

make
("travel-planner", {
DefinitionOptions<Struct<{ readonly destination: String; readonly days: Number; }>, Struct<{ readonly itinerary: $Array<String>; }>, "Create a practical travel itinerary.", Toolkit<...>, undefined, undefined, undefined>.input: Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>
input
:
import Schema
Schema
.
function Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>(fields: {
readonly destination: Schema.String;
readonly days: Schema.Number;
}): Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>

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
({
destination: Schema.String
destination
:
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
,
days: Schema.Number
days
:
import Schema
Schema
.
const Number: Schema.Number

Type-level representation of

Number

.

Schema for number values, including NaN, Infinity, and -Infinity.

Details

Default JSON serializer:

  • Finite numbers are serialized as numbers.
  • Non-finite values are serialized as strings ("NaN", "Infinity", "-Infinity").

@category ― models

@since ― 4.0.0

@see ― Finite for a schema that excludes non-finite values.

@category ― schemas

@since ― 4.0.0

Number
}),
DefinitionOptions<Struct<{ readonly destination: String; readonly days: Number; }>, Struct<{ readonly itinerary: $Array<String>; }>, "Create a practical travel itinerary.", Toolkit<...>, undefined, undefined, undefined>.output: Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>
output
:
import Schema
Schema
.
function Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>(fields: {
readonly itinerary: Schema.$Array<Schema.String>;
}): Schema.Struct<{
readonly itinerary: 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.

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
({
itinerary: Schema.$Array<Schema.String>
itinerary
:
import Schema
Schema
.
Array<Schema.String>(self: Schema.String): Schema.$Array<Schema.String>
export Array

Defines a ReadonlyArray schema for a given element schema.

Example (Defining an array of strings)

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

@category ― constructors

@since ― 4.0.0

Array
(
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
) }),
DefinitionOptions<Struct<{ readonly destination: String; readonly days: Number; }>, Struct<{ readonly itinerary: $Array<String>; }>, ... 4 more ..., undefined>.instructions: "Create a practical travel itinerary."
instructions
: "Create a practical travel itinerary.",
DefinitionOptions<Struct<{ readonly destination: String; readonly days: Number; }>, Struct<{ readonly itinerary: $Array<String>; }>, "Create a practical travel itinerary.", Toolkit<...>, undefined, undefined, undefined>.toolkit: Toolkit.Toolkit<{}>
toolkit
:
import Toolkit
Toolkit
.
const make: <[]>() => Toolkit.Toolkit<{}>

Creates a new toolkit from the specified tools.

Details

This is the primary constructor for creating toolkits. It accepts multiple tools and organizes them into a toolkit that can be provided to AI language models.

Example (Creating a toolkit)

import { Effect, Schema } from "effect"
import { Tool, Toolkit } from "effect/ai"
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Get the current timestamp",
success: Schema.Number
})
const GetWeather = Tool.make("get_weather", {
description: "Get weather information",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const toolkit = Toolkit.make(GetCurrentTime, GetWeather)
const ready = toolkit.pipe(Effect.provide(toolkit.toLayer({
GetCurrentTime: () => Effect.succeed(0),
get_weather: () => Effect.succeed({ temperature: 20, condition: "clear" })
})))
Object.keys((await Effect.runPromise(ready)).tools) // => ["GetCurrentTime", "get_weather"]

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
(),
DefinitionOptions<Struct<{ readonly destination: String; readonly days: Number; }>, Struct<{ readonly itinerary: $Array<String>; }>, "Create a practical travel itinerary.", Toolkit<...>, undefined, undefined, undefined>.policy?: Partial<Readonly<Omit<{
readonly runStatus: "appended" | "off";
readonly maxTurns: number;
readonly maxToolCalls: number;
readonly maxDuration: Duration;
readonly toolConcurrency: number;
readonly repeatedFailureLimit: number;
readonly onExhaustion: "final-answer" | "fail";
readonly completionReserveTokens: number;
readonly toolResultBounds: ToolResultBounds;
readonly compaction: CompactionPolicy;
readonly tokenBudget?: number | undefined;
readonly costBudgetMicrousd?: number | undefined;
readonly contextTokenLimit?: number | undefined;
readonly restartOnJoinedInput?: boolean | undefined;
readonly modelRetries?: number | undefined;
}, "runStatus" | ... 5 more ... | "compaction"> & {
...;
}>> | undefined
policy
: {
maxTurns?: number | undefined
maxTurns
: 3,
maxToolCalls?: number | undefined
maxToolCalls
: 1,
maxDuration?: Input | undefined

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

maxDuration
: "30 seconds",
},
});
const
const modelName: "gpt-6-luna"
modelName
= "gpt-6-luna";
export const
const travelDefinitions: DefinitionDigestInput
travelDefinitions
=
class DefinitionDigestInput

Schema-owned replay inputs whose individual definitions are incorporated into digests.

DefinitionDigestInput
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<DefinitionDigestInput, { readonly agent: Json; readonly model: Json; readonly tools: Json; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly agent: Schema.Json;
readonly model: Schema.Json;
readonly tools: Schema.Json;
}, options?: Schema.MakeOptions): DefinitionDigestInput

Constructs a value from the make input representation synchronously.

When to use

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

Details

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

Gotchas

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

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

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

make
({
agent: Schema.Json
agent
: {
id: Brand<"@effect-agent/core/AgentId"> & "travel-planner"
id
:
const TravelPlanner: Agent.Definition<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "travel-planner";
}
TravelPlanner
.
id: Brand<"@effect-agent/core/AgentId"> & "travel-planner"

Stable agent identity; changing it creates a distinct definition identity.

id
,
revision: number
revision
: 1 },
model: Schema.Json
model
: {
provider: string
provider
: "openai",
name: string
name
:
const modelName: "gpt-6-luna"
modelName
},
tools: Schema.Json
tools
: [],
});
const
const OpenAiLive: Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, never>
OpenAiLive
=
import OpenAiClient
OpenAiClient
.
const layerConfig: (options?: {
readonly apiKey?: Config.Config<Redacted<string> | undefined> | undefined;
readonly apiUrl?: Config.Config<string> | undefined;
readonly organizationId?: Config.Config<Redacted<string> | undefined> | undefined;
readonly projectId?: Config.Config<Redacted<string> | undefined> | undefined;
readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined;
}) => Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, HttpClient>

Creates a layer for the OpenAI client from provided Config values.

When to use

Use when you need client settings for OpenAI-compatible APIs to be read from Effect Config values while providing OpenAiClient as a Layer.

Details

Only config values supplied in options are loaded. Omitted fields are passed to make as undefined, and transformClient is forwarded as a plain option.

@see ― make for constructing the client service effectfully

@see ― layer for providing the client from already-resolved options

@stability ― unstable

@category ― layers

@since ― 4.0.0

layerConfig
({
apiKey?: Config.Config<Redacted<string> | undefined> | undefined

The config value to load for the API key.

apiKey
:
import Config
Config
.
function Redacted(name?: string): Config.Config<import("effect/Redacted").Redacted<string>>

Creates a config for a redacted string value. The parsed result is wrapped in a Redacted container that hides the value from logs and toString.

When to use

Use to read secret string settings that should not be exposed in logs or string output.

Details

Shortcut for Config.schema(Schema.Redacted(Schema.String), name).

Example (Reading a secret)

import { Config, ConfigProvider, Effect } from "effect"
const program = Config.Redacted("API_KEY").pipe(Effect.map(String))
const provider = ConfigProvider.fromEnv({
env: {
API_KEY: "sk-1234567890abcdef"
}
})
Effect.runSync(
program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
) // => "<redacted>"

@see ― String for non-secret string settings

@category ― constructors

@since ― 2.0.0

Redacted
("OPENAI_API_KEY"),
}).
Pipeable.pipe<Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, HttpClient>, Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, never>>(this: Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, HttpClient>, ab: (_: Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, HttpClient>) => Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, never>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

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

When to use

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

Details

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

Gotchas

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

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

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

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));
const
const RuntimeLive: Layer.Layer<DurableAgentRuntime | ThreadStore | SubmissionLedger | MessageDeliveryStore | WakeScheduler | ThreadReader | RunStorage | SettlementPublisher | ThreadPublication | ThreadProjectionMaintenance | ThreadMutationGate | SqlClient | DoStorageConfig | SqliteClient | DurableAlarmService | ProgressWaitRegistry | ThreadObject.ThreadObjectPorts | ThreadMaintenance, Config.ConfigError | ... 3 more ... | DoStorageInitializationError, Crypto | ... 9 more ... | CloudflareDurableRuntimeConfig>
RuntimeLive
=
import ThreadObject
ThreadObject
.
layer<readonly [{
readonly agent: Agent.Definition<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "travel-planner";
};
readonly model: Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>;
readonly definitions: DefinitionDigestInput;
}], never, never>(registrations: readonly [...], options?: ThreadObject.PublicationOptions<...> | undefined): Layer.Layer<...> (+1 overload)
export layer

Preserve additional index services only when a projection Layer is actually supplied.

layer
([
{
agent: Agent.Definition<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "travel-planner";
}
agent
:
const TravelPlanner: Agent.Definition<Schema.Struct<{
readonly destination: Schema.String;
readonly days: Schema.Number;
}>, Schema.Struct<{
readonly itinerary: Schema.$Array<Schema.String>;
}>, "Create a practical travel itinerary.", Toolkit.Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "travel-planner";
}
TravelPlanner
,
model: Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>
model
:
import OpenAiLanguageModel
OpenAiLanguageModel
.
const model: (model: (string & {}) | OpenAiLanguageModel.Model, config?: Omit<{
readonly metadata?: {
readonly [x: string]: string;
} | undefined;
readonly top_logprobs?: number | undefined;
readonly temperature?: number | undefined;
readonly top_p?: number | undefined;
readonly user?: string | undefined;
readonly prompt_cache_key?: string | undefined;
readonly prompt_cache_options?: {
readonly mode?: "explicit" | "implicit" | undefined;
readonly ttl?: "30m" | undefined;
} | undefined;
... 17 more ...;
readonly useItemReferences?: boolean | undefined;
}, "model">) => Model<"openai", LanguageModel, OpenAiClient.OpenAiClient>

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

When to use

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

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

@see ― make for constructing the language model service effectfully

@stability ― unstable

@category ― constructors

@since ― 4.0.0

model
(
const modelName: "gpt-6-luna"
modelName
),
definitions: DefinitionDigestInput
definitions
:
const travelDefinitions: DefinitionDigestInput
travelDefinitions
,
},
]).
Pipeable.pipe<Layer.Layer<DurableAgentRuntime | ThreadStore | SubmissionLedger | MessageDeliveryStore | WakeScheduler | ThreadReader | RunStorage | SettlementPublisher | ThreadPublication | ThreadProjectionMaintenance | ThreadMutationGate | SqlClient | DoStorageConfig | SqliteClient | DurableAlarmService | ProgressWaitRegistry | ThreadObject.ThreadObjectPorts | ThreadMaintenance, DigestError | ... 2 more ... | DoStorageInitializationError, OpenAiClient.OpenAiClient | ... 10 more ... | CloudflareDurableRuntimeConfig>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, Config.ConfigError, OpenAiClient.OpenAiClient>(that: Layer.Layer<OpenAiClient.OpenAiClient, Config.ConfigError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, Config.ConfigError | E2, Exclude<RIn2, OpenAiClient.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.OpenAiClient, Config.ConfigError, never>
OpenAiLive
));
export class
class TravelThread
TravelThread
extends
import ThreadObject
ThreadObject
.
const make: <DoStorageConfig | SqliteClient, Config.ConfigError | DigestError | MessageDeliveryError | DurableAlarmError | DoStorageInitializationError, never, never>(applicationLayer: Layer.Layer<DoStorageConfig | SqliteClient | ThreadObject.Services, Config.ConfigError | DigestError | MessageDeliveryError | DurableAlarmError | DoStorageInitializationError, DurableObjectContext | ... 3 more ... | WorkerEnvironment>, options: ThreadObject.Options<...>) => ThreadObject.Class<...>

Export a composed application Layer as a native Durable Object class. Bootstrap services are provided to the whole graph before it acquires, so application Layers can yield effect-cf's WorkerEnvironment and DurableObjectState, derived identity, and Crypto. Effect Config reads scalar Worker vars and secrets through effect-cf's environment provider; WorkerEnvironment exposes resource bindings without a separate config Layer. Application dependencies remain visible until Layer.provide satisfies them. effect-cf owns the cached ManagedRuntime, native RPC methods, event scopes, and telemetry flushing. Initialization is local and bounded inside the constructor gate. Cloudflare eviction does not guarantee finalizers; put resources requiring timely release in scoped operations or eventLayer.

make
(
const RuntimeLive: Layer.Layer<DurableAgentRuntime | ThreadStore | SubmissionLedger | MessageDeliveryStore | WakeScheduler | ThreadReader | RunStorage | SettlementPublisher | ThreadPublication | ThreadProjectionMaintenance | ThreadMutationGate | SqlClient | DoStorageConfig | SqliteClient | DurableAlarmService | ProgressWaitRegistry | ThreadObject.ThreadObjectPorts | ThreadMaintenance, Config.ConfigError | ... 3 more ... | DoStorageInitializationError, Crypto | ... 9 more ... | CloudflareDurableRuntimeConfig>
RuntimeLive
, {
Options<ApplicationServices = never, EventServices = never, EventLayerError = never>.namespaceBinding: string

Name of the Worker env binding carrying THIS class's DurableObjectNamespace — the Object's route back to sibling Thread Objects for the WP2 cross-Object port calls and remote wakes (DEPLOY-010: the binding enters through a Layer, never ambiently).

namespaceBinding
: "THREADS",
CloudflareDurableRuntimeOptions.deploymentId: string
deploymentId
: "travel-planner",
CloudflareDurableRuntimeOptions.producerPrefix: string

Head of the minted producer identity {producerPrefix}:{threadId}.

producerPrefix
: "travel-worker",
}) {}

Each registration supplies an agent definition, its model Layer, and explicit agent, model, and tool versions. The submitter passes digestDefinitions(travelDefinitions) through DurableSubmitOptions.definitions. Bump the agent revision when instructions, schemas, or policy change. Version tool implementations and model configuration when they change. Register one current binding per stable agentId by default. Hosts with intentionally shared identities can provide CurrentBindingSelection from @yielded/agent/agent-registration when constructing the runtime. Its select(submission) returns an exact registered Definition using canonical input and authoritative host state; undefined retains unique-identity resolution. Both execution and recovery use this selection. Set a stable key and change it when routing changes. Selection does not bypass input decoding, operation replay contracts, or authorization. Queued work keeps its original identity, digests and payload without requiring historical executable versions. Worker declarations and peer-messaging endpoints still require unique Agent identities.

Application layers can use WorkerEnvironment, DurableObjectState, ThreadObjectIdentity, and Crypto. Scalar Worker vars and secrets are available through Effect Config: ThreadObject.make installs effect-cf’s environment config provider. Read secrets with Config.Redacted, and use WorkerEnvironment for resource bindings such as R2 or Durable Object namespaces. Use Layer.unwrap when configuration selects registrations or services. The application is acquired once per Object instance and rebuilt after eviction. Keep initialization local and bounded. Eviction does not guarantee finalizers; acquire resources needing timely cleanup inside scoped operations or options.eventLayer. Each event runs a bounded recovery pass; no worker loop is needed.

Register the exported class as a SQLite Durable Object under THREADS. ThreadObject.layer([]) registers no agents and refuses every agent identity.

{
"name": "travel-planner",
"main": "src/worker.ts",
"compatibility_date": "2026-08-31",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [{ "name": "THREADS", "class_name": "TravelThread" }],
},
"exports": {
"TravelThread": { "type": "durable-object", "storage": "sqlite" },
},
}

Match THREADS to namespaceBinding and TravelThread to the exported class. Enable nodejs_compat for effect-cf’s async context support and native SHA-256 hashing. See Cloudflare’s class configuration guide for Workers using the older migrations array.

import {
class CloudflareThreadClient

Worker-side client over the Thread Object namespace (DEPLOY-010).

CloudflareThreadClient
} from "@yielded/agent-platform-cloudflare/cloudflare-thread-client";
import { type
(alias) interface ThreadObjectRpc
import ThreadObjectRpc

The RPC surface one Thread Durable Object exposes to Workers and to sibling Thread Objects. ThreadObject.make implements it; the Worker-side client and the cross-Object transport call it through DurableObjectNamespace stubs. Every encoded value is a Schema-encoded envelope (client.ts wire schemas for host entry points, @yielded/agent-storage-cloudflare port envelopes for portCall), so the RPC boundary carries only structured-cloneable JSON. The optional trailing trace context is transient native RPC metadata, stripped by an opted-in effect-cf receiver before decoding the host or port envelope. It never enters durable state.

ThreadObjectRpc
} from "@yielded/agent-platform-cloudflare/cloudflare-bindings";
export const
const threadClientLayer: (env: {
THREADS: DurableObjectNamespace<ThreadObjectRpc>;
}) => Layer<CloudflareThreadClient, never, never>
threadClientLayer
= (
env: {
THREADS: DurableObjectNamespace<ThreadObjectRpc>;
}
env
: {
type THREADS: DurableObjectNamespace<ThreadObjectRpc>
THREADS
:
class DurableObjectNamespace<T extends Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace
<
(alias) interface ThreadObjectRpc
import ThreadObjectRpc

The RPC surface one Thread Durable Object exposes to Workers and to sibling Thread Objects. ThreadObject.make implements it; the Worker-side client and the cross-Object transport call it through DurableObjectNamespace stubs. Every encoded value is a Schema-encoded envelope (client.ts wire schemas for host entry points, @yielded/agent-storage-cloudflare port envelopes for portCall), so the RPC boundary carries only structured-cloneable JSON. The optional trailing trace context is transient native RPC metadata, stripped by an opted-in effect-cf receiver before decoding the host or port envelope. It never enters durable state.

ThreadObjectRpc
> }) =>
class CloudflareThreadClient

Worker-side client over the Thread Object namespace (DEPLOY-010).

CloudflareThreadClient
.
CloudflareThreadClient.layerFromBinding(options: {
readonly namespace: DurableObjectNamespace<ThreadObjectRpc>;
readonly rpcTracing?: string;
}): Layer<CloudflareThreadClient>

Assemble the client from a resolved namespace and the platform Crypto implementation. rpcTracing is the binding name and remains opt-in. Caller authentication, principals, idempotency keys, and definition digests are still explicit submission inputs.

layerFromBinding
({
namespace: DurableObjectNamespace<ThreadObjectRpc>
namespace
:
env: {
THREADS: DurableObjectNamespace<ThreadObjectRpc>;
}
env
.
type THREADS: DurableObjectNamespace<ThreadObjectRpc>
THREADS
});

This constructor supplies the namespace and platform Crypto. Pass rpcTracing: "THREADS" only when the receiver also enables native RPC tracing. Keep CloudflareThreadClient.layer for custom Crypto or namespace composition, and threadNamespaceLayer for untyped environment lookup.

In an authenticated handler, call client.submit(agent, input, options) with the thread ID, principal, idempotency key, and definition digests. Return its receipt after admission.

Use client.awaitSettlement(receipt) for completion metadata. When you also need the output, use client.awaitSettlementRecord(receipt) to wait for finalization and retrieve that receipt’s canonical terminal record in one call. It requires both settlement and observation permission. For an ordinary completed record, decode record.result with your Agent’s output Schema; joined completion may have no independent result. Failed and aborted outcomes remain records.

For updates, call readPage, then awaitProgress, then read after the last received sequence. Scope progress waits so interruption cancels them remotely. Cancellation is best effort and waits at most one second for the remote reply, so a lost reply does not prevent local shutdown. The Object retains bounded cancellation hints for late retries. Expose these Effects through your application’s HTTP or RPC API.

Provide custom services to ThreadObject.layer(registrations) before passing the resulting layer to ThreadObject.make. For example, add Layer.provide(RunContextLive) to RuntimeLive above to install prompt preparation or compaction. Provide a tool authorization layer in the same place when needed.

The host supplies passthrough preparation and RunToolAuthorization.allowAll by default. Your application layers override those defaults. Preparation can supply a prompt hook, a compactor, or both; otherwise the runtime uses an available ContextCompactor or its default. Close custom layers’ dependencies with application services or the host services listed above. They are captured when the Object acquires the runtime, not on each worker call.

Use options.eventLayer for per-event observability and resources. Use options.toolFailureObserver for recovered tool failures.

Provide CloudflareTracer.layer from effect-cf as ThreadObject.make’s eventLayer, and enable observability.traces.enabled in the deployed Worker settings. The tracer must be acquired per invocation so alarms and RPC calls use their own Cloudflare tracing context.

The agent and model span attributes follow Cloudflare’s custom harness conventions. Use the Cloudflare Agents tab to group activity by agent and conversation, or filter Workers Observability with gen_ai.operation.name = "invoke_agent". The outer platform trace can still be named alarm: durable execution wakes independently of the submitting HTTP request. Named agent, model, and tool spans appear inside it. Separate alarm invocations do not become one trace solely because they share a Run ID.

Storage append, materialization, and ownership-release spans record expected contention or cleanup refusals in storage.outcome and end successfully. Their typed port errors still reach callers for recovery. Real failures retain error.type and, when tagged, error.cause.type; these attributes contain error tags only, without messages or payloads.

Use ThreadObject.layerInHost(application) when an existing SQLite Durable Object owns related logical Threads. The application Layer receives the existing SqlClient, native stores, ThreadMutationGate, wake scheduler, and PreparedInputAdmission. Build its Bindings from those services and return DurableAgentRuntime plus the application’s services. The platform then constructs one maintenance coordinator from that runtime. Do not construct a second runtime or require ThreadMaintenance while building the application.

Supply ThreadObject.layerHostConfig(options, ownsThread), DurableObjectContext, and ThreadObjectNamespace. The namespace’s get(threadId) returns a bound logical endpoint; its methods call the application’s RPC with the selected Thread ID and native encoded payload. The receiver dispatches with ThreadObject.handleRpc(threadId, operation, encoded). Direct local admission uses ThreadObject.submit(threadId, decodedRequest) and the same validation and prearm. The shared-owner example composes an existing SQL client, application services and an optional projection without external requirements.

Placement must be deterministic and stable across reconstruction. It grants no access: authenticate callers and validate membership at the application’s boundary. Encoded controls additionally check the addressed Thread against the local receipt or submission before routed runtime access. No logical ThreadObjectIdentity is installed globally; addressed dispatch binds it per invocation. The producer identity belongs to the physical Object. Native Thread and receipt identities remain unchanged. Moving existing Threads between physical Objects requires a host-owned fenced transfer; changing the resolver alone does not move their durable records.

Own one SqlClient, ThreadMutationGate and alarm slot per physical Object. Native migrations use their own migration history, leaving the application’s migration rows intact. Call ThreadMaintenance.ensureAlarm in the local constructor gate and one bounded ThreadMaintenance.pass from alarm(). The event runs at most two independent Thread Attempts concurrently, with one active FIFO head per Thread and a durable cursor rotating between Threads. A free slot admits newly ready Threads while another Attempt is busy. Remaining work retains the alarm; generation acknowledgement waits for admitted Attempts to finish.

An unresolved tool effect stays parked as an Unknown Outcome while later input in the same Thread can run. The unknown record and settlement obligation remain intact across eviction, and the effect is not replayed. Approval waits and joined input remain ordering barriers; live ownership still prevents another claim. explainThread exposes parked operations for authorized resolution or abort.

Failed and no-progress passes preserve the dirty generation and use jittered exponential backoff up to alarmBackoffCap (5 seconds by default). Missing or duplicate agent bindings park the original submission and report the refusal once. Interruption before the wait commits can repeat the report; reporting is not exactly once. The wait survives eviction and does not schedule an alarm. Unrelated host work and aborts remain serviceable. On the next invocation with changed registered identities, definition digests, or selection key, constructor maintenance clears binding waits and schedules one native pass. A dormant Object still needs an invocation after deployment; deployment alone does not invoke it. The original receipt, admission evidence and unresolved tool or child obligations remain intact. An obsolete pending tool operation does not wait for historical code: it receives an unavailable result when no mutation was dispatched, or stays unknown when an external effect may have occurred.

The scheduler and admission limits discover work from the ledger’s control-only worklist. The scheduler then hydrates and recovers the selected Thread before its claim. Accepted abort intents identify cleanup even for input that was never claimed; their reads share the recovery timeout and fault boundary, and skip Threads pending recovery or waiting for retry. Old recovery runs sequentially in the event Scope with a 30-second bound per Thread; the same alarm and wake loop can dispatch fresh Threads and publish their replies while old history is stalled. Unfinished cleanup retains its fences and settlement obligations across eviction.

Unreadable retained payloads, history or a failed child recovery block only their Thread. Maintenance retains the fault outside canonical history and retries after 5, 10, 20, 40, then 60 seconds. New admissions retain their receipts and do not bypass that deadline; other Threads remain eligible. Successful recovery clears the fault without resolving uncertain external effects.

Hosts consume per-Submission fault transitions through ThreadRecoveryEvents:

import { ThreadRecoveryEvents } from "@yielded/agent-platform-cloudflare/alarm";
import { Layer } from "effect";
const recoveryEvents = Layer.succeed(ThreadRecoveryEvents, {
publish: (event) =>
failures.apply({
threadId: event.threadId,
submissionId: event.submissionId,
sequence: event.sequence,
failed: event.transition !== "cleared",
failure: event.failure,
}),
});
// Supply { recoveryEvents } to ThreadObject.layer or ThreadObject.layerInHost.

created marks each affected Submission, including new admissions during backoff. changed means the failure classification or content-free diagnostic changed. cleared names every previously affected Submission, even if recovery has since settled it. Events carry Thread and Submission IDs, first-failure and transition times, and the bounded RecoveryFailure details. Retry counters and deadlines are private: bookkeeping emits no event or wake. Healthy execution performs no host recovery-status checks.

Transitions commit atomically with fault state, then an independent maintenance lane delivers them in order. The lane has a 30-second allowance per wave; a failed or interrupted delivery remains pending across eviction and does not gate native execution. Return success only after durably applying the event or retaining it in an application outbox. Delivery is at least once: deduplicate by physical Object and sequence, including when the host commit succeeds but its acknowledgement is lost. Capture application services when constructing the handler Layer.

These are private host events; authorize recipients before exposing a user-facing failure flag. A fault is not a Settlement, and a clear does not prove completion. Source-owned accepted-message notices must not wait for native settlement: a pre-claim fault can precede any reply obligation. Install the handler from the first maintenance pass; an omitted handler discards transitions. Existing retained faults announce their pending Submissions on the next native scan without resetting history or retry deadlines. This replaces ThreadMaintenance.recoveryStatus; hosts must remove their status polling and consume these transitions instead.

Application outboxes enroll independent lanes in one durable due queue:

import {
ThreadHostMaintenance,
ThreadMutationGate,
} from "@yielded/agent-platform-cloudflare/alarm";
import { Context, Effect } from "effect";
const maintenance = Context.make(ThreadHostMaintenance, {
lanes: [{ id: "replies", dispatchTimeoutMillis: 30_000, run: replies.deliverWave }],
});
const retainReply = Effect.gen(function* () {
const gate = yield* ThreadMutationGate;
yield* gate.withMutation(
gate.withTransaction(
Effect.gen(function* () {
const added = yield* replies.retain;
if (added) yield* gate.recordProgress(["replies"]);
}),
),
{ invalidatesRecovery: false, lanes: ["replies"] },
);
});

Each lane has a stable ID, unique within the physical Object. Reserve @yielded/agent: IDs for framework lanes. run returns Effect<Option<number>, DurableAlarmError, Scope>: the next deadline in epoch milliseconds, or None when idle. Calculate it as part of the wave that commits the receipts and retries. The scheduler never calls a separate host deadline reader. Compose hosts by concatenating their lanes.

Every lane has a bounded no-progress budget for waves that return another deadline. Returning None drains the lane and resets its budget, so later ordinary enrollment can wake it again. Pending retries have a one-second floor and exponential backoff; after eight unchanged waves, the lane parks with an hourly recovery deadline and reports once through the installed Effect error reporter. An outage cannot silently strand its retained work. A committed source change can resume it immediately. Deadline renewal, claims and failed mutations cannot reset that budget. Local sources call recordProgress(lanes) only after an actual change, inside their source SQL transaction. Replayed facts must skip it. Remote sources retain their monotonically increasing commit cursor and deliver schedule(id, dueAt, cursor); repeated or older cursors cannot renew the budget. Neither withMutation nor a wake hint records progress. Claims, deadlines, retry counters and clocks are not source facts. Retained source work remains owed when scheduling parks.

When native input or settlement creates application work, select its lanes at the native owner:

const RuntimeLive = ThreadObject.layer(registrations, {
hostLanesForMutation: (mutation) => {
switch (mutation._tag) {
case "Admission":
return ["guidance"];
case "Settlement":
return ["replies", "memory"];
case "WorkerStop":
return ["replies"];
}
},
});

This pure, bounded selector receives the native request. The owner prearms the selected IDs and guards them through the source mutation, including replay and recovery finalization without a new canonical append. Delivery does not depend on lifecycle publication reaching an external database.

Lanes run concurrently by default. Set phase: "after-native" on a lane such as memory delivery to give it one due wave after native Attempts and their scoped cleanup finish, including on native failure. Selection uses the final queue revisions; idle lanes do not run. The phase stays inside the same pass permit and fourteen-minute event deadline. Its failures retain independent retries and are reported together with any native failure.

Newly enrolled concurrent work yields an active post-native phase. Its wave scopes close before the alarm retires, allowing the next alarm to admit input promptly. Unfinished waves retain their due revision and exact delivery receipts; completed waves stay acknowledged.

A registered host lane starts idle. Producers name only the lanes receiving work, and the gate prearms those entries before the mutation body. A failed mutation can leave a discovery wave; validation and authorization should precede enrollment when they establish that no work is needed. gate.schedule(id, dueAt) explicitly enrolls an existing obligation or an earlier deadline. Use it inside a local source transaction. For a remote source, retain a scheduling notice atomically with the work and retry its delivery to schedule until acknowledged, using the source’s existing retry identity. Prearming alone cannot fence a remote commit that finishes after Object eviction. Keep the same gate instance when rebuilding runtime services. Receipt bookkeeping that creates no new work uses invalidatesRecovery: false with no lanes. Native admissions and controls keep the default invalidatesRecovery: true.

The queue retains each lane’s own revision and deadline. Repeated marks in one source transaction merge the earliest deadline and highest progress cursor, then batch changed lanes into revision-fenced writes. A pass shares its queue view, releasing large views when it exits. The gate owns the source transaction and its flush; prearming and attempt charging commit before fallible work. Built-in stores use this boundary automatically. Custom sources can opt into coalescing with ThreadMutationGate.withTransaction at their outermost SQL transaction. Within that boundary, nested transactions that schedule work use it too. A finishing wave cannot erase a newer producer enrollment, and a lane waits for an in-flight source mutation body to finish. Completions and producer notifications drive the active event; there is no wake-scan timer. The one alarm retains the earliest queued deadline after all admitted resources close. Constructor repair reads only local scheduling state, never application deadline tables or execution history. Future deadlines remain armed when an otherwise quiet event retires.

Declare dispatchTimeoutMillis as an integer from 1 to 300000 milliseconds, covering selection, delivery, retry/receipt commits and scoped cleanup. A wave starts only if its allowance fits the event. Failed lanes retain independent backoff and do not retry in the same event; healthy lanes and native work keep their own opportunities. A malformed deadline, missing handler or duplicate ID fails without discarding the obligation. Persist exact envelopes and deduplicate delivery by domain identity: interruption cannot roll back remote effects, and delivery remains at least once. Hooks must not write the raw alarm slot.

Native message delivery keeps the driver’s actual Claim deadline, including timeout/retry commits. ThreadMessageDelivery.prepare returns { timeoutMillis, run }, with run returning the next Option<number> deadline. Disposable projection backfill keeps one wave per event, bounded by projectionDispatchTimeoutMillis (default 30000ms). All native Attempts share the original ten-minute yield deadline, and the entire event shares one fourteen-minute ceiling. New arrivals renew neither budget. Cooperative cancellation cannot preempt synchronous code or stuck finalizers.

BEHAVIOR CHANGE: add IDs to host lanes, return their deadlines from run, and remove pendingDeadline callbacks and wakeScanInterval. Enroll each affected ID explicitly; a generic WakeScheduler.notify is only a promptness hint and does not create host work. On adoption, seed existing host obligations into the queue before serving traffic; registering a lane alone does not discover them. Built-in lanes perform one initial discovery when their queue entries are first created. The queue is new scheduling metadata; canonical history, retry identities, outboxes and receipts require no reset. Upgrade consumers after the matching framework release is published.

Use lifecyclePublication to publish native admissions, progress, controls, waiting, and settlement to an eventually consistent application view:

import { LifecyclePublicationHandler } from "@yielded/agent/lifecycle-publication";
const RuntimeLive = ThreadObject.layer(registrations, {
lifecyclePublication: Layer.effect(LifecyclePublicationHandler)(makeLifecycleHandler),
});

Source transactions durably retain publication intent. A Run’s start, or a Subagent’s actual start, retains the ordered prefix through that record before execution continues. An independent maintenance lane publishes that prefix and subsequent progress while native work runs. Each finite wave materializes a bounded journal suffix and publishes ordered owner batches; committed debt and retry deadlines schedule continuation, with no idle polling. Ledger and delivery facts retain their own source receipts. Attempts, model calls, input joins, and handoffs continue while publication is pending or failing. Eviction reconstructs unmaterialized intent from the journal.

Implement publish(batch) for a nonempty, ordinal-ordered array of at most eight facts from one ownerThreadId. Larger backlogs continue in later batches. Commit the whole batch’s authorization decisions, records, receipts, and delivery intents in one idempotent host transaction before returning. Each call takes a bounded prefix of its owner’s pending facts; facts committed during delivery belong to a later batch. Retries can include already committed identities, so deduplicate each fact’s id. Acknowledgement is atomic for the selected batch and preserves its identity/fingerprint receipts. Publication never replays a model or Tool operation. Destination deletion or revoked authority is an acknowledged domain decision.

source contains immutable private admission evidence, resolved by exact native identities. Delivery facts carry their retained envelope and accepted receipt. Select declared public fields; input, private results, and report payloads are not automatically safe to display. ordinal orders facts within ownerThreadId; source.queueSequence orders accepted inputs within the worker’s Thread. These are separate orders. The handler must reject superseded inputs. AbortIntentRecorded reports the ledger’s accepted abort intent. WorkerInboxSealed.terminal retains the first native seal decision: an assignment outcome, or null for an explicit stop. A Run settlement alone does not establish an inbox seal.

Each batch has a 10-second delivery deadline. Retry state persists before dispatch, with eight automatic attempts and exponential backoff from 1 second to a 60-second cap after the dispatch deadline. An exhausted owner parks with its payload retained; later facts for that owner wait behind it, while execution and other owners continue. After repairing the destination, an operator can call ThreadStore.lifecyclePublications.retryParked(ownerThreadId, nowMillis) through the assembled Cloudflare store; it enrolls the lifecycle lane in the due queue. Serialize publication drains and operator retries per owner. SQL stores acknowledge completed owner batches together at the end of each wave, including completed batches before a later dispatch is interrupted.

Pending and parked obligations retain private payloads until acknowledgement. Keep native source admissions and Run-input records, and do not delete their Object, until publication debt is acknowledged. First enabling the option starts with new commits without backfilling history. Existing source cursors resume retained journal intent; keep the handler enabled while writing new commits and until all debt drains. Existing SQL publication payloads and receipts are preserved when upgrading. In-memory/custom adapters do not retain these obligations. SQL assemblies outside Cloudflare can provide lifecyclePublicationLayer and call drainLifecyclePublications from their existing durable maintenance coordinator; its limit counts owners, not individual facts.

Use the optional publication Layer when canonical records or durable approval, abort, and unknown-resolution intents must be published before dependent native execution. Independent UI relays and outboxes belong in ThreadHostMaintenance, since publication is an execution gate:

import { ThreadPublication } from "@yielded/agent-platform-cloudflare/alarm";
import {
DurableObjectContext,
ThreadObjectIdentity,
} from "@yielded/agent-platform-cloudflare/cloudflare-bindings";
import { ThreadStore } from "@yielded/agent/thread-store";
import { SubmissionLedger } from "@yielded/agent/submission-ledger";
// `makePublication` is an application Effect yielding ThreadPublicationService.
// It yields the raw LOCAL ThreadStore and SubmissionLedger, native DurableObjectContext,
// ThreadObjectIdentity, and any application services its implementation needs.
const RuntimeLive = ThreadObject.layer(registrations, {
publication: Layer.effect(ThreadPublication)(makePublication),
});

Setup errors and service requirements remain in the resulting Layer; its Scope owns acquired resources. Initialization must remain local and bounded. The raw source ports are for reading; publication must not mutate them or write the native alarm slot. Other consumers need no setup.

The host owns schema-versioned cursors, destination idempotency, acknowledgements and retry policy. Implement three hooks, with failures typed as DurableAlarmError:

  • invalidate durably marks source-derived work pending after a source commit.
  • prepareGeneration(generation) invalidates a scan when the native generation changes. Repeated calls for the same generation must preserve bounded scan progress.
  • drain performs bounded delivery, persists acknowledgements or retries, and returns the next epoch-millisecond deadline as Option<number> (None when caught up). Delivery is at least once; use destination idempotency and scope per-delivery resources explicitly.

All hooks except drain must be bounded local operations, without waiting behind network I/O. Hooks can overlap: the host must prevent an older drain from overwriting newer cursor or retry state. Do not reenter source mutations from a publication hook. A parked obligation is host-owned and needs a host repair operation to restore its deadline.

The platform prearms a native generation before ingress mutations and publication-producing runtime writes. It prepares a generation only after its producers have returned, drains publication before recovery or potentially slow Agent work, and keeps the earliest publication/runtime alarm. Pending publication defers runtime work, including when its retry deadline is in the future. Required custom publication also drains after each source commit. Native lifecycle publication uses its independent maintenance lane. A post-commit publication failure is logged without changing the committed source result; a new generation repairs missed invalidation after a crash. Alarm failures propagate for Workerd retry, and interruption remains interruption. Custom host facts must be committed through ThreadMaintenance.withMutation to get the same prearm and post-commit hooks.

Supply projection to ThreadObject.layer with a Layer providing ThreadProjectionMaintenance from @yielded/agent/thread-projection-maintenance. The Layer receives the raw local ThreadStore and the same owner SqlClient; additional services it provides are exposed by the resulting runtime Layer so Tools can share that index.

Implement applyCommitted(request, result) to keep an already-caught-up index current through the complete committed batch before Tools execute. One batch contains at most 256 records; chunk within local byte limits and stop at result.lastSequence. An earlier gap belongs to bounded drain backfill. Rows and their contiguous watermark must commit atomically, including records with no indexable content. Replays and concurrent backfill must be idempotent.

The owner serializes canonical append and live projection; it releases that local gate before publication. Live failures are logged while the source commit remains authoritative. The native alarm runs at most one due backfill batch, and pendingDeadline keeps unfinished work scheduled across reconstruction. A projection deadline never gates approval publication or runtime work. Backfill failures are reported after eligible canonical work, retaining the prearmed generation; interruption stops the event. Hooks return typed ThreadProjectionError failures, own scoped resources, and never write the alarm slot or call source mutation ports.

Memory is optional and belongs in a separate SQLite Durable Object per host-selected MemoryNamespace, not in a Thread Object. Multiple Threads and application ingestion jobs can use the same owner. Canonical Thread history, extraction, and scheduling remain separate.

The compiling setup defines a namespace, owner authorization Layer, ProjectMemory class, and conditional update caller. Register the class:

{
"durable_objects": {
"bindings": [{ "name": "MEMORIES", "class_name": "ProjectMemory" }],
},
"exports": {
"ProjectMemory": { "type": "durable-object", "storage": "sqlite" },
},
}

Add @yielded/agent-storage-cloudflare alongside the packages above. The owner assembles doMemoryStoreLayerWithFailpoints with SqliteClient.layer({ storage: ctx.storage }). Its storage-backed transaction commits the revision and operation receipt together. Local users can instead provide doMemoryStoreLayer(ctx.storage) directly. Neither path imports Node storage.

Bind the namespace and principal in authenticated host code. Never accept them from model output:

import {
import MemoryNamespace
MemoryNamespace
} from "@yielded/agent";
import {
type MemoryAccess<Namespace extends MemoryNamespace.Any = { readonly address: string & Brand<"@effect-agent/core/MemoryNamespaceAddress">; }> = Omit<MemoryAccessWire, "namespace"> & {
readonly namespace: Namespace;
}
const MemoryAccess: {
Wire: typeof MemoryAccessWire;
make: <Namespace extends MemoryNamespace.Any>(fields: MemoryAccess<Namespace>) => MemoryAccess<Namespace>;
}
MemoryAccess
} from "@yielded/agent/memory-revalidation";
import {
type MemoryLookup = {
readonly _tag: "Found";
readonly passages: readonly MemoryPassage[];
} | {
readonly _tag: "NoMatch";
} | {
readonly _tag: "Unavailable";
readonly message: string;
} | {
readonly _tag: "InsufficientFreshness";
readonly message: string;
}
const MemoryLookup: Schema.Union<readonly [Schema.TaggedStruct<"Found", {
readonly passages: Schema.$Array<typeof MemoryPassage>;
}>, Schema.TaggedStruct<"NoMatch", {}>, Schema.TaggedStruct<"Unavailable", {
readonly message: Schema.String;
}>, Schema.TaggedStruct<"InsufficientFreshness", {
readonly message: Schema.String;
}>]>

No-match, unavailable, and insufficient freshness are distinct consumer-visible outcomes.

MemoryLookup
,
class MemoryRecallLimits

Output bounds cover the complete rendered reference text, including citations and provenance.

MemoryRecallLimits
} from "@yielded/agent/memory-reference";
import {
type MemoryScope = string & Brand<"@effect-agent/core/MemoryScope">
const MemoryScope: Schema.brand<Schema.NonEmptyString, "@effect-agent/core/MemoryScope">

Host-defined recall visibility label. A scope identifies access policy; it does not grant it.

MemoryScope
} from "@yielded/agent/memory-store";
import {
const CloudflareMemoryClient: {
make: <Namespace extends MemoryNamespace.Any>(access: MemoryAccess<Namespace>, principal: string & Brand<"@effect-agent/thread/Principal">, rpcLimits?: MemoryRpcLimits | undefined) => Effect.Effect<{
get: (key: MemoryKey<Namespace>) => Effect.Effect<MemoryDocument<Namespace> | null, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: (text: string) => number) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}, MemoryRpcError, MemoryObjectNamespace>;
fromBinding: <Namespace extends MemoryNamespace.Any>(binding: DurableObjectNamespace<MemoryObjectRpc>, options: {
readonly access: MemoryAccess<Namespace>;
readonly principal: Principal;
readonly rpcLimits?: MemoryRpcLimits;
}) => Effect.Effect<...>;
}
CloudflareMemoryClient
,
type
(alias) interface MemoryObjectRpc
import MemoryObjectRpc
MemoryObjectRpc
,
} from "@yielded/agent-platform-cloudflare/cloudflare-memory";
import {
type Principal = string & Brand<"@effect-agent/thread/Principal">
const Principal: Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/Principal">

Stable host-authenticated admission principal; the established durable brand is preserved.

Principal
} from "@yielded/agent/submission-ledger";
import {
import Effect
Effect
,
import Schema
Schema
} from "effect";
const
const Projects: {
name: "app/projects";
version: 1;
make: (identity: string) => MemoryNamespace.Value<"app/projects", 1, string>;
decode: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
restore: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
}
Projects
=
import MemoryNamespace
MemoryNamespace
.
const define: <"app/projects", 1, string, string>(options: {
readonly name: "app/projects";
readonly version: 1;
readonly identity: Schema.Codec<string, string, never, never>;
}) => {
name: "app/projects";
version: 1;
make: (identity: string) => MemoryNamespace.Value<"app/projects", 1, string>;
decode: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
restore: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
}
define
({
name: "app/projects"
name
: "app/projects",
version: 1
version
: 1,
identity: Schema.Codec<string, string, never, never>
identity
:
import Schema
Schema
.
const String: Schema.String

Type-level representation of

String

.

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

@category ― models

@since ― 4.0.0

@category ― schemas

@since ― 4.0.0

String
,
});
const
const access: MemoryAccess<MemoryNamespace.Value<"app/projects", 1, string>>
access
=
const MemoryAccess: {
Wire: typeof MemoryAccessWire;
make: <Namespace extends MemoryNamespace.Any>(fields: MemoryAccess<Namespace>) => MemoryAccess<Namespace>;
}
MemoryAccess
.
make: <MemoryNamespace.Value<"app/projects", 1, string>>(fields: MemoryAccess<MemoryNamespace.Value<"app/projects", 1, string>>) => MemoryAccess<MemoryNamespace.Value<"app/projects", 1, string>>
make
({
namespace: MemoryNamespace.Value<"app/projects", 1, string>
namespace
:
const Projects: {
name: "app/projects";
version: 1;
make: (identity: string) => MemoryNamespace.Value<"app/projects", 1, string>;
decode: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
restore: (input: unknown) => Effect.Effect<Readonly<MemoryNamespace.Value<"app/projects", 1, string>>, MemoryNamespace.MemoryNamespaceError, never>;
}
Projects
.
make: (identity: string) => MemoryNamespace.Value<"app/projects", 1, string>
make
("authorized-project"),
scope: string & Brand<"@effect-agent/core/MemoryScope">
scope
:
const MemoryScope: Schema.brand<Schema.NonEmptyString, "@effect-agent/core/MemoryScope">

Host-defined recall visibility label. A scope identifies access policy; it does not grant it.

MemoryScope
.
BottomWithoutNew<unknown, unknown, unknown, unknown, String, brand<NonEmptyString, "@effect-agent/core/MemoryScope">, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.make(input: string, options?: Schema.MakeOptions): string & Brand<"@effect-agent/core/MemoryScope">

Constructs a value from the make input representation synchronously.

When to use

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

Details

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

Gotchas

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

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

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

make
("project"),
});
const
const limits: MemoryRecallLimits
limits
=
class MemoryRecallLimits

Output bounds cover the complete rendered reference text, including citations and provenance.

MemoryRecallLimits
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<MemoryRecallLimits, { readonly maxSources: number; readonly maxItems: number; readonly maxBytes: number; readonly maxTokens: number; readonly timeoutMillis: number; readonly maxInputBytes?: number | undefined; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly maxSources: number;
readonly maxItems: number;
readonly maxBytes: number;
readonly maxTokens: number;
readonly timeoutMillis: number;
readonly maxInputBytes?: number | undefined;
}, options?: Schema.MakeOptions): MemoryRecallLimits

Constructs a value from the make input representation synchronously.

When to use

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

Details

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

Gotchas

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

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

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

make
({
maxSources: number
maxSources
: 16,
maxItems: number
maxItems
: 32,
maxBytes: number
maxBytes
: 32000,
maxTokens: number
maxTokens
: 32000,
maxInputBytes?: number | undefined

Aggregate UTF-8 JSON passage input, including omitted/duplicate candidates. Defaults to 16 MiB.

maxInputBytes
: 1000000,
timeoutMillis: number
timeoutMillis
: 5000,
});
export const
const recall: (binding: DurableObjectNamespace<MemoryObjectRpc>, candidates: MemoryLookup) => Effect.Effect<RecalledMemory, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>
recall
= (
binding: DurableObjectNamespace<MemoryObjectRpc>
binding
:
class DurableObjectNamespace<T extends Rpc.DurableObjectBranded | undefined = undefined>
DurableObjectNamespace
<
(alias) interface MemoryObjectRpc
import MemoryObjectRpc
MemoryObjectRpc
>,
candidates: {
readonly _tag: "Found";
readonly passages: readonly MemoryPassage[];
} | {
readonly _tag: "NoMatch";
} | {
readonly _tag: "Unavailable";
readonly message: string;
} | {
readonly _tag: "InsufficientFreshness";
readonly message: string;
}
candidates
:
type MemoryLookup = {
readonly _tag: "Found";
readonly passages: readonly MemoryPassage[];
} | {
readonly _tag: "NoMatch";
} | {
readonly _tag: "Unavailable";
readonly message: string;
} | {
readonly _tag: "InsufficientFreshness";
readonly message: string;
}

No-match, unavailable, and insufficient freshness are distinct consumer-visible outcomes.

MemoryLookup
,
) =>
import Effect
Effect
.
const gen: <Effect.Effect<RecalledMemory, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never> | Effect.Effect<{
get: (key: MemoryKey<MemoryNamespace.Value<"app/projects", 1, string>>) => Effect.Effect<...>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: ((text: string) => number) | undefined) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}, MemoryRpcError, never>, RecalledMemory>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

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

When to use

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

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

Example (Sequencing effects with generators)

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

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const client: {
get: (key: MemoryKey<MemoryNamespace.Value<"app/projects", 1, string>>) => Effect.Effect<MemoryDocument<MemoryNamespace.Value<"app/projects", 1, string>> | null, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: ((text: string) => number) | undefined) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}
client
= yield*
const CloudflareMemoryClient: {
make: <Namespace extends MemoryNamespace.Any>(access: MemoryAccess<Namespace>, principal: string & Brand<"@effect-agent/thread/Principal">, rpcLimits?: MemoryRpcLimits | undefined) => Effect.Effect<{
get: (key: MemoryKey<Namespace>) => Effect.Effect<MemoryDocument<Namespace> | null, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: (text: string) => number) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}, MemoryRpcError, MemoryObjectNamespace>;
fromBinding: <Namespace extends MemoryNamespace.Any>(binding: DurableObjectNamespace<MemoryObjectRpc>, options: {
readonly access: MemoryAccess<Namespace>;
readonly principal: Principal;
readonly rpcLimits?: MemoryRpcLimits;
}) => Effect.Effect<...>;
}
CloudflareMemoryClient
.
fromBinding: <MemoryNamespace.Value<"app/projects", 1, string>>(binding: DurableObjectNamespace<MemoryObjectRpc>, options: {
readonly access: MemoryAccess<MemoryNamespace.Value<"app/projects", 1, string>>;
readonly principal: Principal;
readonly rpcLimits?: MemoryRpcLimits;
}) => Effect.Effect<{
get: (key: MemoryKey<MemoryNamespace.Value<"app/projects", 1, string>>) => Effect.Effect<MemoryDocument<MemoryNamespace.Value<"app/projects", 1, string>> | null, MemoryRpcError | ... 7 more ... | SemanticMemoryError, never>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: ((text: string) => number) | undefined) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}, MemoryRpcError, never>

Use a resolved Worker or Durable Object binding without manual service provisioning.

fromBinding
(
binding: DurableObjectNamespace<MemoryObjectRpc>
binding
, {
access: MemoryAccess<MemoryNamespace.Value<"app/projects", 1, string>>
access
,
principal: string & Brand<"@effect-agent/thread/Principal">
principal
:
const Principal: Schema.brand<Schema.NonEmptyString, "@effect-agent/thread/Principal">

Stable host-authenticated admission principal; the established durable brand is preserved.

Principal
.
BottomWithoutNew<unknown, unknown, unknown, unknown, String, brand<NonEmptyString, "@effect-agent/thread/Principal">, unknown, unknown, readonly [], unknown, "readonly", "required", "no-default", "readonly", "required">.make(input: string, options?: Schema.MakeOptions): string & Brand<"@effect-agent/thread/Principal">

Constructs a value from the make input representation synchronously.

When to use

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

Details

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

Gotchas

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

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

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

make
("authenticated-principal"),
});
return yield*
const client: {
get: (key: MemoryKey<MemoryNamespace.Value<"app/projects", 1, string>>) => Effect.Effect<MemoryDocument<MemoryNamespace.Value<"app/projects", 1, string>> | null, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>;
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: ((text: string) => number) | undefined) => Effect.Effect<...>;
revalidate: (lookup: {
...;
} | ... 2 more ... | {
...;
}, limits: MemoryRecallLimits) => Effect.Effect<...>;
revalidateSemantic: (found: MemoryIndexSearch<...>, profile: SemanticMemoryProfile, limits: SemanticCandidateLimits) => Effect.Effect<...>;
change: (write: MemoryWrite<...>) => Effect.Effect<...>;
}
client
.
recall: (lookup: MemoryLookup, limits: MemoryRecallLimits, estimateTokens?: ((text: string) => number) | undefined) => Effect.Effect<RecalledMemory, MemoryRpcError | MemoryStorageError | MemoryRecallError | MemoryConflict | MemoryWithdrawn | MemoryOperationConflict | MemoryMutationFailure | MemoryIndexError | SemanticMemoryError, never>

Revalidate in one owner RPC, then render whole passages within the caller's budget. The bound source is essential: unavailable/stale results and matches that cannot fit fail instead of silently producing empty context. No-match remains successful. The single outcome has sourceId "memory". No embedding or candidate search is performed. Use revalidate with Memory.recall for multiple readers sharing one output budget.

recall
(
candidates: {
readonly _tag: "Found";
readonly passages: readonly MemoryPassage[];
} | {
readonly _tag: "NoMatch";
} | {
readonly _tag: "Unavailable";
readonly message: string;
} | {
readonly _tag: "InsufficientFreshness";
readonly message: string;
}
candidates
,
const limits: MemoryRecallLimits
limits
);
});

CloudflareMemoryClient.fromBinding accepts a resolved binding from either a Worker or another Durable Object. It provisions MemoryObjectNamespace internally; constructing the client does not make an RPC. Applications that provide that service once through their Effect Layers can use CloudflareMemoryClient.make(access, principal) instead. Both return the same Effect-native client with the same validation and budgets.

When the host already knows the document key, use client.get(key) with a MemoryKey in the bound namespace. The compiling example reads project-profile directly. It sends one Get owner request and returns a schema-validated MemoryDocument with its current source.revision, or null only when the key is absent. A withdrawn key returns WithdrawnMemoryDocument, containing its terminal revision and no content. There is no extraction, job draining, embedding, candidate search, index refresh, rendering, or background readiness wait on this path.

The owner invokes MemoryOwnerAuthorizer for the exact key, namespace, authenticated principal, and scope before reading, including for absent and withdrawn documents. It also denies active documents whose scopes omit the bound scope. Key or scope possession never grants access. For source-dependent memories, the application’s owner authorizer must preserve its source authority and provenance checks; a current document revision does not prove that its original evidence remains authorized or current. These checks remain application-owned and are not replaced by get.

Reads begun after an acknowledged write see that revision or a later one through the same SQLite owner. Reads after withdrawal return the tombstone; an already captured read may finish. The adapter must fail with MemoryStorageError if it cannot provide a current view. Denied access, expired deadlines, unavailable owners, invalid wire data, and exceeded budgets remain typed failures, never null. Both client and owner enforce MemoryRpcLimits: encoded request and response bytes, encoded document maxSourceBytes, and timeoutMillis. Storage row limits bound local reads before wire encoding. An interrupted caller stops waiting; the owner’s own deadline finalizes its work. The CloudflareMemoryClient.get span measures the client operation without adding document text, keys, principals, or scopes as span attributes. Measure application authentication and rendering separately; this adapter operation alone does not establish a 100–200 ms complete lookup target.

Access and document scopes share the MemoryScope brand from core. Clients and owner authorizers use the existing Principal brand from thread. Decode external values with their Effect Schemas after authentication; .make is suitable for trusted constants. Scopes are nonempty strings of at most 1,024 characters, and principals are nonempty strings of at most 256 characters. Both encode as ordinary strings on the wire. Brands prevent category mix-ups; they do not grant authorization.

One recall sends all admitted candidates in one RPC to namespace.address. The owner verifies its name, request namespace, principal, and scope; it then reads each distinct source locally once. Passage ordering and authoritative attribution survive the round trip. The client applies the final rendered item, byte, and token budgets locally and returns RecalledMemory. Oversized batches fail typed and are never split into per-document calls. There is deliberately no remote per-document MemoryReader Layer.

The bound source is essential: unavailable or insufficiently fresh results fail, as do matches that cannot fit the output budget. No-match succeeds with empty text. The result has one source outcome with sourceId: "memory". An optional third argument supplies the selected model’s token estimator; without it, recall conservatively estimates one token per UTF-8 byte. The recall deadline covers revalidation and local composition; the engine still enforces its full per-call context budget.

Use client.revalidate(candidates, limits) when you need validated passages without rendering. To combine multiple readers under one shared budget, use Memory.recall from @yielded/agent with their revalidation effects as sources. It retains explicit source IDs and essential/optional policy for that multi-reader case.

For an external semantic index, call client.revalidateSemantic(search, profile, limits) with its MemoryIndexSearch result. Embedding and search stay application-owned. This one RPC checks current generation, revision, locator, exact UTF-8 ranges, scope, and withdrawal before returning result.lookup. Stale scored candidates are omitted. Ordinary cached lookup revalidation instead replaces stale text with the current document, matching local recall. Neither path trusts cached attribution. Semantic validation counts the complete UTF-8 JSON of every accepted passage before retaining it, including repeated metadata and attribution. Its maxOutputBytes defaults to 16 MiB and is capped at the owner’s maxResponseBytes; the final envelope is checked separately. Duplicate-heavy output fails with SemanticMemoryError reason budget before an oversized result is assembled.

Default owner limits are 16 distinct sources, 1 MiB encoded request, 4 MiB encoded response, 16 MiB revalidation input, and a 10-second deadline. MemoryObject.make accepts rpcLimits and storageLimits. Storage defaults cap encoded rows at 1,900,000 bytes, 10,000 documents, 100,000 operation receipts, and 512 MiB of conservatively accounted row data. SQLite page/index overhead is not included. Tombstones and receipts count toward capacity; there is no automatic pruning. Replacements charge the difference between the old and new encoded document, plus the new receipt. Persistent counters make admission independent of retained history size. Opening an existing version-2 store initializes counters once, atomically, without rewriting documents or receipts.

Optional reservedWithdrawalReceipts and reservedWithdrawalBytes storage limits default to zero. They withhold capacity from ordinary Put within the existing hard receipt and byte limits; Withdraw can use the remaining hard budget. Row and document limits still apply. A withdrawal of an existing source adds one receipt and no document identity. A missing source cannot be withdrawn: the host must retain suppression for work that arrives before a document exists.

Reserves are finite. Budget enough receipts and encoded bytes for the cleanup commands the host must complete; they do not guarantee unlimited cleanup. An existing store above the ordinary threshold remains readable and replayable, while new ordinary writes fail typed. A full store needs an explicit capacity increase within supported bounds before it has cleanup headroom.

Deploy exclusively upgraded writers before relying on reserves. Accounting triggers include already-open older writers, but those writers can consume the reserved region because they do not know the new admission policy. Keep the database, accounting table, triggers and metadata together in backups; missing established accounting fails rather than silently resetting usage.

Cleanup that edits a shared profile is a Put. A trusted host can build a second memoryStoreLayer with SqlMemoryLimits over the same owner SQL client, omitting the reserves while retaining the same hard totals. Authorize that capability only for cleanup obligations. Limits are captured when the Layer is built; providing different limits around an existing writer call does not change them. Do not create a second independently locked DO SQL client.

ThreadObject.layer exposes its existing generic Effect SqlClient through ThreadObject.Services. Build optional owner-local repositories after that Layer and reuse this client. For local Memory, provide memoryStoreLayer with explicit SqlMemoryLimits, using defaultDoMemoryStorageLimits from @yielded/agent-storage-cloudflare/do-memory-store or stricter validated limits. The generic SQL Memory defaults are not Durable Object limits. Thread Objects install no Memory tables unless the host composes the Memory store.

Owner-local Memory reads reuse validated, write-through documents, operation receipts, and usage counters under the Thread Object’s transaction gate. Rebuilt Layers share those views; eviction and failed transactions discard them. Cache misses read SQLite. Direct maintenance writes must follow the storage invalidation contract.

The SQL Memory Layer also supplies SqlMemoryBatchWriter from @yielded/agent/sql-memory-store. Use changeMany(commands) to commit up to 128 commands atomically, with results in input order. Commands see earlier revisions in the batch; identical operation IDs recover their original results, and any conflict or exceeded limit rolls back the whole batch. Storage limits apply to every intermediate revision. Single-command MemoryWriter.change uses the same writer. The batch service is owner-local; routed CloudflareMemoryClient.change remains one command.

Expected failures cross RPC in Schema-defined envelopes. MemoryRpcError distinguishes denied, protocol, budget, timeout, and unavailable failures; source and write errors retain their domain tags. cloudflareMemoryWriterLayer(access, principal) adapts the client for an application’s committed activity destination, preserving domain errors and mapping transport failures to MemoryStorageError. The application still owns invoking processCommittedActivity and persisting its progress.

Successful writes are visible to checks begun afterward. Already captured views may finish. Caller interruption stops waiting but does not promise remote cancellation; the owner enforces its own deadline and finalizes request-scoped work. A failed or timed-out write may have committed. Retry only its identical operation ID and command to recover the original receipt. Changed commands with the same ID fail; withdrawal is terminal. Owner eviction preserves SQLite records and receipts.

Named Effect spans cover calls and local validation without adding source text, private namespace values, or metadata to span attributes. Keep RPC bindings private and audit host authorization.

The opt-in deployed benchmark measures 1, 4, 8, and 16 sources plus duplicate-heavy candidates, with separate validation-RPC and full-recall durations. Local SQLite and workerd runs do not establish deployed latency.

Bind the remembering checkpoint contract to the application’s existing owner-local jobs. Source commit and outbox admission must be durable; the owner then runs finite remembering passes in a separate Scope. Keep its model permits separate from foreground runs. A blocked extraction or profile write must not hold a producer lock or a database transaction.

The application’s job discovery, retry schedule, quotas, authorization, and alarm composition remain host responsibilities.

Retain source-to-target checkpoints after pruning active jobs. Invalidation reactivates them for conditional cleanup and preserves uncertain prepared commands. Cleanup of an aggregate profile’s last contribution should leave an empty writable profile; a Withdraw memory command permanently withdraws the entire target. Do not discard receipts or suppression to admit more work.

Alarms recover pending work after eviction without another user request. The host owns the Object’s single alarm; do not replace its handler or schedule unrelated alarms on that Object.

Schedule Owners and Subscription Partitions use effect-cf logical alarms. Failed handlers and self-rearms use exponential backoff with a one-second minimum; after eight attempts without reported source progress, recovery runs hourly. Deadline changes and retry counters do not reset that budget. See the logical alarm recovery guide for configuration and persisted schedule upgrades. Thread Objects retain their own native alarm policy described below.

Each Thread alarm grants an initial head Attempt and can advance further heads while auxiliary delivery remains in flight. Recovery precedes each claim, and all Attempts share the event’s original ten-minute yield deadline. Accepted input can still join the active Run at normal turn boundaries. At the yield deadline, the Attempt commits its completed turn before yielding; a later alarm resumes the same Run with its original duration deadline and cumulative usage.

The whole Thread alarm has a fourteen-minute watchdog, including time waiting for another pass. The Schedule Owner uses the same watchdog while scanning due schedules. It continues past failed pages so a page of broken schedules cannot block healthy followers. Interrupted work keeps its durable retry obligation. These timers leave room below Cloudflare’s fifteen-minute alarm lifetime, but cannot preempt synchronous CPU work or an uninterruptible finalizer. Cloudflare’s CPU limit is separate from elapsed time.

maxQueueDepthPerLane, maxInputBytes, and maxDatabaseBytes refuse excess work with AdmissionLimitExceeded before admission. Keep Object RPC private and supply operationAuthorizer for application access rules. The default policy trusts service possession.

An ordinary tool interrupted before its outcome is confirmed can become Unknown during recovery; it is never automatically replayed. Unconfirmed outcomes need authorized resolution. See operations.

Cloudflare’s 128 MB memory limit applies to an isolate, which can contain multiple Durable Objects and their Worker. It is not a separate allowance for every Object. See memory usage metrics.

Use the package subpaths shown above to keep dependencies explicit. The package also declares unused modules removable, so Wrangler can remove unused adapters from root imports.

Canonical reads and observations fetch at most 4 MiB of record JSON per internal SQL page, after capturing up to 1,024 sequence and size entries. Decoded objects and strings require additional heap. Recovery retains evidence for the addressed runs; prompt projection scans a fixed canonical tail and avoids retaining summarized response payloads. Metadata and scanning work still grow with history, and an uncompacted prompt still grows with the conversation. Configure contextTokenLimit, compaction, tool result bounds, and concurrency for the workload from the start. Admission and record-size limits do not reserve isolate memory. Whole-thread export still returns a complete collection; use paged reads for large histories.

The local heap benchmark measures exact Worker bundles and several concurrent Thread Objects using a synthetic model and tools. It requires no model key or deployment. Its local JavaScript heap snapshots help compare changes; profile production-like histories and tool payloads before choosing deployment capacity.

Use the Code Mode guide to run generated JavaScript in a Dynamic Worker with allowlisted host tools. The warehouse example queries a SQLite Durable Object through that broker; its agent runs ephemerally and uses the Object for data only.

The browser guide covers Quick Actions, screenshots, REST capture and crawl, and interactive passes with Live View and handoff. Browser adapters use separate package imports and can be used without a durable thread host. REST capture and crawl also work on Node.