pleach
Cookbook

compliantChatbot

Chatbot with PII / PHI scrubbing at the message-content boundary and, when a Supabase client is supplied, at the durable EventLogWriter boundary. Four named profiles map to curated scrubber bundles from @pleach/compliance.

compliantChatbot wraps simpleChatbot with per-profile scrubbers. The profile field selects a curated bundle from @pleach/compliance/scrubbers; the bundle (plus any extraScrubbers) is applied to the user message string before ask() forwards it to the runtime. When supabase is supplied, the recipe also constructs an @pleach/core EventLogWriter configured with the same scrubbers and plumbs it through host.modules.eventLogWriter — so the persisted copy of every event row is scrubbed at the substrate boundary too.

Best fit: compliance-bound or regulated-industry deployments. Reach for this when HIPAA, GDPR, PCI-DSS, or SOC 2 requires the event log persisted or exported to be free of unredacted PII.

Quickstart

Message-content scrubbing only (v0.2 baseline):

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

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

Opt in to EventLogWriter-boundary scrubbing by passing a Supabase client:

import { createClient } from "@supabase/supabase-js";
import { compliantChatbot } from "@pleach/recipes/compliance";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!,
);

const bot = compliantChatbot({
  profile: "hipaa",
  supabase,
});

What it does

Profile → scrubber bundle mapping:

  • hipaaSsnUsScrubber + UsDriverLicenseScrubber + CreditCardScrubber
  • gdprSsnUsScrubber + CreditCardScrubber
  • pci-dssCreditCardScrubber
  • soc2SsnUsScrubber + CreditCardScrubber

On each ask(message) the resolved bundle (plus any extraScrubbers) runs against the message string before the recipe delegates to simpleChatbot.ask(). Matches are replaced with the scrubber's redaction marker.

When supabase is supplied, the recipe additionally constructs an EventLogWriter with onScrubberError: "fail-closed" and forwards it through createPleachRuntime({host: {modules: {eventLogWriter}}}). Every write() invocation runs the DefaultScrubberChain against the per-event-type allowlist (content.delta, interrupt.edited, audit.recorded, domain.ivy.job.*) before the row lands in the durable buffer.

Config reference

type ComplianceProfile = "hipaa" | "gdpr" | "pci-dss" | "soc2";

interface ComplianceScrubber {
  readonly id: string;
  scrub(
    input: string,
    ctx?: {
      readonly eventType?: string;
      readonly fieldPath?: readonly string[];
      readonly tenantId?: string;
    },
  ): {
    readonly redacted: string;
    readonly matchCount: number;
    readonly matchedScrubberIds?: readonly string[];
  };
}

interface CompliantChatbotConfig extends SimpleChatbotConfig {
  profile: ComplianceProfile;
  /** Run AFTER the profile bundle (last-wins on overlap). */
  extraScrubbers?: readonly ComplianceScrubber[];
  /** Opt-in to EventLogWriter-boundary scrubbing. */
  supabase?: { from: (table: string) => any };
}

Common gotchas

  • Assistant responses are not scrubbed. Scrubbing applies to the user message before the runtime sees it, and (with supabase) to event-log rows at write time. The model's reply itself is not post-processed. If you need to redact PII from generated text, run a scrubber pass in your UI layer or compose ComplianceRuntime from @pleach/compliance directly.
  • @pleach/compliance is an optional peer. The recipe loads scrubbers via require.resolve("@pleach/compliance") at construction. Install @pleach/compliance alongside @pleach/recipes when using this recipe.
  • The recipe stays storage-agnostic. It does NOT call createClient(...). You construct the Supabase client and pass it; the recipe uses only the .from(table) chain entry and runtime-validates deeper access inside @pleach/core's EventLogWriter.
  • Profile change requires a new instance. Profiles resolve at construction; there is no bot.setProfile().
  • extraScrubbers runs after the profile bundle. Last-wins on overlap. If a custom scrubber must run first, prepend by wrapping it as the new bundle and dropping the profile — or open a feature request.

See also

On this page