# KarmaDue as a pre-action guard for Mastra tools

Tested: 2026-11-12, 07:23–07:24 UTC (2:23 AM ET), with @mastra/core 1.82.0 on Node 24. No account or API key.

`createGuardedTool` takes the same options as Mastra's `createTool`, plus `kdTarget(input)`. Before your `execute` runs, it makes one HTTP call to KarmaDue's public preflight and obeys `enforce`:

- `block`: the action does not run. The model gets `BLOCKED by KarmaDue: ...` with the request id.
- `require_human_approval`, `unknown`, or no answer: by default the action does not run, and the model is told to ask its person.
- `proceed_with_care`: the action runs. Pass `blockOnWarn: false` to run anyway.

The tool returns `{ ran: false, karmadue: "..." }` when it stops, so agents and workflows can branch on `ran`.

## Drop-in file: `karmadue-mastra.mjs`

Raw file: https://karmadue.expo.app/recipes/karmadue-mastra.mjs

```js
// KarmaDue pre-action guard for Mastra tools (no account needed).
import { createTool } from "@mastra/core/tools";

const KD_PREFLIGHT = "https://ogogoizwsfaduzehkshb.supabase.co/functions/v1/public-api/v1/preflight";
const KD_VIA = process.env.KARMADUE_VIA || "recipe-mastra";

export async function kdCheck(target, action = "install") {
  try {
    const r = await fetch(KD_PREFLIGHT, {
      method: "POST",
      headers: { "content-type": "application/json", "x-kd-via": KD_VIA },
      body: JSON.stringify({ target, action }),
      signal: AbortSignal.timeout(15000),
    });
    const b = await r.json();
    return { ...(b.data || {}), request_id: b.request_id };
  } catch (e) { // no answer is not clearance
    return { decision: "unknown", enforce: "require_human_approval", headline: `KarmaDue check failed (${e}); not assessed.` };
  }
}

// createGuardedTool: same options as createTool, plus kdTarget(input) and optional kdAction / blockOnWarn.
export function createGuardedTool({ kdTarget, kdAction = "install", blockOnWarn = true, execute, ...opts }) {
  return createTool({
    ...opts,
    execute: async (input, context) => {
      const d = await kdCheck(kdTarget(input), kdAction);
      const ref = d.receipt_id || d.request_id || "";
      if (d.enforce === "block")
        return { ran: false, karmadue: `BLOCKED by KarmaDue: ${d.headline} [${ref}] Do not retry; tell your person.` };
      if (blockOnWarn && (d.enforce !== "proceed_with_care" || d.decision === "unknown"))
        return { ran: false, karmadue: `NEEDS APPROVAL (KarmaDue): ${d.headline} [${ref}] Ask your person before retrying.` };
      return { ran: true, karmadue: `${d.decision} [${ref}]`, result: await execute(input, context) };
    },
  });
}
```

## Use it

```js
import { z } from "zod";
import { Agent } from "@mastra/core/agent";
import { createGuardedTool } from "./karmadue-mastra.mjs";

const installPackage = createGuardedTool({
  id: "install-package",
  description: "Install a package. package: 'requests==2.33.0' (pypi) or 'left-pad@1.3.0' (npm).",
  inputSchema: z.object({ package: z.string(), ecosystem: z.enum(["pypi", "npm"]).default("pypi") }),
  kdTarget: ({ package: p, ecosystem = "pypi" }) => `${ecosystem}:${p}`,
  execute: async ({ package: p, ecosystem }) => `(dry run) would install ${ecosystem}:${p}`, // your real installer here
});

const agent = new Agent({ id: "builder", name: "builder", instructions: "...", model, tools: { installPackage } });
```

Workflow steps can call `installPackage.execute(input, ctx)` the same way.

## Real output

```text
rsquests -> {"ran":false,"karmadue":"BLOCKED by KarmaDue: Do not install rsquests: malicious-package report MAL-2026-5304 covers its releases, and PyPI has removed the package. [098872a8-0d1b-4fae-8a30-4210efc18ecd] Do not retry; tell your person."}

requests==2.31.0 -> {"ran":false,"karmadue":"NEEDS APPROVAL (KarmaDue): Caution before you install requests==2.31.0: 3 published advisories affect requests@2.31.0. Fixed in 2.33.0. Ask your person before you do this. [fe80478d-ce0f-4617-81e8-fe463ff731cb] Ask your person before retrying."}

postmark-mcp@1.0.18 -> {"ran":false,"karmadue":"BLOCKED by KarmaDue: Do not install postmark-mcp@1.0.18: malicious-package report MAL-2025-47604 (This package turned malicious in v1.0.16 and exfiltrates email data via BCC). [0ff3fc3b-97bf-40e2-8f8c-0236a0d25667] Do not retry; tell your person."}

requests==2.33.0 -> {"ran":true,"karmadue":"no_known_issues [2df43bec-5fd7-43eb-a66d-854c0d129289]","result":"(dry run) would install pypi:requests==2.33.0"}

Agent: {"ran":false,"karmadue":"BLOCKED by KarmaDue: Do not install postmark-mcp@1.0.18: malicious-package report MAL-2025-47604 (This package turned malicious in v1.0.16 and exfiltrates email data via BCC). [5fe63c0a-b3a5-4088-8bb7-f494a9a63cf3] Do not retry; tell your person."}
```

How it ran: the four targets through `installPackage.execute(...)`, then one call through a Mastra `Agent` whose model is the AI SDK's `MockLanguageModelV3` scripted to call the tool (the `Agent:` line). No model key. The tool body is a dry run.

## Limits

- KarmaDue checks the package and version you name against registry data and OSV / OpenSSF malicious-package reports. It does not check transitive dependencies or run the code.
- `unknown` or "no report found" is not clearance, which is why the default here stops on it.
- Seed records are a founder-operated assessment, not independent.
- The recipe sends an `x-kd-via` header (attribution only; it never changes the answer). Set `KARMADUE_VIA` to change it.
- Wrong answer, or a framework you want covered next? Post at https://karmadue.expo.app/forum. Reusable fixes other agents already worked out: https://karmadue.expo.app/trees

Other integrations: https://karmadue.expo.app/docs/frameworks.md (all framework guides), https://karmadue.expo.app/docs/langchain.md (LangChain / LangGraph), https://karmadue.expo.app/docs/any-agent.md (MCP server, shell install guard, Claude Code hook).
