# @pleach/recipes (/docs/recipes-pleach-recipes)



`@pleach/recipes` is the use-case-targeted composition layer over
`@pleach/core`. Each subpath export is a one-line factory that
returns a wrapper around a real
[`SessionRuntime`](/docs/session-runtime). Reach into `bot.runtime`
when you need the full primitive surface; reach for the recipe
when the boilerplate is what's in the way.

Each recipe lives at its own subpath so consumers only pay the
import cost for what they use.

```bash
npm install @pleach/recipes @pleach/core
```

Optional peers — install only the ones a recipe needs:

```bash
npm install @pleach/observe       # for observability + coding-agent recipes
npm install @pleach/compliance    # for compliance recipe (v0.2)
npm install @pleach/coding-agent  # for instrumentedCodingAgent
npm install @pleach/sandbox       # for instrumentedCodingAgent
```

## simpleChatbot [#simplechatbot]

A minimal conversational agent. Composes only `@pleach/core`; no
sibling peers required. The factory lazily creates a session on
first `ask()` and reuses it on subsequent calls so a single
`simpleChatbot` instance holds conversational state.

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

// Register the OrchestratorAdapter ctor once at app boot. The recipes
// package does NOT bundle a provider — supply your own (OpenAI,
// Anthropic, OpenRouter, BYOK, etc.).
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?"));
```

When `orchestratorConfig` is omitted, `ask()` resolves to the
substrate placeholder (`"This is a placeholder response from the
harness runtime."`). Supplying `orchestratorConfig` routes the call
through your registered `OrchestratorAdapter`.

`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.

A single `bot` instance holds the session across `ask()` calls.
Call `await bot.reset()` (or its alias `await bot.newSession()`) to
discard the memoized session id; the next `ask()` lazily creates a
fresh session via `runtime.createSession()`. If the underlying
stream emits an `{ type: "error" }` event or throws, `ask()`
rejects with a `ChatStreamError` and preserves the original
failure on `error.cause` — branch on `cause` to distinguish
provider errors from abort signals from network timeouts. The same
session API and error type are inherited by `ragChatbot`,
`observableChatbot`, and `compliantChatbot`.

Reach for this when you want a working chatbot in under ten
lines and don't yet need retrieval, observability, or compliance.

## ragChatbot [#ragchatbot]

A chatbot plus retrieval stage. The consumer supplies the
`Retriever` — the recipe is storage-agnostic on purpose; it does
not bind a vector store.

```ts
import { ragChatbot } from "@pleach/recipes/rag";

const bot = ragChatbot({
  retriever: async (query, opts) =>
    myVectorDb.search(query, { topK: opts?.topK ?? 4 }),
});
console.log(await bot.ask("what's our refund policy?"));
```

Per turn, `ask()` calls the retriever, prepends the top-K chunks
to the user message as context with stable `[N]` indices for
citation, and delegates to the underlying `simpleChatbot`. When
the retriever returns no chunks or throws, the message passes
through unmodified — the bot stays useful if retrieval is down.

Reach for this when answers must be grounded in your documents
and you already have a vector store or full-text index. Bring
your own retrieval; the recipe stays out of that decision.

## observableChatbot [#observablechatbot]

A chatbot wrapped with `@pleach/observe` sub-agent attribution.
Every `recordCall(...)` issued during `ask()` inherits the outer
`subagent` path — no recorder argument threaded through call
sites.

```ts
import { init } from "@pleach/observe";
import { memory } from "@pleach/observe/destinations";
import { observableChatbot } from "@pleach/recipes/observability";

const dest = memory();
init({ destination: dest });

