Skip to content

Storage

PostgreSQL

Connect PostgreSQL storage to your agent using your application’s native Effect SQL client:

import {
import PostgresStorage
PostgresStorage
} from "@yielded/agent-storage-postgres";
import {
import NodeCrypto
NodeCrypto
} from "@effect/platform-node";
import {
import PgClient
PgClient
} from "@effect/sql-pg";
import {
import Config
Config
,
import Effect
Effect
,
import Layer
Layer
} from "effect";
import {
import AgentRuntime
AgentRuntime
,
import PersistentHistory
PersistentHistory
} from "@yielded/agent";
const
const Database: Layer.Layer<PgClient.PgClient | SqlClient, Config.ConfigError | SqlError, never>
Database
=
import PgClient
PgClient
.
const layerConfig: (config: Config.Wrap<PgClient.PgPoolConfig>) => Layer.Layer<PgClient.PgClient | SqlClient, Config.ConfigError | SqlError>

Creates a client layer from wrapped pool configuration.

@category ― layers

@since ― 4.0.0

layerConfig
({
url?: {
readonly label: Config.Config<string | undefined>;
readonly "~effect/Redacted": {
readonly _A: Config.Config<Covariant<string>>;
} | Config.Config<{
readonly _A: Covariant<string>;
}>;
readonly "~effect/Equal": Config.Config<(that: Equal) => boolean>;
readonly "~effect/Hash": Config.Config<() => number>;
readonly pipe: Config.Config<{
<A>(this: A): A;
<A, B = never>(this: A, ab: (_: A) => B): B;
<A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C;
<A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D;
<A, B = never, C = never, D = never, E = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E): E;
<A, B = never, C = never, D = never, E = never, F = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F): F;
<A, B = never, C = never, D = never, E = never, F = never, G = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G): G;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H): H;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I): I;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J): J;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K): K;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L): L;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L, lm: (_: L) => M): M;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never, N = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L, lm: (_: L) => M, mn: (_: M) => N): N;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never, N = never, O = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L, lm: (_: L) => M, mn: (_: M) => N, no: (_: N) => O): O;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never, N = never, O = never, P = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L, lm: (_: L) => M, mn: (_: M) => N, no: (_: N) => O, op: (_: O) => P): P;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never, N = never, O = never, P = never, Q = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: K) => L, lm: (_: L) => M, mn: (_: M) => N, no: (_: N) => O, op: (_: O) => P, pq: (_: P) => Q): Q;
<A, B = never, C = never, D = never, E = never, F = never, G = never, H = never, I = never, J = never, K = never, L = never, M = never, N = never, O = never, P = never, Q = never, R = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D, de: (_: D) => E, ef: (_: E) => F, fg: (_: F) => G, gh: (_: G) => H, hi: (_: H) => I, ij: (_: I) => J, jk: (_: J) => K, kl: (_: ...
url
:
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
("DATABASE_URL"),
});
const
const Persistence: Layer.Layer<PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>
Persistence
=
import PostgresStorage
PostgresStorage
.
const layer: Layer.Layer<ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher, PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, SqlClient | Crypto>

Thread history and submissions with default settings. Requires SqlClient and Crypto.

layer
.
Pipeable.pipe<Layer.Layer<ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher, PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, SqlClient | Crypto>, Layer.Layer<PgClient.PgClient | SqlClient | ... 4 more ... | SettlementPublisher, Config.ConfigError | ... 4 more ... | PostgresWriteContention, Crypto>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provideMerge: <never, Config.ConfigError | SqlError, PgClient.PgClient | SqlClient>(that: Layer.Layer<PgClient.PgClient | SqlClient, Config.ConfigError | SqlError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<PgClient.PgClient | SqlClient | ROut2, Config.ConfigError | SqlError | E2, Exclude<RIn2, PgClient.PgClient | SqlClient>> (+3 overloads)

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

When to use

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

Details

Prefer

provide

when the dependency should stay private.

Example (Providing dependencies while retaining services)

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

@see ― provide for keeping dependency services private

@category ― providing services

@since ― 2.0.0

provideMerge
(
const Database: Layer.Layer<PgClient.PgClient | SqlClient, Config.ConfigError | SqlError, never>
Database
),
import Layer
Layer
.
const provide: <never, never, Crypto>(that: Layer.Layer<Crypto, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, Crypto>> (+3 overloads)

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

When to use

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

Details

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

Example (Providing layer dependencies)

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

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

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

Layer that provides the Node.js Crypto service implementation.

@category ― layers

@since ― 1.0.0

layer
),
);
const
const History: Layer.Layer<PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher | ThreadHistory, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>
History
=
import PersistentHistory
PersistentHistory
.
const layer: Layer.Layer<ThreadHistory, never, ThreadStore>

