CapForge
Policy-as-Code SDK for AI agents — signed capability manifests with runtime authorization.
Version: 0.6.0 | Status: Alpha
CapForge lets you wrap any AI agent tool with a signed, expiring permission slip. Before every tool invocation, the agent must present a cryptographically verifiable manifest declaring who it is, what it's allowed to do, and what limits it operates within.
pip install capforge
from capforge import ACM, Capability, Constraints
# Create a signed permission slip for your agent
manifest = ACM.create(
spiffe_id="spiffe://org/agent/support-bot",
sponsor="alice@org.com",
capabilities=[Capability(resource="knowledge_base", action="read")],
)
manifest.sign("my-key.pem")
# At runtime: may this agent execute this action right now?
result = manifest.check(resource="knowledge_base", action="read")
print(result.allowed) # True
print(result.reason) # "Action authorized"
No framework dependencies. No external services. Works with LangGraph, CrewAI, OpenAI Agents SDK, FastAPI, or any custom agent runtime.
Why CapForge?
When you give an AI agent a tool — a database query, an API call, a shell command — how do you know the agent won't use it beyond what you intended?
Today, teams solve this ad-hoc: hardcoded API keys, fragile conditionals scattered across tool code, and manual review that doesn't scale.
CapForge answers five questions that every production agent deployment needs:
| Question | How CapForge Answers |
|---|---|
| Who is this agent? | Cryptographically signed identity (SPIFFE ID) |
| What is it allowed to do? | Resource:action capability pairs |
| Who is responsible? | Human sponsor field |
| What are its limits? | Constraints (cost, tokens, models, tools, time) |
| Can I verify this? | Ed25519 signature + canonical JSON |
When should you use CapForge?
- You're deploying multi-agent systems in production
- Multiple teams build agents that access shared infrastructure
- You need audit trails for agent actions
- Your agents interact across organizational boundaries
- You operate in regulated environments (fintech, healthcare, government)
When should you NOT use CapForge?
- Single-agent, no-tool setup — You don't need capability manifests for a chatbot that summarises notes
- Simple API key scoping — If a single static key is good enough, it's simpler
- Early prototyping — ACM shines at production scale, not during rapid iteration
Quick Start (under 5 minutes)
1. Install
pip install capforge
2. Create a manifest and check authorization
from capforge import ACM, Capability, Constraints
# This is the core value of CapForge — runtime authorization decisions
manifest = ACM.create(
spiffe_id="spiffe://acme-corp.com/agent/support-bot",
sponsor="alice@acme-corp.com",
capabilities=[
Capability(resource="knowledge_base", action="read"),
Capability(resource="tickets", action="read"),
],
constraints=Constraints(max_cost_per_session=0.05),
)
# Before every tool call: may this action execute?
result = manifest.check(resource="knowledge_base", action="read")
if result.allowed:
print("✅ Authorized — executing tool")
print(f" Remaining budget: ${result.remaining_budget['cost']:.2f}")
else:
print(f"❌ Denied: {result.reason}")
print(f" Failed constraint: {result.failed_constraint}")
3. Cryptographically sign and persist
capforge keygen --output ./my-agent-key
# Sign
manifest.sign("./my-agent-key.pem")
manifest.save("manifest.json")
# Later: load and verify
loaded = ACM.load("manifest.json")
loaded.verify(trusted_keys=["./my-agent-key.pub"])
print(f"Agent: {loaded.spiffe_id}") # spiffe://acme-corp.com/agent/support-bot
print(f"Valid: {loaded.is_valid()}") # True
4. Delegate scope to a sub-agent
child = manifest.delegate(
child_spiffe_id="spiffe://acme-corp.com/agent/search-worker",
capabilities=[Capability(resource="knowledge_base", action="read")],
key="./my-agent-key.pem",
)
child.save("delegated.json")
# Child can only read — cannot write tickets or escalate scope
Framework Integrations
CapForge provides tools to protect agent execution across popular frameworks.
LangGraph
Wrap LangChain tools with ACM authorization checks:
from capforge_langgraph import ACMControlledTool, wrap_tools
wrapped = ACMControlledTool(
tool=search_kb,
acm=manifest,
resource="knowledge_base",
action="read",
)
result = wrapped.invoke({"query": "ACM protocol"})
# → ACM.check() runs before every invocation
📖 examples/langgraph/ — runnable example
CrewAI
Wrap CrewAI tools with ACM authorization:
from examples.crewai.acm_controlled_crew import ACMControlledCrewTool
safe_tool = ACMControlledCrewTool(
tool=SearchKnowledgeBase(),
acm=manifest,
resource="knowledge_base",
action="read",
)
result = safe_tool(query="ACM protocol")
📖 examples/crewai/ — runnable example
FastAPI
Protect API endpoints with ACM dependency injection:
from fastapi import Depends
@app.get("/agents/{agent_id}")
async def get_agent(
agent_id: str,
acm: Annotated[ACM, Depends(require_capability("agent", "read"))],
):
"""Returns agent info only if the request carries a valid ACM."""
return {"agent_id": agent_id, "authorized_by": acm.sponsor}
📖 examples/fastapi/ — runnable server + curl test commands
OpenAI Agents SDK
Wrap function tools with ACM checks:
class ACMControlledFunctionTool:
def __call__(self, *args, **kwargs):
result = self.acm.check(self.resource, self.action)
if not result.allowed:
raise PermissionError(f"ACM denied: {result.reason}")
return self.fn(*args, **kwargs)
📖 examples/openai_agents/ — runnable example
CLI Reference
capforge --help
capforge --version
| Command | Description |
|---|---|
capforge keygen --output <prefix> |
Generate an Ed25519 key pair (.pem + .pub) |
capforge create |
Create and sign an ACM document |
capforge verify <file> --trusted-roots <pubkey> |
Verify temporal validity + cryptographic signature |
capforge validate <file> |
Validate temporal + structural correctness (no crypto) |
capforge info <file> |
Display human-readable ACM information |
capforge delegate |
Create a delegated ACM with narrowed scope |
CLI Example
# Generate keys
capforge keygen --output ./my-key
# Create and sign a manifest
capforge create \
--spiffe-id spiffe://acme-corp.com/agent/support-bot \
--sponsor alice@acme-corp.com \
--capability knowledge_base:read \
--capability tickets:read \
--constraint max_cost_per_session=0.05 \
--key-file ./my-key.pem \
--output manifest.json
# Verify
capforge verify manifest.json --trusted-roots ./my-key.pub
# → Verification PASSED (signature valid)
# → Agent: spiffe://acme-corp.com/agent/support-bot
# → Sponsor: alice@acme-corp.com
📖 examples/cli/ — full end-to-end CLI workflow
Python SDK API
from capforge import ACM, Capability, Constraints, PolicyDecision
# Create
manifest = ACM.create(
spiffe_id="spiffe://org/agent/bot",
sponsor="user@org.com",
capabilities=[Capability(resource="kb", action="read")],
constraints=Constraints(max_cost_per_session=0.05),
ttl=3600,
)
# Sign & persist
manifest.sign("key.pem")
manifest.save("manifest.json")
# Load & verify
loaded = ACM.load("manifest.json")
loaded.verify(trusted_keys=["key.pub"])
# Runtime authorization
result: PolicyDecision = loaded.check(
resource="kb", action="read",
cost=0.01, session_cost=0.03,
model="gpt-5",
)
print(result.allowed, result.reason, result.remaining_budget)
# Delegate scope
child = loaded.delegate(
child_spiffe_id="spiffe://org/agent/sub",
capabilities=[Capability(resource="kb", action="read")],
)
Full Method Reference
| Method | Returns | Description |
|---|---|---|
ACM.create(...) |
ACM |
Factory: create a new manifest |
ACM.load(source) |
ACM |
Factory: load from file path or dict |
manifest.sign(key) |
ACM |
Sign with Ed25519 (PEM path or key object) |
manifest.verify(trusted_keys=...) |
None |
Temporal + optional crypto verification (raises on failure) |
manifest.save(path) |
str |
Save JSON to file, returns JSON string |
manifest.check(...) |
PolicyDecision |
Runtime authorization decision |
manifest.delegate(...) |
ACM |
Create delegated ACM with narrowed scope |
manifest.has_capability(r, a) |
bool |
Check if capability exists |
manifest.get_capability(r, a) |
Capability|None |
Get capability by resource/action |
manifest.is_valid() |
bool |
Check temporal validity window |
manifest.to_dict() |
dict |
Export as dictionary |
manifest.to_json() |
str |
Export as pretty-printed JSON |
Error Handling
from capforge import ACMError
from capforge._exceptions import ACMExpiredError, ACMSignatureError
try:
manifest.verify(trusted_keys=["root.pub"])
except ACMExpiredError:
print("Manifest has expired — renew it")
except ACMSignatureError:
print("Signature invalid — tampered or wrong key")
except ACMError:
print("Other ACM error")
Runtime Policy Engine
Beyond simple capability lookup, ACM.check() evaluates whether an action
is authorized right now considering:
| Check | Field | Example |
|---|---|---|
| Temporal validity | expires_at, not_before |
Block expired manifests |
| Capability presence | resource:action |
Is knowledge_base:read granted? |
| Cost budget | max_cost_per_session |
Limit spend per session |
| Token budget | max_tokens_per_session |
Limit LLM token usage |
| Model restriction | allowed_models |
Only allow specific models |
| Blocked tools | disallowed_tools |
Forbid dangerous operations |
| Execution timeout | max_execution_seconds |
Prevent runaway agents |
result = manifest.check(
resource="github", action="search",
tool="search_code", cost=0.02,
session_cost=0.10, session_tokens=500,
model="gpt-5", execution_seconds=10,
)
if result.allowed:
print(f"✅ Allowed. Budget remaining: ${result.remaining_budget['cost']:.2f}")
else:
print(f"❌ Denied: {result.reason} (constraint: {result.failed_constraint})")
📖 examples/policy/ — 7 scenarios with expected output
CI/CD Integration
Verify ACM documents as a deployment gate in GitHub Actions:
- name: Verify ACM signature
run: capforge verify manifests/agent-acm.json --trusted-roots manifests/trusted-roots.pub
- name: Check capabilities
run: python -c "
from capforge import ACM;
m = ACM.load('manifest.json');
m.check('knowledge_base', 'read')
print('✅ All checks passed')
"
📖 examples/github_actions/ — full workflow
Benchmarks
Core operation performance at varying manifest sizes (100 iterations each):
| Operation | n=1 | n=10 | n=100 | n=1000 |
|---|---|---|---|---|
| Manifest load | ~5 us | ~10 us | ~57 us | ~837 us |
| Signature verify | ~147 us | ~173 us | ~307 us | ~1838 us |
ACM.check() |
~3 us | ~3 us | ~3 us | ~3 us |
| Delegation | ~80 us | ~81 us | ~89 us | ~160 us |
Run locally: python benchmarks/acm_benchmarks.py
FAQ
What problem does CapForge solve?
When an AI agent calls a tool in production, there is no standard way to verify the agent is authorized to make that call. CapForge provides a lightweight, cryptographically verifiable "permission slip" that agents must present before executing tools.
How is it different from RBAC / IAM?
IAM systems (AWS IAM, Kubernetes RBAC) control human or service access to resources. CapForge controls AI agent access to tools and functions within an agent runtime — a problem IAM systems don't address. ACM is complementary: you can use IAM for infrastructure access and ACM for agent tool authorization.
How does it work with MCP (Model Context Protocol)?
MCP defines how agents connect to external tools and data sources. ACM defines what each agent is allowed to do with those connections. An MCP server can require an ACM before executing a tool — the MCP handshake conveys identity, the ACM conveys authorization scope.
How does it work with LangGraph?
CapForge's LangGraph adapter (capforge-langgraph) wraps LangChain tools with
ACM.check() calls. Every tool.invoke() first verifies the ACM is valid,
the capability is granted, and all constraints are met — before the inner tool
executes.
How does signing work?
CapForge uses Ed25519 (Curve25519) for signing. Before signing, the ACM
document is canonicalized (sorted keys, compact JSON, signature field
removed). The canonical bytes are signed, and the base64 signature is stored
in the signature field. Verification re-canonicalizes and checks the
signature against trusted public keys.
What happens when a manifest expires?
The agent can no longer execute authorized actions. The ACM.check() method
returns PolicyDecision(allowed=False, reason="ACM manifest is expired").
You must issue a new ACM with a fresh expires_at. Short-lived ACMs (TTL of
hours) are recommended for production.
Can I revoke a manifest before it expires?
Not directly — ACMs are self-contained documents with no central authority. Revocation is handled by:
- Short TTLs — Manifests expire quickly (hours, not days)
- Revocation lists — Maintain an external list of revoked signature keys
- Key rotation — Rotate trusted root keys, making old signatures invalid
What does a manifest look like?
{
"acm_version": "1.0",
"agent": {
"spiffe_id": "spiffe://acme-corp.com/agent/support-bot"
},
"human_sponsor": "alice@acme-corp.com",
"capabilities": [
{"resource": "knowledge_base", "action": "read"},
{"resource": "tickets", "action": "write",
"constraints": {"status": "resolved_only"}}
],
"issuer": "spiffe://acme-corp.com/user/alice",
"expires_at": "2026-08-22T00:00:00Z",
"signature": "base64_encoded_signature..."
}
Full specification: SPECIFICATION.md
Documentation
| Document | Purpose |
|---|---|
| SPECIFICATION.md | ACM protocol specification v1.0 (canonical) |
| SPEC.md | Protocol specification (alias) |
| ARCHITECTURE.md | Module design, data flow, design principles |
| DECISIONS.md | Architecture Decision Records |
| ROADMAP.md | Phased milestones through standardization |
| CHANGELOG.md | Version history (v0.1.0 → v0.6.0) |
| SECURITY.md | Security policy and vulnerability reporting |
| CONTRIBUTING.md | Contribution guidelines |
Project Structure
capforge/ # Core SDK (zero framework dependencies)
├── __init__.py # Public API: ACM, Capability, Constraints
├── _acm.py # Main ACM class
├── _model.py # Pydantic data models
├── _crypto.py # Ed25519 signing/verification
├── _policy.py # Runtime policy engine (ACM.check())
├── _validator.py # Temporal + delegation validation
├── _delegation.py # Delegation scope narrowing
├── _exceptions.py # Exception hierarchy
├── _version.py # Package version (dynamic)
├── py.typed # PEP 561 type marker
└── cli/
├── __init__.py
└── main.py # Click CLI (6 commands)
tests/ # 103 tests
├── test_acm.py # 30 tests
├── test_crypto.py # 14 tests
├── test_policy.py # 34 tests
├── test_langgraph_adapter.py # 10 tests
├── test_model.py # 7 tests
├── test_validator.py # 5 tests
└── test_version.py # 3 tests
capforge_langgraph/ # LangGraph adapter (separate package)
├── __init__.py
├── _tool_wrapper.py
└── pyproject.toml
examples/ # Production showcase
├── langgraph/ # LangGraph integration
├── crewai/ # CrewAI integration
├── fastapi/ # FastAPI + ACM dependencies
├── openai_agents/ # OpenAI Agents SDK integration
├── cli/ # CLI workflow
├── github_actions/ # CI/CD verification
└── policy/ # Runtime policy engine
benchmarks/ # Performance benchmarks
└── acm_benchmarks.py
spec/ # Protocol specification
├── acm-schema.json # JSON Schema (Draft 2020-12)
└── examples/
├── valid-acm.json
├── minimal-acm.json
└── expired-acm.json
Progress
| Area | Status |
|---|---|
| Protocol specification | ✅ 100% |
| JSON Schema | ✅ 100% |
| Pydantic data models | ✅ 100% |
| Ed25519 cryptography | ✅ 100% |
| Temporal validation | ✅ 100% |
| Delegation chain | ✅ 100% |
| Public API (ACM class) | ✅ 100% |
| Click CLI (6 commands) | ✅ 100% |
| Test suite (216 tests) | ✅ 100% |
| ruff + mypy strict | ✅ 100% |
| CI pipeline | ✅ 100% |
| LangGraph adapter | ✅ 100% |
| Runtime policy engine | ✅ 100% |
| MCP adapter | ✅ 100% |
| Audit logging | ✅ 100% |
| Remote policy distribution | ✅ 100% |
| Policy server (REST API) | ✅ 100% |
| Framework examples (6 frameworks) | ✅ 100% |
| Performance benchmarks | ✅ 100% |
| Repository stabilization | ✅ 100% |
Roadmap
Phase 1 ─── Foundation + Specification ✓ (v0.1.0)
Phase 2 ─── Cryptography + CLI Enhancement ✓ (v0.2.0)
Phase 3a ─ CapForge SDK ✓ (v0.3.0)
Phase 3b ─ LangGraph adapter ✓
Phase 4 ─── Runtime Policy Engine ✓ (v0.4.0)
Phase 5 ─── Production Showcase ✓ (v0.5.0)
Phase 6 ─── MCP Integration ✓ (v0.6.0)
Phase 7 ─── Audit Logging ✓ (v0.6.0)
Phase 8 ─── Remote Policy Distribution ✓ (v0.6.0)
Phase 9 ─── Policy Server ✓ (v0.6.0)
Phase 9.1 ─ Repository Stabilization ✓ (v0.6.0) ← You are here
See ROADMAP.md for details on upcoming phases.
Contributing
See CONTRIBUTING.md for development setup, coding standards, and pull request process.
- Report bugs: GitHub Issues
- Discuss ideas: GitHub Discussions
- Security vulnerabilities: See SECURITY.md
License
Built for the AI agent ecosystem. Not another chatbot. Not another agent framework. Infrastructure for agent authorization.
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 capforge-0.6.0.tar.gz.
File metadata
- Download URL: capforge-0.6.0.tar.gz
- Upload date:
- Size: 153.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d56433fcd568040d43f6e32dafa7c92311598e08b15b299c27d6696476d28c3
|
|
| MD5 |
5a0447588ace4dc56b938f67d2e217b5
|
|
| BLAKE2b-256 |
9f5f0af83d075e0db8dd558f710c938d848b4e8186f1627b506fc1f89b2c8925
|
File details
Details for the file capforge-0.6.0-py3-none-any.whl.
File metadata
- Download URL: capforge-0.6.0-py3-none-any.whl
- Upload date:
- Size: 69.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
910ddc5d568f65108eafaf0ddc4f8fbfed6f897673053e0c653a3e9c099f7335
|
|
| MD5 |
490c79056b9622557eff12700a26154a
|
|
| BLAKE2b-256 |
e1097ddb4a6d7427370be83f606e7c55ff553881292f311a9e5b78edd71c13be
|