Skip to content

Storage

In-memory

Use InMemory.layer from @yielded/agent to share conversation history across runs:

import {
import AgentRuntime
AgentRuntime
,
import InMemory
InMemory
} from "@yielded/agent";
import {
import Effect
Effect
} from "effect";
const
const conversation: Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, ModelServices>
conversation
=
import Effect
Effect
.
const gen: <Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>, {
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}>(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 first: {
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}
first
= yield*
import AgentRuntime
AgentRuntime
.
run<Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, never, never>(agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, input: string, options?: RunOptions<...> | undefined): Effect.Effect<...>
export run

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

run
(
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
planner
, "Plan a trip to Lisbon");
return yield*
import AgentRuntime
AgentRuntime
.
run<Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, never, never>(agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}, input: string, options?: RunOptions<...> | undefined): Effect.Effect<...>
export run

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

run
(
const planner: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
planner
, "Make it cheaper", {
RunOptions<HookError = never, HookRequirements = never>.threadId?: (string & Brand<"@effect-agent/core/ThreadId">) | undefined

Reuse a Thread identity, including retained history, instead of allocating one.

threadId
:
const first: {
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}
first
.
threadId: string & Brand<"@effect-agent/core/ThreadId">
threadId
,
});
}).
Pipeable.pipe<Effect.Effect<{
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "model-stop" | "budget-exhausted";
readonly turns: number;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly runDisposition?: Json | undefined;
readonly usage?: RunTotals | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, AgentRuntime.AgentRuntimeFailure<Definition<String, ... 5 more ..., undefined> & {
...;
}, never, never>, AgentRuntime.AgentRuntimeRequirements<...>>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <ThreadHistory | Store | SubagentReservations, never, never>(layer: Layer<ThreadHistory | Store | SubagentReservations, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ThreadHistory | Store | SubagentReservations>> (+5 overloads)

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

Example (Providing dependencies with a layer)

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

@category ― providing services

@since ― 2.0.0

provide
(
import InMemory
InMemory
.
const layer: Layer<ThreadHistory | Store | SubagentReservations, never, never>

Run agents and attached subagents with in-memory conversation history. Provide once around the parent program and all child handler Layers so siblings share history and one reservation ledger. Reuse a Thread ID to continue a conversation. Conversations can span any number of Runs within the store's capacity limits. Use scoped for disposable request workflows within this shared store. Each independent Layer build owns fresh state; state is released when its Scope closes. Process loss loses history and active execution; this Layer provides no crash recovery.

Models, tool handlers, and provider clients remain application-supplied. Default IDs need no Layer; enclosing ID and context-preparation overrides are preserved. For storage-backed history, provide PersistentHistory.layer and a shared SubagentReservationsMemoryLive instead. Durable hosts own their own assembly.

layer
));

Here, planner is an agent definition. Supply its model and tool services around the program; see getting started for complete provider setup. No storage package is needed.

Provide the Layer once around the conversation, or use one ManagedRuntime for a long-lived application. A new Layer acquisition creates a separate store. Reuse the returned threadId for follow-ups; omitting it starts a new conversation.

InMemory.layer shares conversation history and attached-subagent reservations. History records complete messages and tool batches as execution advances. If a later step fails or is interrupted, earlier recorded updates remain available.

State stays available while the application Scope remains open, within the store’s capacity limits. Closing the Scope or losing the process loses that state. See in-memory conversations for limits and history inspection.

Canonical stores for tests and custom assemblies

Section titled “Canonical stores for tests and custom assemblies”

@yielded/agent-storage-memory supplies implementations of the canonical ThreadStore, SubmissionLedger and SettlementPublisher ports. These are useful for adapter tests and custom runtime assemblies. The ledger and publisher share the thread store’s mutation boundary. Assemble them with MemorySubmissionLedgerLive.pipe(Layer.provideMerge(MemoryThreadStoreLive)) so each assembly owns one store. They are separate from the conversation store supplied by InMemory.layer; the submission ledger reports itself as non-durable.

When using memoryMessageDeliveryStoreLayer, merge it with the ledger before providing the same MemoryThreadStoreLive. Thread exports then identify retained deliveries as external obligations, and import refuses to overwrite delivery work.

MemoryThreadStoreLive, imported from @yielded/agent-storage-memory/memory-thread-store, provides ThreadStore and requires a platform Crypto Layer. Providing it to PersistentHistory.layer uses the whole successful-run commit policy, while still keeping all records in memory.

For history that survives a restart, choose SQLite or Postgres. To recover unfinished work as well, use a durable platform.