Provide retained history to the normal AgentRuntime entry points. Each successful Run appends one three-record batch. Staging is private to that Run; interruption discards it. Epoch zero and the loaded tail fence stale writers without replaying external execution. A storage failure after append may leave the whole Run recorded. Adapter failpoints cover both durable mutations.

layer
.
Pipeable.pipe<Layer.Layer<ThreadHistory, never, ThreadStore>, Layer.Layer<PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher | ThreadHistory, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provideMerge: <never, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher>(that: Layer.Layer<PgClient.PgClient | ... 5 more ... | SettlementPublisher, Config.ConfigError | ... 4 more ... | PostgresWriteContention, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<...>) => Layer.Layer<...> (+3 overloads)

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

When to use

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

Details

Prefer

provide

when the dependency should stay private.

Example (Providing dependencies while retaining services)

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

@see ― provide for keeping dependency services private

@category ― providing services

@since ― 2.0.0

provideMerge
(
const Persistence: Layer.Layer<PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>
Persistence
));
const
const program: Effect.Effect<{
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "budget-exhausted" | "model-stop";
readonly turns: number;
readonly usage?: RunTotals | undefined;
readonly runDisposition?: Json | undefined;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
readonly delegatedUsage?: RunTotals | undefined;
}, Config.ConfigError | ... 5 more ... | AgentRuntime.AgentRuntimeFailure<...>, ModelServices>
program
=
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 agent: Definition<String, Struct<{
readonly itinerary: $Array<String>;
}>, "Plan a trip with one itinerary entry per day.", Toolkit<{}>, undefined, undefined, undefined> & {
readonly id: Brand<"@effect-agent/core/AgentId"> & "trip-planner";
}
agent
,
const input: string
input
, {
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
}).
Pipeable.pipe<Effect.Effect<{
readonly runId: string & Brand<"@effect-agent/core/RunId">;
readonly threadId: string & Brand<"@effect-agent/core/ThreadId">;
readonly output: {
readonly itinerary: readonly string[];
};
readonly finishReason: "completed" | "budget-exhausted" | "model-stop";
readonly turns: number;
readonly usage?: RunTotals | undefined;
readonly runDisposition?: Json | undefined;
readonly exhausted?: "tokens" | "tool-calls" | "turns" | 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: <PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher | ThreadHistory, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>(layer: Layer.Layer<...>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+5 overloads)

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

Example (Providing dependencies with a layer)

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

@category ― providing services

@since ― 2.0.0

provide
(
const History: Layer.Layer<PgClient.PgClient | SqlClient | ThreadReader | ThreadStore | ThreadImport | SubmissionLedger | SettlementPublisher | ThreadHistory, Config.ConfigError | SqlError | PostgresStorageError | PostgresStorageCompatibilityError | PostgresStorageCorruptionError | PostgresWriteContention, never>
History
));
bun add @yielded/agent-storage-postgres@beta effect

Requires PostgreSQL 16 or newer. Create the database and set DATABASE_URL to its connection URL. Keep framework packages at one release and use compatible Effect and model provider packages.

Here, agent, input, and threadId come from your application. PostgresStorage.layer provides ThreadStore and SubmissionLedger; PersistentHistory.layer connects the thread store to ordinary AgentRuntime calls. History also exposes the same native SQL client for application queries. Supply the agent’s model and tool Layers at your application boundary. For a server, provide History once around the application or build one ManagedRuntime so requests share the pool. Reuse the same thread ID to continue a conversation, including after a process restart.

This setup commits each successful Run as one atomic batch. It does not recover interrupted execution. See retained history for commit and concurrency behavior, and persistence and durability for recovery.

The adapter initializes its tables when the Layer opens. Credentials need permission to use and initialize the selected schema. Tables default to public; use PostgresStorage.layerWith({ schema: "agent" }) for another namespace. An existing schema avoids the need for database-wide CREATE permission. Storage qualifies its tables without changing the client’s search path, codecs, or application query transformations.

Call storage writes and snapshot reads outside sql.withTransaction: the adapter owns its transactions and rejects nesting. It also rejects incompatible stored versions.

For individual stores, configuration, and error details, see the PostgreSQL package reference. For durable execution with PostgreSQL, supply these stores to a custom durable runtime and provide its recovery driver. The existing Node.js host owns SQLite storage.