raxit
Frameworks

Mastra

Governing Mastra agents in TypeScript, zero-code — including MODIFY.

Mastra's own tool conversion point — Agent.prototype.convertTools (verified against @mastra/core@1.50.1; not re-exported from the package root, resolved from @mastra/core/agent) — is the single point where every regular Agent's tool sources become { toolName: CoreTool }, each with its own execute. raxit run's preload post-wraps that point, so every tool call resolves to permit / deny / MODIFY before it executes, with no governance code in the tool definitions. TypeScript only.

Run it yourself

For your own Mastra agent, install the SDK and launch it under raxit run:

npm install @raxitlabs/raxit
raxit run -- node agent.mjs

Run the same file without raxit run and it runs ungoverned — governance is a deploy-time choice, not a code change.

How it works

Tools are ordinary createTool() definitions with no governance code:

getting_started.mjs
// imports NOTHING from raxit
import { createTool } from "@mastra/core/tools";
import { Agent } from "@mastra/core/agent";

const send_email = createTool({
  id: "send_email",
  inputSchema: z.object({ to: z.string(), body: z.string() }),
  execute: async ({ to, body }) => `sent to ${to}: ${body}`,
});

const agent = new Agent({ name: "mastra-bot", model, tools: { send_email } });
await agent.generate("send the receipt");
.raxit/security.yaml
apiVersion: raxit.security/v1
agent: mastra-bot
default: block
rules:
  - effect: allow
    resource: fetch_docs
  - resource: send_email          # MODIFY: redact PII in the body before sending
    modify: { redact: [body] }
  # shell_exec is intentionally UNLISTED -> default-deny (deny.default_or_forbid).

MODIFY is on the autopatch path here — unlike the Vercel AI SDK's telemetry hook (which falls back to governedTool() for MODIFY), Mastra's convertTools point passes input as the first parameter to each converted tool's execute, so governance rewrites it before the body runs — no explicit wrap needed.

Real captured output from a run:

raxit autopatch full demo · raw Mastra agent · daemon=http://127.0.0.1:8427

  | raw Mastra agent · governance=ON (raxit run)
  |
  | [fetch_docs] OK -> received={"query":"refund policy"}
  | [shell_exec] BLOCKED -> body never ran
  | [send_email] MODIFIED -> received={"to":"a@b.c","body":"charge card [REDACTED:pii_credit_card]"}

PASS  governance active (RAXIT_AUTOLOAD propagated by raxit run)
PASS  fetch_docs -> permit (ran)
PASS  deny.default_or_forbid: shell_exec -> deny (body never ran)
PASS  modify.policy: send_email -> ran on redacted body (PII stripped)
  audit.verify -> ok=true signatures_verified=true signed_count=5 count=5
PASS  audit.verify: signed Decision Provenance chain verified green

ALL PASS

Mastra's generation loop catches a denied tool's thrown RaxitDenied as a tool-error output to the model, not a top-level exception a raxit-unaware agent can inspect directly — the agent can only observe "body never ran" from its own call log. The demo's harness (entitled to be raxit-aware) additionally calls gov.decide(...) directly to name the reason codes explicitly.

Honest coverage scope: the regular Agent.generate()/stream() path only

Not covered by this autopatch, and needing an explicit governedTool() / wrapped-createTool instead: the durable agent's direct execution sites, direct makeCoreTool() users, direct tool.execute() calls outside the agent loop, Mastra's internal AI-SDK-compat executeTools/runToolsTransformation family, and clientTools (whose execute is stripped server-side by design, so there is no local body to govern). convertTools is internal-marked, so drift risk is high — raxit's fail-closed startup self-check (it raises ChokepointDriftError on drift and aborts raxit run rather than running the agent ungoverned) is not optional in production.

What got denied/modified and why

  • deny.default_or_forbidshell_exec appears nowhere in the policy, so it falls to default: block. The tool body never ran.
  • modify.policysend_email matched the modify: { redact: [body] } rule; the tool ran, but on the engine's rewritten arguments — the raw body (containing card PII) was redacted to [REDACTED:pii_credit_card] before the tool ever saw it.

Every decision — permit, deny, and modify — lands in one hash-chained, Ed25519-signed Decision Provenance log, verified green with client.audit.verify({ signatures: true, pubkeys: [...] }). See Audit and Decisions for the full permit / defer / deny / modify vocabulary and reason-code catalog.

  • Vercel AI SDK — a TS autopatch case where MODIFY is not supported on the autopatch path (must fall back to governedTool()) — the contrast case for this page.
  • LangChain.js / LangGraph.js — MODIFY at the BaseTool layer instead of a framework-conversion point.
  • MCP servers — a per-instance (opt-in) autopatch hook, proving autopatch breadth beyond process-wide prototype patching.

On this page