OGuardAI
Integrations

Mastra

Protect Mastra agents with OGuardAI so PII is tokenized before the model and restored in the output

The OGuardAI TypeScript SDK ships an optional Mastra integration that keeps customer PII out of a Mastra agent. The agent and the model only ever reason on the tokenized safe_text; the sealed session never enters the agent. There is no hard dependency on @mastra/core: the Mastra message and processor shapes are declared structurally, so @oguardai/sdk builds and imports without Mastra installed, and the returned processor is structurally compatible with Mastra's Processor.

Install

npm install @oguardai/sdk @mastra/core

Two entry points

Both keep the model in the token domain, but they place the trust boundary differently.

  • guardMastraGenerate is the recommended, boundary-safe path. Transform and rehydrate run OUTSIDE the agent, so no Mastra tracer, telemetry span, callback, or output processor ever observes the raw input or the restored answer. Prefer this whenever a PII-exporting tracer is attached to the agent.
  • guardMastraAgent returns a Processor you register on the agent itself. Tokenization still happens before the model runs, but restoration happens INSIDE the framework, in processOutputResult. A Mastra tracer or output processor attached ABOVE this guard's output boundary WILL see the restored answer. Use it only when the agent is not exported to a PII-sensitive destination. guardMastra is an alias of guardMastraAgent.

guardMastraGenerate(client, agent, input, options?) transforms input to tokens, runs agent.generate(safeText, generateOptions) on the SAFE text, and rehydrates the model's output outside the agent. It fails closed if the transform yields no session_state. It mirrors LangChain's guardedInvoke.

import { OGuardAIClient } from "@oguardai/sdk";
import { guardMastraGenerate } from "@oguardai/sdk/integrations/mastra";
import { Agent } from "@mastra/core/agent";

const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });

const agent = new Agent({
  id: "support-copilot",
  name: "Support Copilot",
  instructions:
    "Antworte formell auf Deutsch. Uebernimm jeden {{type:id:cap}}-Platzhalter Zeichen fuer Zeichen.",
  model: "openai/gpt-4o-mini",
});

const answer = await guardMastraGenerate(
  client,
  agent,
  "Email Julia Schneider at julia@firma.example",
  { policy: "german-support", language: "de", outputChannel: "customer_email" },
);

The agent, its tracer, and the model only ever see Email {{person:p_001:ad4f97591c16}} at {{email:e_001:1bcaef1a4aff}}. The returned answer has the real values restored per the customer_email channel.

agent is any object with a compatible generate(input, options?) => { text }; pass a real Mastra Agent, or your own object, or an outputExtractor for a different result shape. A streaming agent must be buffered to text by the caller before rehydration.

Options

GuardMastraGenerateOptions:

OptionDefaultEffect
policyserver defaultOGuardAI policy name.
languageserver defaultDetection language hint.
outputChanneluser_outputOutput channel whose restore rules apply.
restoreModefullfull, partial, masked, formatted, abstract, or none.
generateOptions(none)Forwarded verbatim as the second argument to agent.generate.
outputExtractorresult.textSelects the string to rehydrate from the agent result.

In-framework processor path

guardMastraAgent(client, options?) returns ONE processor object implementing both processInput (tokenize) and processOutputResult (rehydrate). Register the SAME object in BOTH the agent's inputProcessors and outputProcessors, so a single per-request session bridges tokenize and rehydrate.

import { guardMastraAgent } from "@oguardai/sdk/integrations/mastra";
import { Agent } from "@mastra/core/agent";

const guard = guardMastraAgent(client, {
  policy: "german-support",
  language: "de",
  outputChannel: "customer_email",
});

const agent = new Agent({
  id: "support-copilot",
  name: "Support Copilot",
  instructions: "Antworte formell auf Deutsch. Uebernimm jeden Platzhalter unveraendert.",
  model: "openai/gpt-4o-mini",
  inputProcessors: [guard],
  outputProcessors: [guard],
});

const result = await agent.generate("Email Julia Schneider at julia@firma.example");

processInput tokenizes the user text before the model sees it, and processOutputResult restores the reply. Because restoration is inside the framework, a tracer or output processor attached above this guard's output boundary sees the restored answer; when that matters, use guardMastraGenerate.

Options

GuardMastraOptions:

OptionDefaultEffect
policyserver defaultOGuardAI policy name.
languageserver defaultDetection language hint.
outputChanneluser_outputOutput channel whose restore rules apply.
restoreModefullRestore mode for processOutputResult.
idoguardai-guardProcessor id/name reported to Mastra.
inputRoles["user"]Message roles whose text is tokenized on input.
outputRoles["assistant"]Message roles whose text is rehydrated on output.

String-only messages

The processor supports text only. A message whose content is a plain string, or whose content.parts are all type: "text", is protected; multiple text parts on one message are joined and protected as a single text. A message carrying any non-text, multimodal, or file part is REJECTED (fail closed), since the adapter cannot scan raw strings inside such parts. Strip or separately protect such parts before the agent.

What the model and tracer see

In the boundary-safe path, the only string handed to agent.generate is the tokenized safe_text; transform and rehydrate run outside the agent, so no Mastra tracer, telemetry span, callback, or output processor ever observes a raw or restored value.

In the processor path, the model still sees only tokens (processInput tokenizes before the model runs), but restoration happens inside processOutputResult, so a tracer attached above the guard's output boundary sees the restored answer. That is the one difference between the two paths, and the reason guardMastraGenerate is the default recommendation.

How it works

  1. The integration calls /v1/transform on the input text, replacing PII with semantic tokens and returning a sealed session_state.
  2. The Mastra agent runs on the tokenized safe_text.
  3. The integration calls /v1/rehydrate on the agent output using that session, restoring the real values per the output channel and restore mode.

Fail-closed behavior is explicit:

  • If the transform returns no session_state, guardMastraGenerate never runs the agent, and the processor's processInput throws before the model runs.
  • In the processor path, each request is keyed on Mastra's per-request carrier (messageList, then state, then requestContext). If Mastra provides none of them, both methods throw rather than share a global session slot across concurrent requests.
  • The session is consumed once: processOutputResult deletes it after use, so a replayed or observed output cannot re-restore against a stale session. If nothing was tokenized for a request, the output is returned unchanged rather than fabricating a restoration.