If your team runs LLM or agent workflows that make real-world API calls, CAPS Guard gives you deterministic policy enforcement, human approval gates, and full audit traces at the execution boundary. CAPS Guard is extracted from CAPS ("Context Action Planning Service"), my broader private project. This repo is the standalone guardrail + audit layer — published as an independent dev tool.
AI workflows can make side-effect calls (message/email/calendar/etc.) without clear policy visibility. CAPS Guard enforces deterministic policy decisions at the tool boundary and emits trace artifacts that explain exactly what happened and why.
- Audience: developers/teams building tool-calling workflows who control the execution boundary.
- Value: deterministic policy decisions (
ALLOW | REVIEW_REQUIRED | BLOCK), HITL for risky actions, and auditable trace artifacts. - Adoption model: register tools in manifests and route calls through CAPS adapters or thin wrappers.
- Fastest trial path: run
checkandexecute --planfirst (no Ollama/model needed). - Prompt flow (
execute --prompt) needs local Ollama + model.
CAPS Guard is for developers building tool-calling AI workflows who want deterministic policy checks, human approval for risky actions, and auditable traces at the execution boundary. v0.1 works best when your tool/API calls can be routed through CAPS manifests and adapters. It is not yet a drop-in wrapper for every existing agent framework. Best fit: teams that control their tool execution layer and can integrate at that boundary.
What this is:
- A guardrail and audit layer for tool-calling AI workflows.
- Deterministic
ALLOW | REVIEW_REQUIRED | BLOCKdecisions. - Human approval for risky side effects.
- Trace artifacts for execution and review history.
What this is not:
- Not a universal wrapper for every LLM app out of the box.
- Not a full agent framework.
- Not a no-code tool.
- Not a hosted review platform in v0.1.
To use CAPS Guard, your workflow must route tool execution through CAPS Guard’s execution boundary. In practice, that means:
- Register tools in a manifest.
- Define policy coverage for those tools.
- Use CAPS adapters directly, or wrap existing tool/API calls so CAPS Guard evaluates them before execution. This is deliberate for v0.1: CAPS Guard is an integration boundary, not a zero-config global wrapper. If your tool calls are already routed through clean adapter boundaries, integration is usually a few minutes (manifest + one routing point). If tool calls are scattered across your codebase, integrate adapters first.
New tools require two things:
- Manifest coverage: define tool name, side-effect class, sink behavior, and relevant policies.
- Adapter coverage: route the actual tool/API call through CAPS Guard so policy is evaluated before execution.
Implementation Details: before/after adapter wrapping pattern
This mirrors the repo's runtime pattern (core.manifest_loader + core.policy_engine + adapter call), simplified for clarity.
Before (direct adapter/API call, no guard):
from adapters.messaging_api import send_message
def send_message_tool(message: str):
params = {"recipient_ref": "Jacob", "message": message}
request_context = {"thread_id": "demo1"}
return send_message(params, request_context, trace_id="trace_local")After (policy-gated at execution boundary):
from adapters.messaging_api import send_message
from core.manifest_loader import build_manifest_context, load_manifest
from core.policy_engine import evaluate_tool_policy
manifest = load_manifest("src/manifest_demo.json")
manifest_context = build_manifest_context(manifest)
def send_message_tool(message: str):
params = {"recipient_ref": "Jacob", "message": message}
decision = evaluate_tool_policy(
step_id="send_message_step",
tool_name="messaging_api",
params=params,
manifest_context=manifest_context,
approved_for_sink=False, # switch true on explicit human approval path
trace_id="trace_local",
)
if decision["decision"] == "ALLOW":
return send_message(params, {"thread_id": "demo1"}, trace_id="trace_local")
if decision["decision"] == "REVIEW_REQUIRED":
raise RuntimeError(f"review required: {decision['reason_code']}")
raise RuntimeError(f"blocked: {decision['reason_code']}")For LangGraph/CrewAI/custom loops, this wrapper belongs in your tool node/executor; no framework rewrite is required.
You can likely use CAPS Guard if:
- Your app/agent makes tool or API calls.
- You control the tool execution layer.
- You can route calls through manifests/adapters or thin wrappers.
You probably cannot use it directly yet if:
- Your stack hides execution in a closed runtime you cannot intercept.
- You want zero integration work.
- You expect automatic support for arbitrary tools without manifest/adapter mapping.
Core requirements (for check and execute --plan):
- Python 3.10+
Prompt mode add-ons (for execute --prompt only):
- Ollama running locally.
- Pulled local model (default from
src/config.py).
Setup:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtDocker (optional):
docker build -t caps-guard:local .
docker run --rm caps-guard:local --helpIf docker is not found on macOS but Docker Desktop is installed:
export PATH="/Applications/Docker.app/Contents/Resources/bin:$PATH"
hash -r
docker --versionPolicy check without execution:
python scripts/caps_guard.py check \
--manifest src/manifest_demo.json \
--tool messaging_api \
--args-json '{"message":"hello"}' \
--output-dir /tmp/guard_check_demoPlan execution (no prompt parsing needed):
python scripts/caps_guard.py execute \
--manifest src/manifest_demo.json \
--plan examples/plan_rw_demo.json \
--output-dir /tmp/guard_execute_demoContainerized check demo (no local Python env needed):
docker run --rm caps-guard:local check \
--manifest src/manifest_args_demo.json \
--tool weather_api \
--args-json '{"query":"drop table users"}' \
--output-dir /tmp/args_demo_checkUse this exact v0.1 flow to show pause on sink and explicit approval resume:
rm -f .caps_guard_demo.sqlite
rm -rf /tmp/section9_block /tmp/section9_approve
python scripts/caps_guard.py execute \
--manifest src/manifest_demo.json \
--prompt "If weather is below 100C in Toronto, text Jacob I am not coming to university today." \
--thread-id demo1 \
--sqlite-path .caps_guard_demo.sqlite \
--output-dir /tmp/section9_block \
> /tmp/section9_block_stdout.json
python scripts/caps_guard.py execute \
--manifest src/manifest_demo.json \
--resume-review approve \
--thread-id demo1 \
--sqlite-path .caps_guard_demo.sqlite \
--output-dir /tmp/section9_approve \
> /tmp/section9_approve_stdout.jsonInspect artifacts:
cat /tmp/section9_block/trace.json
cat /tmp/section9_approve/trace.json
cat /tmp/section9_approve/trace_graph.json
python scripts/caps_guard.py render-trace \
--trace /tmp/section9_approve/trace.json \
--output /tmp/section9_approve/trace_render.html \
--title "CAPS Guard Section9 Trace"Expected behavior:
- First run pauses before sink execution (
pending_review=true,paused_for_review=true). - Resume run emits
review_resumeand completes sink execution. trace_idremains stable across pause/resume for the same thread.
checkandexecute --planpaths do not require prompt parsing.- Prompt-driven execution (
execute --prompt) uses the LangGraph pipeline and local model/runtime configuration. - Keep Ollama available when using prompt mode.
- A ready-to-run LangGraph demo is available at
examples/langgraph_demo/README.md.
Render any trace.json artifact into a shareable static HTML timeline:
python scripts/caps_guard.py render-trace \
--trace /tmp/section9_approve/trace.json \
--output /tmp/section9_approve/trace_render.html \
--title "CAPS Guard Trace"What it shows:
- Event cards in execution order.
- Decision color cues (
ALLOWgreen,REVIEW_REQUIREDyellow,BLOCKred). - Hover detail payload (reason code, rule id, run id, timestamps, args when present).
Use these profiles to validate v0.1 policy proofs:
src/manifest_demo.json: primary profile (alias of default policy profile used for demos).src/manifest_side_effect_demo.json: side-effect class policy proof:WRITEnon-sink ->REVIEW_REQUIRED(rule_id=REVIEW_WRITE_CLASS)IRREVERSIBLE->BLOCK(rule_id=BLOCK_IRREVERSIBLE)
src/manifest_args_demo.json: argument-level block proof:- forbidden args ->
BLOCK(reason_code=ARGS_FORBIDDEN_PATTERN)
- forbidden args ->
Side-effect class proof commands:
python scripts/caps_guard.py check \
--manifest src/manifest_side_effect_demo.json \
--tool messaging_api \
--args-json '{"message":"hi"}' \
--output-dir /tmp/sidefx_check_write
python scripts/caps_guard.py check \
--manifest src/manifest_side_effect_demo.json \
--tool calendar_api \
--args-json '{"title":"deploy"}' \
--output-dir /tmp/sidefx_check_irrevBlocked demo (ARGS_FORBIDDEN_PATTERN):
python scripts/caps_guard.py check \
--manifest src/manifest_args_demo.json \
--tool weather_api \
--args-json '{"query":"drop table users"}' \
--output-dir /tmp/args_demo_checkArchitecture visuals:
- Every tool step is evaluated before execution.
- Decision outcomes are deterministic:
ALLOW,REVIEW_REQUIRED,BLOCK.
- Decisions are manifest-driven (
src/manifest*.json), not hardcoded in runtime flow. - Precedence is deterministic (
BLOCK > REVIEW_REQUIRED > ALLOW). - Decision payload includes
reason_codeandrule_idfor auditability.
- If policy returns
REVIEW_REQUIREDfor an actionable sink step, execution pauses. - Resume path uses explicit human decision (
approveorreject).
trace.json: canonical event log for decisions/tool calls/results/final summary.trace_graph.json: deterministic nodes/edges execution-path view derived fromtrace.json.
Advanced trace/event contract details live in:
TRACE_SCHEMA.md
- Supports the current tool/step model.
- New tools require manifest + adapter coverage.
trace_graph.jsonis currently sequential execution flow, not a full branch tree.- Env-aware rules are not in v0.1 (Slice D scope).
- Hosted review workflows are out of scope for v0.1.
- Post-v0.1 Slice D: env-aware policy hardening (
prod-sensitive rules). - Branch-aware trace graph evolution (
trace_graph_v2.json). - Lightweight graph renderer (
trace_graph.json-> HTML/SVG) for demo UX.
Run before release:
python -m py_compile src/main.py scripts/caps_guard.py scripts/regression_suite.py src/core/langgraph_flow.py src/core/mcp.py src/core/execution_runtime.py src/core/policy_engine.py
python scripts/regression_suite.py --policy-only
python scripts/regression_suite.py --guard-only
python scripts/regression_suite.py --hitl-only
