Skip to main content

toolguard — Python SDK for Tool Guard Core

Universal AI agent governance: evaluate any tool call against configurable policies before it executes. Drop-in adapters for LangChain, AutoGen, native OpenAI/Anthropic tool use, and MCP. Two backends — CLI (tg evaluate) and HTTP proxy (tg-proxy /evaluate) — so you can run locally or in production.

Install

The PyPI distribution is named tool-guard-core because the unqualified toolguard project belongs to an unrelated package. The import remains toolguard. Tagged releases beginning with v0.8.0 install with:

pip install "tool-guard-core"                    # core only (httpx)
pip install "tool-guard-core[langchain]"         # + langchain-core
pip install "tool-guard-core[autogen]"           # + pyautogen
pip install "tool-guard-core[openai]"            # + openai SDK
pip install "tool-guard-core[anthropic]"         # + anthropic SDK

Before v0.8.0, or when testing an unreleased checkout, use pip install "./sdk/python[langchain]" from the repository root.

Quick start

from toolguard import ToolGuard, ToolDenied

guard = ToolGuard(
    mode="proxy",                        # or "cli" to run tg locally
    proxy_url="http://localhost:9090",
    agent_id="my-agent",
    org_id="acme",
)

try:
    result = guard.evaluate("issue_refund", {"amount": 1000})
    # allowed — call the real tool
except ToolDenied as exc:
    # exc.result.decision_reason, exc.result.suggested_response
    print("Blocked:", exc)

CLI mode (no running proxy)

guard = ToolGuard(
    mode="cli",
    tg_bin="/usr/local/bin/tg",          # or just "tg" if on PATH
    policy_dir="/etc/tg/policies",       # directory of *.yaml policies
    agent_id="my-agent",
    org_id="acme",
)
result = guard.evaluate_raw("issue_refund", {"amount": 100})
print(result.decision)  # "allowed"

CLI mode evaluates the whole policy directory as one set. Each policy's YAML mode owns its contribution: a mode: shadow policy remains observe-only under the CLI's default enforcement call-site mode, while a mode: enforcement policy cannot be downgraded by a shadow default. Mixed-mode behavior is pinned against the real Go engine in tests/test_contract.py.

evaluate() raises ToolDenied or ToolEscalated on a block. evaluate_raw() always returns the EvaluationResult without raising.


LangChain / LangGraph

from toolguard import ToolGuard
from toolguard.adapters.langchain import ToolGuardCallbackHandler
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor

guard = ToolGuard(mode="proxy", proxy_url="http://localhost:9090",
                  agent_id="lc-agent", org_id="acme")

handler = ToolGuardCallbackHandler(
    guard,
    tool_groups={"issue_refund": "monetary_outflow", "search": "read_ops"},
)

executor = AgentExecutor(agent=agent, tools=tools, callbacks=[handler])
# Every tool call goes through policy evaluation before execution.

The handler raises ToolDenied / ToolEscalated on a block, stopping the chain. Flagged calls (recorded near-misses) proceed.


Microsoft AutoGen

from toolguard import ToolGuard
from toolguard.adapters.autogen import guarded

guard = ToolGuard(mode="proxy", proxy_url="http://localhost:9090",
                  agent_id="autogen-agent", org_id="acme")

@guarded(guard, tool_name="issue_refund", tool_group="monetary_outflow")
def issue_refund(order_id: str, amount: float) -> dict:
    return {"status": "refunded", "amount": amount}

# Register with AutoGen as normal:
assistant.register_for_execution(name="issue_refund")(issue_refund)

On deny/escalate, guarded returns {"error": "denied", "reason": "...", "suggested_response": "..."} — the conversation loop surfaces it to the LLM.


Native OpenAI tool use

import openai
from toolguard import ToolGuard
from toolguard.adapters.native import guard_tool_calls

client_oai = openai.OpenAI()
guard = ToolGuard(mode="proxy", proxy_url="http://localhost:9090",
                  agent_id="openai-agent", org_id="acme")

response = client_oai.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
)

allowed, denied = guard_tool_calls(
    response.choices[0].message.tool_calls,
    guard,
    provider="openai",
    tool_groups={"issue_refund": "monetary_outflow"},
)

tool_results = []
for item in allowed:
    call = item["call"]
    output = MY_TOOLS[call.function.name](**json.loads(call.function.arguments))
    tool_results.append({"role": "tool", "tool_call_id": call.id, "content": str(output)})
for item in denied:
    tool_results.append(item["tool_result"])  # role=tool, error message

Native Anthropic tool use

import anthropic
from toolguard import ToolGuard
from toolguard.adapters.native import guard_tool_calls

client_ant = anthropic.Anthropic()
guard = ToolGuard(mode="proxy", proxy_url="http://localhost:9090",
                  agent_id="anthropic-agent", org_id="acme")