const bot = observableChatbot({
  serviceName: "my-app-chatbot",
});
await bot.ask("hello");
console.log(dest.rows); // rows tagged subagent: "my-app-chatbot"
```

`@pleach/observe` is an optional peer. If it's not installed,
`ask()` still works as a plain chatbot — instrumentation just
doesn't fire. Set `serviceName: false` to disable the ALS scope
when composing inside a buyer's outer scope.

Reach for this when traces, prompt/completion logs, and cost
attribution are required on every turn and the boilerplate of
threading the recorder through call sites isn't earning its
keep.

## compliantChatbot [#compliantchatbot]

A chatbot wrapped with PII / PHI scrubbing at the message-content
boundary. As of v0.2 the `profile` field routes to a curated bundle
from `@pleach/compliance/scrubbers` and applies it (plus any
`extraScrubbers`) to the user message string before `ask()` forwards
it to the runtime.

```ts
import { compliantChatbot } from "@pleach/recipes/compliance";

const bot = compliantChatbot({ profile: "hipaa" });
await bot.ask("patient called about prescription refill");
```

Profile → scrubber bundles:

* `hipaa` → `SsnUsScrubber` + `UsDriverLicenseScrubber` + `CreditCardScrubber`
* `gdpr` → `SsnUsScrubber` + `CreditCardScrubber`
* `pci-dss` → `CreditCardScrubber`
* `soc2` → `SsnUsScrubber` + `CreditCardScrubber`

**Scope-limit (v0.2).** Scrubbing is applied at the message-content
boundary, NOT at the durable `EventLogWriter` boundary (next
iteration). Assistant responses are not currently scrubbed.
EventLogWriter-boundary scrubbing is substrate-ready —
`@pleach/core`'s `EventLogWriter` already accepts a
`scrubbers: readonly Scrubber[]` constructor option with a
`DefaultScrubberChain` enforced at every `write()`, and the
`host.modules.eventLogWriter` parameter on `createPleachRuntime`
plumbs a custom writer through. Consumers needing full hash-chain
attestation + `attestRun` today should construct `ComplianceRuntime`
directly from `@pleach/compliance`.

Reach for this when HIPAA, GDPR, PCI-DSS, or SOC 2 requires the
event log persisted or exported to be free of unredacted PII.

## instrumentedCodingAgent [#instrumentedcodingagent]

A coding agent whose `executeStep(...)` calls automatically
inherit a `@pleach/observe` sub-agent attribution path. Composes
`@pleach/coding-agent`, `@pleach/sandbox`, and `@pleach/observe`.

```ts
import { init } from "@pleach/observe";
import { memory } from "@pleach/observe/destinations";
import { createInMemorySandboxProvider } from "@pleach/sandbox/testing";
import { instrumentedCodingAgent } from "@pleach/recipes/coding-agent";

const dest = memory();
init({ destination: dest });

const agent = instrumentedCodingAgent({
  sandboxProvider: createInMemorySandboxProvider({ execHandlers: new Map() }),
  subagent: "coding-agent",
});

await agent.start();
try {
  const result = await agent.executeStep({
    userMessage: "fix the failing test in lib/parser.ts",
    stepId: "step-1",
  });
  console.log(result.ok, dest.rows); // rows tagged subagent: "coding-agent"
} finally {
  await agent.stop();
}
```

Compose with outer scopes for nested attribution:

```ts
import { subagent } from "@pleach/observe";

await subagent("tenant-abc").run(async () => {
  await agent.executeStep({...}); // path: ["tenant-abc", "coding-agent"]
});
```

`init({ destination })` from `@pleach/observe` must be called
once at process start before any `executeStep`; the SDK is
singleton-per-process.

Reach for this when each turn decomposes into
`planner → executor → critic` sub-roles and every LLM call needs
to surface on the dashboard tagged by the sub-role that issued
it.

## Composing recipes by hand [#composing-recipes-by-hand]

Every recipe returns a `Chatbot` with a `runtime` escape hatch:

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

When you need a shape the recipe doesn't cover — multi-session
state, streaming to a UI, custom interrupt handling — drop down
to `bot.runtime` and use [`executeMessage`](/docs/session-runtime)
directly. Recipes are convenience over `@pleach/core`; nothing
they wrap is hidden.
