Metadata-Version: 2.4
Name: callguard
Version: 0.3.0
Summary: Runtime safety for AI agents. Stop agents before they break things.
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Provides-Extra: agno
Requires-Dist: agno>=1.0; extra == 'agno'
Provides-Extra: all
Requires-Dist: agno>=1.0; extra == 'all'
Requires-Dist: aiohttp>=3.9; extra == 'all'
Requires-Dist: click>=8.0; extra == 'all'
Requires-Dist: crewai>=0.80; extra == 'all'
Requires-Dist: jsonschema>=4.20; extra == 'all'
Requires-Dist: langchain-core>=0.3; extra == 'all'
Requires-Dist: openai-agents>=0.1; extra == 'all'
Requires-Dist: opentelemetry-api>=1.20; extra == 'all'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: rich>=13.0; extra == 'all'
Requires-Dist: semantic-kernel>=1.0; extra == 'all'
Provides-Extra: cli
Requires-Dist: click>=8.0; extra == 'cli'
Requires-Dist: jsonschema>=4.20; extra == 'cli'
Requires-Dist: pyyaml>=6.0; extra == 'cli'
Requires-Dist: rich>=13.0; extra == 'cli'
Provides-Extra: crewai
Requires-Dist: crewai>=0.80; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.1; extra == 'openai-agents'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
Provides-Extra: semantic-kernel
Requires-Dist: semantic-kernel>=1.0; extra == 'semantic-kernel'
Provides-Extra: sinks
Requires-Dist: aiohttp>=3.9; extra == 'sinks'
Provides-Extra: yaml
Requires-Dist: jsonschema>=4.20; extra == 'yaml'
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# CallGuard

[![PyPI](https://img.shields.io/pypi/v/callguard)](https://pypi.org/project/callguard/)
[![License](https://img.shields.io/pypi/l/callguard)](LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/callguard)](https://pypi.org/project/callguard/)

**Runtime contracts for AI agents.**

AI agents make tool calls. Tool calls have side effects. Nobody governs what happens between "agent decides" and "tool executes." CallGuard is that governance layer — preconditions, postconditions, session limits, and a full audit trail, enforced at the point where decision becomes action.

## Show Me

**contracts.yaml**

```yaml
apiVersion: callguard/v1
kind: ContractBundle

metadata:
  name: my-policy

defaults:
  mode: enforce

contracts:
  - id: block-sensitive-reads
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
    then:
      effect: deny
      message: "Sensitive file '{args.path}' blocked."
      tags: [secrets, dlp]
```

**Python**

```python
import asyncio
from callguard import CallGuard, CallGuardDenied

async def main():
    guard = CallGuard.from_yaml("contracts.yaml")

    try:
        result = await guard.run("read_file", {"path": "/app/config.json"}, read_file_fn)
        print(result)
    except CallGuardDenied as e:
        print(f"Denied: {e}")

asyncio.run(main())
```

**CLI**

```bash
$ callguard validate contracts.yaml
✓ contracts.yaml — 1 contract (1 pre)

$ callguard check contracts.yaml --tool read_file --args '{"path": ".env"}'
⛔ DENIED by block-sensitive-reads
   Message: Sensitive file '.env' blocked.
   Tags: secrets, dlp
   Rules evaluated: 1
```

**Framework integration (one adapter, same guard)**

```python
from callguard.adapters.langchain import CallGuardMiddleware

middleware = CallGuardMiddleware(guard)
# Wraps any LangChain tool — preconditions, audit, and session limits apply automatically
```

## Features

- **YAML contracts** — Preconditions, postconditions, and session limits declared in version-controlled YAML files
- **6 framework adapters** — LangChain, CrewAI, Agno, Semantic Kernel, OpenAI Agents SDK, Claude Agent SDK
- **Audit trail** — Structured JSON events with automatic redaction of secrets (OpenAI keys, AWS creds, JWTs, GitHub tokens)
- **Observe mode** — Shadow-deploy contracts without blocking; review `CALL_WOULD_DENY` events before enforcing
- **CLI tooling** — `validate`, `check`, `diff`, and `replay` commands for CI/CD integration
- **Principal context** — Role, ticket ref, and claims propagated through every decision and audit event
- **Session limits** — Cap total calls, attempts, and per-tool executions to catch runaway agents
- **Zero runtime deps** — Pure Python 3.11+. OTel, sinks, and adapters are optional extras

## How It Compares

| Approach | Scope | Runtime enforcement | Audit trail |
|---|---|---|---|
| Prompt/output guardrails | Input/output text | No — advisory only | No |
| API gateways / MCP proxies | Network transport | Yes — at the proxy | Partial |
| Security scanners | Post-hoc analysis | No — detection only | Yes |
| Manual if-statements | Per-tool, ad hoc | Yes — scattered logic | No |
| **CallGuard** | **Tool call contracts** | **Yes — deterministic pipeline** | **Yes — structured + redacted** |

## Install

```bash
pip install callguard              # core (zero deps)
pip install callguard[yaml]        # + YAML contract engine
pip install callguard[sinks]       # + webhook, Splunk, Datadog sinks
pip install callguard[cli]         # + validate/check/diff/replay CLI
pip install callguard[all]         # everything
```

## Built-in Templates

```python
guard = CallGuard.from_template("file-agent")      # secret file protection, destructive cmd blocking
guard = CallGuard.from_template("research-agent")   # output PII detection, session limits
guard = CallGuard.from_template("devops-agent")     # role gates, ticket requirements, bash safety
```

## Links

- [Documentation](https://acartagena.github.io/callguard/)
- [GitHub](https://github.com/acartagena/callguard)
- [PyPI](https://pypi.org/project/callguard/)
- [Changelog](CHANGELOG.md)
- [License](LICENSE) (MIT)