agent-tool-guardrails
Typed policy rails for agent-tool calls: compile declarative requirements into executable ⟨Type, Predicate, Evidence, Action⟩ rails, enforce them in-process or through an MCP proxy, and keep a tamper-evident evidence trail.
Most agent guardrails are filters bolted on after the fact: a prompt classifier here, an
allow/deny list there, nothing an auditor can trace from a regulation to a blocked call.
agent-tool-guardrails makes governance a first-class architectural connector between agents and
tools - typed rails that sit between every agent and every tool - and adds two things most
guardrail layers lack:
- a policy compiler that turns declarative requirements (GDPR anonymisation, approval thresholds, data residency, PII redaction, rate limits, trust gates ...) into executable rails, from one configuration file;
- a cryptographically verifiable Evidence Store so every predicate evaluation, every enforcement and every human approval is an append-only, hash-chained, signed record.
It ships as an MCP proxy: wrap any Model Context Protocol server command and every
tools/call is evaluated with zero changes to the client or the server. Trust-scored
governance (compliance scores, evolving trust, fusion thresholds, policy-driven memory
sharding) throttles or isolates agents that keep violating policy.
Naming: the distribution is
agent-tool-guardrails; the enforcement model is called Typed Rails, so the import isfrom typed_rails import ...and the CLI istyped-rails <command>.
Install
pip install agent-tool-guardrails # PyYAML is the only dependency
pip install 'agent-tool-guardrails[langchain]' # + guard LangChain tools
60-second tour
1. Write requirements, not code - policy.yaml:
version: 1
name: finance
defaults: {on_fail: deny}
requirements:
- id: gdpr-anonymised-exports # a raw rule: Type + Predicate + Evidence + Action
type: privacy
tools: [export_report]
predicate: "evidence.anonymised == true"
evidence: [anonymised]
on_fail: {action: redact, fields: [args.customer.email, args.customer.name]}
citation: "GDPR Art. 5(1)(c)"
- template: approval_threshold # templates capture recurring regulatory patterns
tool: transfer_funds
field: args.amount
threshold: 1000
- template: pii_redaction
tools: ["*"]
- template: trust_gate
tools: [execute_code]
min_trust: 0.7
- template: rate_limit
tools: [search]
max_calls: 30
per_seconds: 60
- template: deny_tools
tools: [drop_database]
typed-rails compile policy.yaml -o finance.rails.json # validate + compile (content-addressed policy hash)
typed-rails explain policy.yaml # Markdown table of every rail
typed-rails check policy.yaml --tool transfer_funds --args '{"amount": 5000}' # -> REQUIRE_APPROVAL, exit 1
2. Enforce with zero code changes - the MCP proxy:
typed-rails proxy --policy policy.yaml --evidence evidence.jsonl -- npx -y @modelcontextprotocol/server-filesystem /data
Point Claude Desktop / Cursor / your agent framework at that command instead of the server.
Denied calls come back as MCP tool errors the model can read and recover from; arguments
and results are redacted in flight; tools an agent can never call disappear from
tools/list; every decision lands in evidence.jsonl.
typed-rails evidence evidence.jsonl verify # OK: 42 record(s), chain intact + Merkle root
typed-rails metrics evidence.jsonl # coverage, safety incident rate, redactions, approvals, per rail / per agent
3. Or enforce in-process:
from typed_rails import compile_policy, PolicyEngine, CallContext, PolicyDenied
engine = PolicyEngine(compile_policy("policy.yaml"))
@engine.guard("transfer_funds", agent="treasury-bot") # pre-conditions before, post-conditions after
def transfer_funds(amount: float, to: str) -> str: ...
decision = engine.evaluate(
CallContext(tool="export_report", args={"customer": {"email": "a@b.com"}}, agent="analyst")
)
print(decision.explain())
# REDACT analyst -> export_report [pre] (2 rail(s))
# - gdpr-anonymised-exports [privacy/tool] FAIL -> redact missing evidence: anonymised (GDPR Art. 5(1)(c))
# - pii-redaction-2 [privacy/tool] FAIL -> redact
# redacted: args.customer.email, args.customer.name
print(engine.evidence.verify()) # OK: 1 record(s), chain intact
The model
| Element | In the code | What it captures |
|---|---|---|
Type T |
RailType(risk, capability) |
hierarchical types: risk (safety, privacy, compliance, security, cost, quality) × capability (tool, api, memory, data, message, code) |
Predicate P |
Predicate("agent.trust >= 0.7 and not has_pii(args)") |
a safe expression over tool, args, agent, evidence, session, env, time, result - no eval, missing paths are null |
Evidence E |
EvidenceSpec(required=[...], capture=[...]) |
evidence keys the predicate needs (auth tokens, anonymisation flags, tickets, regions); what to snapshot into the audit record |
Action A |
ActionSpec(action, fields, message, retry_after_s) |
allow, log, redact, throttle, require_approval, quarantine, deny on fail (and optionally on pass) |
Allow(agent, action) = 1 iff every applicable rail's predicate holds over its evidence.
Rails have a selector (tool and agent globs, pre/post phase) and the most severe
action across applicable rails wins; redactions accumulate. Post-phase rails see the tool
result and can redact or reject it.
Templates
allowed_tools, deny_tools, approval_threshold, rate_limit, trust_gate,
pii_redaction, output_pii_redaction, time_window, data_residency,
purpose_limitation, sandbox_required, max_output_size, evidence_required,
forbidden_patterns. Each compiles to an ordinary rail (run typed-rails explain to see
the generated predicate), so you can always fall back to a raw rule.
Predicate language
tool == "transfer_funds" and args.amount > 1000
evidence.region in ["EU", "UK"] and time.hour >= 8 and time.hour < 18
matches(args.sql, "(?i)drop\s+table") or args.query contains "rm -rf"
count_pii(result) == 0
rate(tool, 60) < 30 # provided by the engine
len(args.items) <= 100 and all(args.flags)
Operators and or not == != < <= > >= in "not in" matches contains startswith endswith + - * / %;
functions len lower upper str int float abs min max sum any all exists keys values matches contains startswith endswith now age count_pii has_pii pii_kinds round. Regular expressions
are bounded and errors fail closed by default.
Evidence Store
from typed_rails import EvidenceStore
store = EvidenceStore("evidence.jsonl", hmac_key=os.environ["RAILS_KEY"])
engine = PolicyEngine(rails, evidence=store)
...
store.verify() # hash chain + HMAC signatures
store.merkle_root() # commit to (or timestamp) the whole log in one hash
store.query(agent="analyst", action="deny")
Records carry the rail evaluations, the evidence the predicate saw (PII-redacted), digests
of the raw arguments/results, the redactions applied and the policy_hash of the compiled
rules in force - the traceability an auditor needs from "which rule" to "which call".
Trust-scored governance
from typed_rails import GovernanceLoop, GovernanceConfig
gov = GovernanceLoop(GovernanceConfig(lam=0.8, alpha=0.5, window=20, throttle_below=0.6, isolate_below=0.4))
engine = PolicyEngine(rails, governance=gov)
Every decision updates the agent's compliance score C_i (allowed actions over the
last K) and trust T_i ← λT_i + (1−λ)E_i; the fusion G_i = αC_i + (1−α)T_i
throttles then isolates agents that keep violating policy - automatically, in the same
engine. Predicates can read the agent's trust (agent.trust) and the loop's
strictness knob (env.strictness, driven by gov.adapt(delta_risk)), and
ShardedMemory applies the same predicate gating to policy / context / analytics memory
shards with auditable lineage.
Architecture
flowchart LR
subgraph design["design time"]
Y[policy.yaml<br/>requirements + templates] --> C[policy compiler] --> RS[(RailSet<br/>policy_hash)]
end
subgraph runtime["runtime"]
A[agent / MCP client] -->|tools/call| PX[MCP proxy<br/>or @engine.guard]
PX --> E[Policy Engine<br/>predicates · evidence · actions]
RS --> E
E -->|allow / redact| T[tool / MCP server]
E -->|deny · approval · throttle| A
T -->|result| E
E --> ES[(Evidence Store<br/>hash chain · HMAC · Merkle)]
E --> G[Governance loop<br/>compliance · trust · fusion]
G --> E
end
ES --> M[typed-rails metrics<br/>SIR · coverage · approvals]
CLI
typed-rails compile policy.yaml [-o rails.json]
typed-rails explain policy.yaml
typed-rails check policy.yaml --tool T [--agent A] [--trust 0.8] [--args JSON] [--evidence JSON] [--result JSON] [--json]
typed-rails proxy --policy policy.yaml [--evidence file.jsonl] [--agent NAME] [--static-evidence JSON] [--hmac-key-env VAR] [--no-hide] -- <server command>
typed-rails evidence file.jsonl [--hmac-key-env VAR] verify | show [--last N] [--json] | export OUT
typed-rails metrics file.jsonl [--json]
Scope
The engine enforces the rules you compile and reports what it measures on your traffic; it
makes no safety claims of its own. Longer documents live in docs/:
policy-language.md (document schema, templates, predicate
namespace, action combination) and architecture.md (viewpoints,
enforcement sequence, trust boundary). To cite the software, use CITATION.cff.
Companion projects
multi-agent-observability- causal tracing, coordination SLOs and deterministic replay for multi-agent systems.agent-chaos-engineering- fault injection and self-healing recovery patterns.
Contributing
See CONTRIBUTING.md. Most wanted: more requirement templates (HIPAA minimum-necessary, PCI-DSS scope, SOC 2 change control), Streamable-HTTP MCP proxying, and adapters for the OpenAI Agents SDK and Semantic Kernel.
git clone https://github.com/nunar-nexus-forge/agent-tool-guardrails && cd agent-tool-guardrails
make sync && make check # everything lives in ./.venv
python examples/guard_demo.py
License
Metadata
Release files for agent-tool-guardrails 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agent_tool_guardrails-0.1.0.tar.gz | 65.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agent_tool_guardrails-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 117.3 kB
Release files / agent_tool_guardrails-0.1.0.tar.gz
| Download URL | agent_tool_guardrails-0.1.0.tar.gz |
|---|---|
| Size | 65.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f0a4d9fc56770c001306bef2dddf014bdd6b38d97ad0bd08f6f61234c4b2ab72
|
|
BLAKE2b-256 checksum How to use checksums |
bb4e94483df58ba55a7fb750cfb5a936ef66a8b067d1035160effc6e55a21c83
|
| 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 4, 2026.
Transparency logRelease files / agent_tool_guardrails-0.1.0-py3-none-any.whl
| Download URL | agent_tool_guardrails-0.1.0-py3-none-any.whl |
|---|---|
| Size | 52.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
908269f3912534aec3f818b834381c10c6f1febc06085ee1a0e9dc93cc1819c8
|
|
BLAKE2b-256 checksum How to use checksums |
e577bc57d110e615064844953acfc8feb79d33900c4e61a6a24f09958391dda0
|
| 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 4, 2026.
Transparency log