response = client_ant.messages.create(
    model="claude-opus-4-5",
    max_tokens=4096,
    tools=tools,
    messages=messages,
)

tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
allowed, denied = guard_tool_calls(tool_use_blocks, guard, provider="anthropic")

tool_results = []
for item in allowed:
    call = item["call"]
    output = MY_TOOLS[call.name](**call.input)
    tool_results.append({"type": "tool_result", "tool_use_id": call.id, "content": str(output)})
for item in denied:
    tool_results.append(item["tool_result"])  # type=tool_result, is_error=True

MCP server

from toolguard import ToolGuard
from toolguard.adapters.mcp import guard_mcp_tool

guard = ToolGuard(mode="proxy", proxy_url="http://localhost:9090",
                  agent_id="mcp-server", org_id="acme")

@guard_mcp_tool(guard, tool_group="data_access")
def call_tool(tool_name: str, parameters: dict, **kwargs) -> dict:
    return TOOLS[tool_name](**parameters)

Errors

Exception When raised
ToolDenied action_taken is anything other than allowed, allowed_shadow, flagged, or escalated (i.e. the call was actually blocked)
ToolEscalated action_taken == "escalated" (wait for human approval)

Raised on action_taken, not decision — deliberately. In shadow mode the engine reports decision="denied" (what would happen) alongside action_taken="allowed_shadow" (what actually happened, since shadow mode never enforces); evaluate() never raises for that case, since shadow mode exists specifically to observe without blocking.

Both carry a .result attribute with the full EvaluationResult: result.decision_reason, result.suggested_response, result.rule_results, etc.


Wire contract

The SDK mirrors the Go pkg/domain JSON contract exactly. Key field names:

ActionEnvelope (what the SDK sends to the engine): envelope_id, timestamp, agent_id, session_id, org_id, framework ("mcp" | "langgraph" | "sdk" | "unknown"), tool_name, tool_group, parameters, context.verified, context.session_state, integration_type ("mcp_proxy" | "langgraph_middleware" | "sdk").

EvaluationResult (what the engine returns): decision ("allowed" | "denied" | "escalated" | "flagged"), action_taken, decision_reason, effective_mode, policies_matched, rules_evaluated, rules_triggered, rule_results, applied_rule_results, primary_citation, applied_primary_citation, is_near_miss, suggested_response, and the proxy-only optional decision_receipt. The applied provenance fields explain action_taken; the aggregate fields can point to stricter shadow-only telemetry.

DecisionReceipt (after a successful proxy audit append): receipt_version, trace_id, trace_hash, hash_algorithm, canonical_trace_version, integrity_model, decision, action_taken, timestamp, optional issuer, and the opaque receipt_uri.

Escalation (poll/approve/deny responses): id, state, timestamps, approver fields, envelope, decision, and an optional resolution_receipt after the terminal resolution was audited.

Decision-receipt helper

from toolguard.adapters.memory import with_receipt_reference

result = guard.evaluate_raw("issue_refund", {"amount": 100})
event = with_receipt_reference(
    {"tool": "issue_refund", "outcome": result.action_taken}, result
)

The helper adds tool_guard_receipt_uri only when the proxy supplied a real receipt. It performs no storage or network operation and never fabricates a fallback identifier. Absence is never authorization. The URN is a tamper-evident hash-chain reference, not a signature or fetchable URL; see the full contract.


Development

cd sdk/python
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ --cov-fail-under=90

Or via the top-level Makefile:

make sdk-test

Metadata

Release files for tool-guard-core 0.8.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tool-guard-core 0.8.1
File Size Uploaded Provenance
tool_guard_core-0.8.1.tar.gz 50.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for tool-guard-core 0.8.1
File Interpreter ABI Platform
tool_guard_core-0.8.1-py3-none-any.whl Python 3 none any Details

Total release size: 81.5 kB

Release files / tool_guard_core-0.8.1.tar.gz

Download URL tool_guard_core-0.8.1.tar.gz
Size 50.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b79b88f05da25014937da9c5f4074a3f26eb25839d3fc7e48acb89fe5f18e0e0
BLAKE2b-256 checksum
How to use checksums
6a19862bbac621b77cc4fe6724d49f8383715b098928688e94e0b113e29d24d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / tool_guard_core-0.8.1-py3-none-any.whl

Download URL tool_guard_core-0.8.1-py3-none-any.whl
Size 30.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49aa6f8b0a786190a55b7272619742a6f09c52ebf927a55912a4e7626c5a531a
BLAKE2b-256 checksum
How to use checksums
88260ff9a8c49d21ca2e55fe7e5691492c4a7f54d9dbf1101f5fe4f993a914e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Security for tool-guard-core 0.8.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for tool_guard_core-0.8.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.8.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page