Skip to content
pleach
Get Started

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 → Response shape.
  • 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.

Driving a turn in tests and CI

For local development without a database or a provider account, reach for @pleach/core/testing. runScriptedTurn drives one real SessionRuntime turn against a scripted provider stream — no network, no browser, and no provider credentials:

import { runScriptedTurn, scriptText } from "@pleach/core/testing";

const result = await runScriptedTurn({
  prompt: "hello",
  script: scriptText("hello world"),
});

result.text;      // "hello world"
result.toolCalls; // tool calls the turn issued
result.events;    // the raw StreamEvent sequence

Omit runtime and the call builds and destroys a bare createPleachRuntime() for you; pass runtime to drive one you configured yourself, so your storage, plugins and config stay in the loop while the provider is the only scripted part. Storage needs no wiring either — a zero-config runtime defaults to the in-memory MemoryAdapter + MemorySaver. (The default provider-decision ledger is NoopProviderDecisionLedger, which drops all writes unless a host supplies a real ledger.)

Choosing between the three

If you're building...Reach for
A tutorial, eval harness, headless smoke scriptcreatePleachRuntime
An HTTP chat handler + UI in five minutes@pleach/core/quickstart
A multi-tenant SaaS, regulated deployment, custom transportnew SessionRuntime(...) directly
An existing React app composing the runtimeHarnessProvider + 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