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/coreTwo entry points
Both keep the model in the token domain, but they place the trust boundary differently.
guardMastraGenerateis 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.guardMastraAgentreturns aProcessoryou register on the agent itself. Tokenization still happens before the model runs, but restoration happens INSIDE the framework, inprocessOutputResult. 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.guardMastrais an alias ofguardMastraAgent.
Boundary-safe path (recommended)
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:
| Option | Default | Effect |
|---|---|---|
policy | server default | OGuardAI policy name. |
language | server default | Detection language hint. |
outputChannel | user_output | Output channel whose restore rules apply. |
restoreMode | full | full, partial, masked, formatted, abstract, or none. |
generateOptions | (none) | Forwarded verbatim as the second argument to agent.generate. |
outputExtractor | result.text | Selects 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:
| Option | Default | Effect |
|---|---|---|
policy | server default | OGuardAI policy name. |
language | server default | Detection language hint. |
outputChannel | user_output | Output channel whose restore rules apply. |
restoreMode | full | Restore mode for processOutputResult. |
id | oguardai-guard | Processor 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
- The integration calls
/v1/transformon the input text, replacing PII with semantic tokens and returning a sealedsession_state. - The Mastra agent runs on the tokenized
safe_text. - The integration calls
/v1/rehydrateon 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,guardMastraGeneratenever runs the agent, and the processor'sprocessInputthrows before the model runs. - In the processor path, each request is keyed on Mastra's per-request carrier
(
messageList, thenstate, thenrequestContext). If Mastra provides none of them, both methods throw rather than share a global session slot across concurrent requests. - The session is consumed once:
processOutputResultdeletes 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.