pleach
Cookbook

simpleChatbot

One-line factory over @pleach/core for the minimal conversational agent. No sibling peers — supply your own provider adapter and the recipe handles session lifecycle, conversational state, and stream collection.

simpleChatbot is the smallest recipe in the package. It composes only @pleach/core and returns a Chatbot wrapper around a real SessionRuntime. The factory does not bundle a provider — you register an OrchestratorAdapter ctor once at app boot and the recipe forwards your orchestratorConfig through.

Best fit: a SaaS adding chat. Reach for this when you want a working chatbot in under ten lines and don't yet need retrieval, observability, or compliance scrubbing.

Quickstart

import { simpleChatbot } from "@pleach/recipes/chatbot";
import { setOrchestratorAdapterCtor } from "@pleach/core/runtime";
import { MyOrchestratorAdapter } from "./my-provider";

// Register the OrchestratorAdapter ctor once at app boot.
setOrchestratorAdapterCtor(MyOrchestratorAdapter);

const bot = simpleChatbot({
  systemPrompt: "You are a helpful assistant.",
  orchestratorConfig: {
    provider: "openai",
    model: "gpt-4o-mini",
    apiKey: process.env.OPENAI_API_KEY!,
  },
});

console.log(await bot.ask("hello"));
console.log(await bot.ask("what did I just say?"));

A single bot instance holds the session across ask() calls, so the second turn sees the first turn's history. Call await bot.reset() (or its alias await bot.newSession()) to discard the memoized session id; the next ask() lazily creates a fresh one.

What it does

The factory constructs a SessionRuntime via createPleachRuntime(...) and stores the orchestratorConfig on host.strategies.orchestratorConfig. On the first ask(), the runtime walks the registered adapter ctor and binds your provider. Each subsequent ask() reuses the same session id, so conversation state accumulates in the underlying event log.

ask() returns the final assistant text. It collects the message.delta and message.complete events from runtime.executeMessage() and concatenates the deltas. Streaming consumers should reach into bot.runtime directly.

Config reference

interface SimpleChatbotConfig {
  /** Optional system prompt prepended to every conversation. */
  systemPrompt?: string;
  /**
   * Provider/orchestrator config forwarded to the underlying
   * SessionRuntime. Wires the LLM provider (model id, API key,
   * etc.). When omitted, ask() returns the substrate placeholder.
   */
  orchestratorConfig?: Record<string, unknown>;
  // Plus the standard CreatePleachRuntimeConfig fields
  // (host, plugins, storage, etc.) from @pleach/core.
}

interface Chatbot {
  readonly runtime: SessionRuntime;
  ask(message: string): Promise<string>;
  newSession(): Promise<void>;
  reset(): Promise<void>;
}

Common gotchas

  • setOrchestratorAdapterCtor runs once per process. It registers a module-level ctor inside @pleach/core/runtime. Call it at app boot, before any simpleChatbot({orchestratorConfig: ...}). Calling it again with a different ctor is a no-op after the first registration in v0.2.
  • Without orchestratorConfig, ask() returns a placeholder. The string is "This is a placeholder response from the harness runtime." This is the documented no-provider contract — useful for shape tests, not for production.
  • ChatStreamError carries the original failure on .cause. If the stream emits {type: "error"} or throws, ask() rejects with ChatStreamError. Branch on error.cause to distinguish provider errors from abort signals from network timeouts.
  • Subpath import is load-bearing. Import from @pleach/recipes/chatbot, not the root barrel. The factory references createPleachRuntime from @pleach/core/runtime (the subpath the setter is also registered against). The top-level @pleach/core ESM bundle does not currently re-export the setter.

See also

On this page