Runtime construction
When you need to drop below `createPleachRoute` — the `createPleachRuntime` factory, the `SessionRuntime` constructor, and the React `HarnessProvider`.
Getting started shows the route-handler
path — createPleachRoute() wraps everything below into one
fetch handler. This page is for the cases where you need direct
access to the runtime:
- A headless script, eval harness, or test fixture (no HTTP boundary).
- A multi-tenant SaaS with per-tenant storage / policies / plugins.
- A custom transport that doesn't fit the
Request → Responseshape. - An existing React app that wants the hook surface without the route handler.
Three entry points, ordered by depth.
createPleachRuntime — the factory
One line, sensible defaults baked in: in-memory storage, the
default memoryCacheBackend, no plugins, no safety policies.
Use it in tutorials, eval harnesses, and headless scripts.
import { createPleachRuntime } from "@pleach/core";
const runtime = createPleachRuntime();
const { id: sessionId } = await runtime.sessions.create();
for await (const event of runtime.executeMessage(sessionId, "Hello")) {
console.log(event.type, event);
}Every field on CreatePleachRuntimeConfig is optional. The
typical form layers on a persistent storage adapter, an explicit
tenantId, and a plugin set:
import { createPleachRuntime } from "@pleach/core";
import { SupabaseAdapter } from "@pleach/core/sessions";
const runtime = createPleachRuntime({
userId: "alice@example.com",
tenantId: "acme-corp",
storage: new SupabaseAdapter({ client: supabase }),
plugins: [myAppPlugin],
});Model and provider are not locked at factory creation —
per-session locking is the canonical pattern. Pass provider and
model when you mint the session:
const session = await runtime.sessions.create({
provider: { type: "anthropic" },
model: { id: "claude-sonnet-4-5" },
});The factory is a thin layer over the SessionRuntime
constructor; the advanced?: Partial<SessionRuntimeConfig>
pass-through reaches the full config surface.
SessionRuntime — the constructor
When you need persistent storage, checkpointing, or per-request user identity, drop to the constructor directly:
import { SessionRuntime } from "@pleach/core";
import { SupabaseAdapter } from "@pleach/core/sessions";
import { SupabaseSaver } from "@pleach/core/checkpointing";
const runtime = new SessionRuntime({
storage: new SupabaseAdapter({ client: supabase }),
checkpointer: new SupabaseSaver({ client: supabase }),
userId: "user_123",
});
const { id: sessionId } = await runtime.sessions.create();
for await (const event of runtime.executeMessage(sessionId, "Hello")) {
console.log(event.type, event);
}The full constructor surface — including plugin sets, the provider, the channel registry, the interrupt manager, and the extension loader — lives at SessionRuntime.
HarnessProvider — the React surface
If you want the hook surface without the route handler — for
example, embedding the runtime in an Electron app or a React
Native shell — use @pleach/core/react directly:
// app/layout.tsx
import { HarnessProvider, useHarness } from "@pleach/core/react";
function App() {
return (
<HarnessProvider userId="user_123">
<ChatComponent />
</HarnessProvider>
);
}
function ChatComponent() {
const { createSession, sendMessage, messages } = useHarness();
return (
<div>
<button onClick={() => createSession()}>New Session</button>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
</div>
);
}The provider owns one SessionRuntime instance per mount and
hands it down through context. See the
React page for the full hook + facade surface.
Mock mode for tests and CI
For local development without a database or provider account,
HARNESS_MOCK_MODE=true gates the mock tool executor — a
host-side utility (createMockToolExecutor / isMockModeEnabled)
that synthesizes tool results. It is the only thing that reads the
env var; the runtime constructors (new SessionRuntime,
createPleachRuntime) do not, so wiring mock mode into a runtime is
a manual step — construct the mock executor and pass it in, and pair
it with the in-memory MemoryAdapter + MemorySaver yourself. The
mock executor does not classify seams or write ledger rows; it only
produces synthesized tool outputs. (The default provider-decision
ledger is NoopProviderDecisionLedger, which drops all writes unless
a host supplies a real ledger.) A regression test (planned:
@pleach/eval; DIY today) composes these pieces itself.
Choosing between the three
| If you're building... | Reach for |
|---|---|
| A tutorial, eval harness, headless smoke script | createPleachRuntime |
| An HTTP chat handler + UI in five minutes | @pleach/core/quickstart |
| A multi-tenant SaaS, regulated deployment, custom transport | new SessionRuntime(...) directly |
| An existing React app composing the runtime | HarnessProvider + useHarness |
The four are the same substrate at different abstraction layers
— a createPleachRoute handler internally constructs a
SessionRuntime per request; createPleachRuntime is the
substrate factory the handler delegates to; HarnessProvider
wraps the same constructor for React.
Already on Anthropic Enterprise or OpenAI Enterprise?
Pleach runs underneath. AnthropicSdkProvider and the AI SDK's
OpenAI provider both accept your existing workspace or project
admin key; the runtime stamps tenantId on every audit row so
per-axis rollup runs inside the Workspace or Project you already
pay for. See
Migrating from Anthropic Enterprise
and
Migrating from OpenAI Enterprise
for the contract-composition walk-through.
Where to go next
SessionRuntime
Full constructor surface — storage, checkpointer, plugins, channels, extensions.
Providers
Wire a provider — AI SDK, Anthropic SDK, or your own AgentProvider implementation.
Turn lifecycle
What executeMessage does — the event stream, the four seams, the audit row.
Subpath exports
The full @pleach/* subpath surface, per package.
Landscape
Twenty agent-runtime capabilities, ordered by how differentiated @pleach/core actually is — what's structural to the audit row, what Pleach assembles from parts the market sells separately, and what's table stakes you shouldn't switch for.
Adoption paths
Two ways to adopt Pleach — brownfield (hook @pleach/observe into your existing agent loop) or greenfield (take @pleach/core as your substrate).