AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
正在加载完整 README。 以下为已保存的摘录。
You deployed a Python AI agent.
Do you know every function it can call that writes to a database, sends an email,
charges a card, or deletes data — and which ones have zero checks?
diplomat-agent runs a static AST scan and tells you exactly that.
Zero dependencies. 2 seconds on a 1,000-file repo.
In a web app, a human clicks a button. The UI has validation, confirmation
dialogs, rate limits per session.
In an agent, an LLM decides which functions to call, with what arguments,
how many times. It doesn't know your business rules. It can loop, hallucinate
arguments, or get prompt-injected.查看完整 README ↗You deployed a Python AI agent. Do you know every function it can call that writes to a database, sends an email, charges a card, or deletes data — and which ones have zero checks?
diplomat-agent runs a static AST scan and tells you exactly that.
Zero dependencies. 2 seconds on a 1,000-file repo.
pip install diplomat-agent
diplomat-agent scan .
diplomat-agent — governance scan
Scanned: ./my-agent
Tool calls with side effects: 12
⚠ process_refund(amount, customer_id)
Write protection: NONE
Rate limit: NONE
→ stripe.Refund.create() with no amount limit
Governance: ❌ UNGUARDED
⚠ delete_user_data(user_id)
Confirmation step: NONE
Batch protection: NONE
→ session.delete() with no confirmation
Governance: ❌ UNGUARDED
✓ update_order(order_id)
Governance: ✅ GUARDED
────────────────────────────────────────────
RESULT: 8 unguarded · 3 partial · 1 guarded (12 total)
In a web app, a human clicks a button. The UI has validation, confirmation dialogs, rate limits per session.
In an agent, an LLM decides which functions to call, with what arguments, how many times. It doesn't know your business rules. It can loop, hallucinate arguments, or get prompt-injected.
Without guards in the code, there's nothing between the LLM's decision and the real-world consequence.
We scanned 16 open-source agent repos. 70.9% of analyzable tool calls have no guard, with a 1.7% opacity rate — measured with inter-procedural tracing across 7,552 tool calls.
40+ patterns across 8 categories:
| Category | Examples |
|---|---|
| Database writes | session.commit(), .save(), .create(), .update() |
| Database deletes | session.delete(), .remove(), DELETE FROM |
| HTTP writes | requests.post(), httpx.put(), client.patch() |
| Payments | stripe.Charge.create(), stripe.Refund.create() |
| Email / messaging | smtp.sendmail(), ses.send_email(), slack.chat_postMessage() |
| Agent invocations | graph.ainvoke(), agent.execute(), Runner.run_sync() |
| Destructive commands | subprocess.run(), exec(), eval() |
| Publish / upload | s3.put_object(), client.publish() |
What counts as a guard: input validation, rate limiting, auth checks, confirmation steps, idempotency keys, retry bounds. Full list →
- name: Diplomat governance scan
run: |
pip install diplomat-agent
diplomat-agent scan . --fail-on-unchecked
Works in your IDE with zero extension to install:
| IDE | How | Setup |
|---|---|---|
| Copilot Chat (VS Code, Cursor, Windsurf) | Select "Diplomat Reviewer" in agent dropdown | Copy .github/agents/diplomat-reviewer.agent.md |
| Claude Code | Ask "scan for unguarded tool calls" | AGENTS.md at repo root (included) |
| Cursor (native) | Auto-activates on Python files | Copy .cursor/rules/diplomat-reviewer.mdc |
repos:
- repo: https://github.com/Diplomat-ai/diplomat-agent
rev: v0.5.3
hooks:
- id: diplomat-agent
diplomat-agent scan . --format sarif --output results.sarif
Open with SARIF Viewer. Or upload to GitHub Code Scanning.
diplomat-agent scan . --diff-only
diplomat-agent scan . --format registry --output-registry toolcalls.yaml
Like requirements.txt — but for what your agent can do, not what it
depends on. Commit it. Diff it in PRs. When your agent gains a new
capability, the change shows up in review.
| Repo | Type | Tool calls | Unguarded |
|---|---|---|---|
| Skyvern | Application | 753 | 435 (58%) |
| AutoGPT | Application | 668 | 469 (70%) |
| Dify | Platform | 1,361 | 967 (71%) |
| PraisonAI | Framework | 1,281 | 1,106 (86%) |
| CrewAI | Framework | 425 | 317 (75%) |
Application layer: ~62% unguarded across 2,943 tool calls in 9 repos (weighted, v0.5.0 with inter-procedural tracing). Frameworks sit higher — absence of guards there is by design. We scan both identically. Large repos (>400 tool calls) take longer with inter-procedural tracing (e.g. CrewAI ~38s).
| Verdict | Meaning | Posture |
|---|---|---|
| ❌ UNGUARDED | Side effects detected, no checks found | Fix before deploy |
| ⚡ PARTIALLY GUARDED | Some checks present, others missing | Review + add missing guards |
| ✅ GUARDED | All expected checks are present | OK |
| ✅ LOW RISK | Read-only operations — no state mutation | OK |
| ◐ OPAQUE | Effect surface could not be statically resolved | Not a risk rating — review manually |
OPAQUE is honest, not alarming. It means the scanner reached the boundary of what static analysis can see (callable passed to an executor, remote MCP call, unresolvable dispatcher branch). Use # checked:ok — [reason] once reviewed.
| Format | Flag | Use case |
|---|---|---|
| Terminal (default) | — | Human review |
| JSON | --format json | IDE agents, automation |
| SARIF 2.1.0 | --format sarif | VS Code, GitHub Code Scanning |
| CSAF 2.0 | --format csaf | Security teams, CERTs |
| Markdown | --format markdown | Documentation, reports |
| Registry | --format registry | toolcalls.yaml SBOM |
If a function is intentionally unguarded or protected elsewhere:
def send_alert(message): # checked:ok — protected by API gateway
requests.post(ALERT_URL, json={"msg": message})
diplomat-agent finds what your agent can do.
diplomat-gate stops it from doing the dangerous parts at runtime.
| Tool | Stage | What it does | License |
|---|---|---|---|
| diplomat-agent | Know | Maps every tool call with side effects. Static. Pre-deploy. | Apache-2.0 |
| diplomat-gate | Decide | Enforces CONTINUE / REVIEW / STOP at runtime. < 1ms. Zero deps. | BSL 1.1 (source-available) |
| diplomat.run | Prove | Immutable audit trail, dashboard, compliance export. | Hosted service |
# Step 1 — find what your agent can do
pip install diplomat-agent
diplomat-agent scan .
# → 12 unguarded tool calls (8 payments, 4 emails)
# Step 2 — protect them at runtime
pip install "diplomat-gate[yaml]"
# → write gate.yaml, wrap your tools with @gate
from diplomat_gate import Gate
gate = Gate.from_yaml("gate.yaml")
verdict = gate.evaluate({"action": "charge_card", "amount": 15000})
# verdict.decision → STOP
# verdict.violations → [{"policy": "amount_limit", "message": "Amount 15000 exceeds limit of 10000"}]
15+ pre-built policies (payments, emails, shell commands). CONTINUE / REVIEW / STOP in < 1ms. Zero dependencies.
diplomat-gate → · diplomat.run → (hosted control plane with hash-chained audit trail)
# checked:ok for guards
in those paths or external packages@mcp.tool attribute decorator only — bare @tool (from direct import) is not detected@server.call_tool() low-level dispatcher is resolved when handler branches are in scan scope; unresolved/out-of-scope branches are surfaced as OPAQUEtoolcalls.yaml behavioral SBOM--fail-on-unchecked)--diff-only and --file modesast only)rich (colored output), pyyaml (registry)Detection patterns are data, not logic — adding a new pattern means adding an
entry to a list in scanner/patterns.py.
No AST knowledge required.
See CONTRIBUTING.md for how to add patterns, report false positives, and run the test suite.
Apache 2.0