# KarmaDue as a pre-action guard for Google ADK

Tested: 2026-11-12, 07:23–07:24 UTC (2:23 AM ET), with google-adk 2.14.0 on Python 3.13. No account or API key.

ADK's `before_tool_callback` can skip a tool by returning a dict. `karmadue_before_tool` checks the listed tools with KarmaDue 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.

When it stops a call, the model gets `{"status": "not_run", "karmadue": "..."}` as the tool result.

## Drop-in file: `karmadue_adk.py`

Raw file: https://karmadue.expo.app/recipes/karmadue_adk.py

```python
"""KarmaDue pre-action guard for Google ADK (a before_tool_callback; no account needed)."""
import json, os, urllib.request

KD_PREFLIGHT = "https://ogogoizwsfaduzehkshb.supabase.co/functions/v1/public-api/v1/preflight"
KD_VIA = os.environ.get("KARMADUE_VIA", "recipe-google-adk")

def kd_check(target: str, action: str = "install") -> dict:
    """Ask KarmaDue before acting. target: 'npm:name@ver', 'pypi:name==ver', or a URL."""
    req = urllib.request.Request(KD_PREFLIGHT, data=json.dumps({"target": target, "action": action}).encode(),
                                 headers={"content-type": "application/json", "x-kd-via": KD_VIA})
    try:
        with urllib.request.urlopen(req, timeout=15) as r:
            body = json.load(r)
        return {**(body.get("data") or {}), "request_id": body.get("request_id")}
    except Exception as e:  # no answer is not clearance
        return {"decision": "unknown", "enforce": "require_human_approval",
                "headline": f"KarmaDue check failed ({e}); not assessed."}

def kd_gate(target: str, action: str = "install", block_on_warn: bool = True):
    """Returns (allowed, message). block -> never allowed; warn/unknown/no answer -> not allowed unless block_on_warn=False."""
    d = kd_check(target, action)
    enforce, ref = d.get("enforce"), d.get("receipt_id") or d.get("request_id") or ""
    if enforce == "block":
        return False, f"BLOCKED by KarmaDue: {d.get('headline')} [{ref}] Do not retry; tell your person."
    # decision "unknown" means KarmaDue had no evidence: ask, even if enforce says proceed_with_care.
    unknown = d.get("decision") == "unknown"
    if (enforce != "proceed_with_care" or unknown) and block_on_warn:
        return False, f"NEEDS APPROVAL (KarmaDue): {d.get('headline')} [{ref}] Ask your person before retrying."
    return True, ("" if enforce == "proceed_with_care" and not unknown else f"KarmaDue: {d.get('headline')} [{ref}]\n")

def pkg_target(package: str, ecosystem: str = "pypi") -> str:
    return f"{'npm' if ecosystem == 'npm' else 'pypi'}:{package}"

# ---- Google ADK adapter: before_tool_callback. Returning a dict skips the tool. ----
KD_TARGETS = {
    "install_package": lambda a: pkg_target(a["package"], a.get("ecosystem", "pypi")),
}

def karmadue_before_tool(tool, args, tool_context, targets=KD_TARGETS, action="install", block_on_warn=True):
    to_target = targets.get(tool.name)
    if not to_target:
        return None
    ok, msg = kd_gate(to_target(args), action, block_on_warn)
    if not ok:
        return {"status": "not_run", "karmadue": msg}
    return None  # run the tool
```

Dependencies: none beyond the framework (the check uses Python's standard library `urllib`).

## Use it

```python
from google.adk.agents import LlmAgent
from karmadue_adk import karmadue_before_tool, KD_TARGETS

agent = LlmAgent(name="builder", model="gemini-2.5-flash", tools=[install_package],
                 before_tool_callback=karmadue_before_tool)
# KD_TARGETS["connect_mcp"] = lambda a: a["url"]   # add one line per tool that acts
```

## Real output

```text
rsquests -> {'status': 'not_run', 'karmadue': 'BLOCKED by KarmaDue: Do not install rsquests: malicious-package report MAL-2026-5304 covers its releases, and PyPI has removed the package. [af46e55e-03ad-4fd9-8687-f86628783126] Do not retry; tell your person.'}

requests==2.31.0 -> {'status': 'not_run', '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. [27f7a406-64bb-41a8-a5af-790ee0b84565] Ask your person before retrying.'}

postmark-mcp@1.0.18 -> {'status': 'not_run', '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). [7d9eb7d3-68b7-4ac0-9638-9ad38a4625e2] Do not retry; tell your person.'}

requests==2.33.0 -> {'status': 'ok', 'result': '(dry run) would install pypi:requests==2.33.0'}
```

How it ran: an `LlmAgent` run by ADK's `InMemoryRunner`, with a small `BaseLlm` subclass standing in for Gemini that asks for one `install_package` call per target; the lines are the function responses ADK recorded. 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).
