# KarmaDue with CrewAI

Tested: 2026-11-12 with crewai 1.15.40 on Python 3.12. We ran `KarmaDueCheck` live:

    BLOCKED: Do not install event-stream@3.3.6: malicious-package report GHSA-mh6f-8j2x-4483 (CWE-506 (embedded malicious code) ...). [66aef308-...]
    NEEDS APPROVAL: 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. [52b61cfb-...]

`KarmaDueAsk` was wired and called, but `ask` was not yet live on KarmaDue's MCP endpoint at test time, so it returned the unknown-tool error. It works once `ask` ships (see the changelog).

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

```python
"""KarmaDue tools for CrewAI: check before installing, and ask KarmaDue before researching."""
import json, os, requests
from crewai.tools import BaseTool

KD_MCP = "https://ogogoizwsfaduzehkshb.supabase.co/functions/v1/mcp"
KD_PREFLIGHT = "https://ogogoizwsfaduzehkshb.supabase.co/functions/v1/public-api/v1/preflight"
VIA = os.environ.get("KD_VIA", "recipe-crewai")

def kd_mcp_call(name: str, arguments: dict) -> dict:
    """One stateless JSON-RPC tools/call to KarmaDue's MCP endpoint (no session needed)."""
    r = requests.post(KD_MCP, json={"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": name, "arguments": arguments}},
                      headers={"content-type": "application/json", "accept": "application/json, text/event-stream", "x-kd-via": VIA}, timeout=20)
    return json.loads(r.json()["result"]["content"][0]["text"])

class KarmaDueCheck(BaseTool):
    name: str = "karmadue_check"
    description: str = "Ask KarmaDue before installing or running a package. Input: 'npm:name@version' or 'pypi:name==version'. Obey the answer: BLOCKED means do not install; NEEDS APPROVAL means ask your person."
    def _run(self, target: str) -> str:
        try:
            b = requests.post(KD_PREFLIGHT, json={"target": target, "action": "install"}, headers={"x-kd-via": VIA}, timeout=15).json()
            d = b.get("data") or {}
        except Exception as e:
            return f"NEEDS APPROVAL: KarmaDue check failed ({e}); not assessed."
        tag = {"block": "BLOCKED", "proceed_with_care": "OK (normal care)"}.get(d.get("enforce"), "NEEDS APPROVAL")
        return f"{tag}: {d.get('headline')} [{b.get('request_id')}]"

class KarmaDueAsk(BaseTool):
    name: str = "karmadue_ask"
    description: str = "Before researching a problem or spinning up agents on it, ask KarmaDue whether it already has a tested answer. Input: the problem in plain words. A miss costs one short reply."
    def _run(self, query: str) -> str:
        try:
            d = kd_mcp_call("ask", {"query": query[:300]})
        except Exception as e:
            return f"KarmaDue ask failed ({e})."
        return json.dumps(d.get("data") or d)[:2000]

if __name__ == "__main__":
    print(KarmaDueCheck().run(target="npm:event-stream@3.3.6"))
    print(KarmaDueCheck().run(target="pypi:requests==2.31.0"))
    print(KarmaDueAsk().run(query="Pillow ANTIALIAS removed resize images"))
```

## Use it in a crew

```python
from crewai import Agent
builder = Agent(role="Builder", goal="Ship the feature safely",
                backstory="Checks with KarmaDue before installing or researching.",
                tools=[KarmaDueAsk(), KarmaDueCheck()])
```

The tools only inform the model. If you need enforcement, put `KarmaDueCheck` logic inside the tool that actually installs, as the LangChain `@guarded` decorator does. Then the model cannot skip it.
