Runtime safety for AI agents. Stop agents before they break things.
Project description
CallGuard
Runtime safety for AI agents. Stop agents before they break things.
Nothing sits between an AI agent deciding to call rm -rf / and it happening. CallGuard is that layer. It intercepts every tool call, enforces contracts and operation limits, logs a structured audit trail, and returns actionable error messages so agents self-correct instead of failing silently. Zero runtime dependencies. Drop it in front of any tool-calling agent.
Why CallGuard
- Audit trail for every tool call. Structured JSON events with automatic redaction of secrets (OpenAI keys, AWS credentials, JWTs, GitHub tokens). Know exactly what your agent did, when, and why it was allowed or denied.
- Agents self-correct from actionable denials. When CallGuard blocks a tool call, it tells the agent why with a specific, instructive message. The agent adjusts its approach instead of retrying blindly.
- Observe mode for shadow deployment. Run the full governance pipeline without blocking anything. Audit events log
CALL_WOULD_DENYso you can tune rules before enforcing them in production. - Zero runtime dependencies. Pure Python 3.11+. OpenTelemetry support via optional
callguard[otel].
Install
pip install callguard
Optional OpenTelemetry support:
pip install callguard[otel]
Requires Python 3.11+.
Quickstart
import asyncio
from callguard import CallGuard, CallGuardDenied, deny_sensitive_reads
guard = CallGuard(contracts=[deny_sensitive_reads()])
async def read_file(file_path):
return open(file_path).read()
async def main():
# This succeeds
result = await guard.run("Read", {"file_path": "/tmp/notes.txt"}, read_file)
# This raises CallGuardDenied
try:
await guard.run("Read", {"file_path": "/home/user/.ssh/id_rsa"}, read_file)
except CallGuardDenied as e:
print(e.reason)
# "Access to sensitive path blocked: /home/user/.ssh/id_rsa.
# This file may contain secrets or credentials."
asyncio.run(main())
See docs/quickstart.md for Claude Agent SDK integration, custom contracts, hooks, and audit configuration.
Key Concepts
Every tool call is wrapped in a ToolEnvelope -- a frozen, deep-copied snapshot of the invocation (tool name, args, side-effect classification, environment). Envelopes are immutable. Nothing downstream can tamper with the original args.
Contracts define governance rules. A @precondition runs before execution and can deny the call. A @postcondition runs after and emits warnings (observe-only in v0.0.1 -- it never blocks). A @session_contract checks cross-turn state like total execution counts. All return a Verdict: either Verdict.pass_() or Verdict.fail("actionable message").
Hooks are lower-level interception points. A before-hook receives the envelope and returns HookDecision.allow() or HookDecision.deny("reason"). After-hooks observe the result. Hooks run before contracts in the pipeline.
The GovernancePipeline evaluates five steps in order: attempt limit, before-hooks, preconditions, session contracts, execution limits. First denial wins. If everything passes, the tool executes, then postconditions and after-hooks run.
CallGuard tracks two counter types. max_attempts caps all governance evaluations, including denied ones -- this catches denial loops where an agent keeps retrying the same blocked call. max_tool_calls caps only successful executions. Both fire independently.
In observe mode, the full pipeline runs and audit events are emitted, but denials are converted to CALL_WOULD_DENY and the tool executes anyway. Use this for shadow deployment: see what would break before you enforce it.
Audit and redaction happen at write time. Every tool call emits a structured AuditEvent to a configurable sink (stdout, file, or custom). RedactionPolicy strips sensitive keys, detects secret value patterns, redacts bash credentials, and caps payloads at 32KB. Redaction is destructive by design -- no recovery path.
What This Is NOT
- Not prompt injection defense.
- Not content safety filtering.
- Not network egress control.
- Not a security boundary for Bash. (
BashClassifieris a heuristic, not a sandbox.) - Not concurrency-safe across workers. (
MemoryBackendis single-process.)
Links
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file callguard-0.1.0.tar.gz.
File metadata
- Download URL: callguard-0.1.0.tar.gz
- Upload date:
- Size: 30.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1d34395b258f4d29b1807b863235f29632012f8a3767dff5439337bcf743480
|
|
| MD5 |
3c4010caec6b5e477b3ab89e61e34e6b
|
|
| BLAKE2b-256 |
386c5b34d7d30c8f00ef52d3c1b007e8bb58129eae1dfd3dda2ac40812cd4d25
|
File details
Details for the file callguard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: callguard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
916feab4cddee9db1937c20d22f26db6462a189a95a1c86fca755afd85d596a2
|
|
| MD5 |
915eb21f85e33b2c8ade05d3ea6f8584
|
|
| BLAKE2b-256 |
d881c8e8f4db19a4964ce2b06d853b7ae9da083ce07946e02dfa9110aa17e3be
|