A local-first, explainable runtime policy gateway for AI tool calls.
PermitWeave gives an agent tool call a deterministic checkpoint before it reaches an executor. It evaluates a versioned policy, returns ALLOW, DENY, or REQUIRE_APPROVAL, explains the matched rules, and writes a redacted hash-linked receipt. It is deliberately small, offline-friendly, and independent of any model provider.
PermitWeave is a policy decision point, not a sandbox, secret manager, or guarantee against malicious code. Run it in front of the component that executes tools.
Static capability scanners answer what an agent could reach, while observability systems explain what an agent already did. PermitWeave covers the runtime decision boundary: should this particular actor call this particular tool with these capabilities and side effects right now?
| Capability | Result |
|---|---|
| Deterministic policy evaluation | Same request and policy produce the same decision contract. |
| Explainable outcomes | Stable reason codes, matched rule IDs, and human-readable explanations. |
| Safe defaults | Default deny, strict schemas, bounded policy files, and no command execution. |
| Approval gates | Risky side effects can require an explicit approval group. |
| Redacted receipts | Sensitive-looking arguments are redacted before persistence. |
| Tamper evidence | Canonical JSON and a hash chain detect receipt modification. |
| CLI and HTTP | The same application service powers automation and local gateway use. |
| Policy contract tests | Declarative allow/deny/approval cases become a deterministic CI gate. |
| Signed policy bundles | Ed25519 provenance and expiry verification before compilation. |
| Decision evidence packs | Portable, deterministic review artifacts joining policy, contracts, receipts, and decision summaries. |
| Policy impact analysis | Compare baseline and candidate authorization behavior before rollout, with CI drift gates. |
| Session policy sequences | Simulate ordered calls with bounded per-session limits and a first-blocked CI gate. |
| Side-effect-free explain | Inspect a decision without writing receipts or executing tools. |
PermitWeave supports Python 3.11+.
git clone https://github.com/Alqudimi/PermitWeave.git
cd PermitWeave
python -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'
permitweave validate examples/policy.yml
permitweave explain examples/policy.yml examples/read-request.json
permitweave decide examples/policy.yml examples/read-request.json
permitweave test-policy examples/policy.yml examples/policy-tests.yml
permitweave keygen --private-key .permitweave/private.pem --public-key .permitweave/public.pem
permitweave sign-policy examples/policy.yml .permitweave/policy.bundle.json \
--private-key .permitweave/private.pem --key-id local-dev --issuer local \
--expires-at 2030-01-01T00:00:00Z
permitweave verify-bundle .permitweave/policy.bundle.json \
--public-key .permitweave/public.pem
permitweave evidence .permitweave/policy.bundle.json .permitweave/evidence.json \
--public-key .permitweave/public.pem \
--generated-at 2026-08-19T12:00:00Z
permitweave verify-evidence .permitweave/evidence.json
permitweave verify-receipts .permitweave/receipts.jsonl
permitweave sequence examples/session-policy.yml examples/session-sequence.yml \
--output .permitweave/session-sequence.json --fail-on-blockThe example request is allowed and the token argument appears only as [REDACTED] in the receipt.
permitweave serve examples/policy.yml
curl -s http://127.0.0.1:8787/health
curl -s -X POST http://127.0.0.1:8787/v1/decisions \
-H 'content-type: application/json' \
-d @examples/read-request.jsonThe HTTP surface is intentionally small: GET /health, POST /v1/policies/validate, POST /v1/decisions/explain, POST /v1/decisions, and GET /v1/receipts/verify. The explain endpoint is side-effect-free; the decision endpoint appends a redacted receipt. Invalid payloads use FastAPI's structured validation response; domain errors fail closed with a stable code.
schema_version: 1
default_effect: DENY
rules:
- id: allow-read-repository
effect: ALLOW
tools: [read_repository]
required_capabilities: [repo:read]
side_effects: [PURE]
reason: Read-only repository access is allowed.
- id: approve-publish
effect: REQUIRE_APPROVAL
tools: [publish_release]
side_effects: [EXTERNAL_WRITE]
approval_group: release-managers
reason: Publishing requires human approval.Rules are evaluated in declaration order. Empty selectors mean “any value” for that selector. A request that matches no rule receives the policy's default_effect, which is DENY in the recommended secure baseline.
A policy is not complete when it parses; its intended behavior should be executable as a regression contract. examples/policy-tests.yml defines expected effects, reason codes, and matched rules. The same production decision engine runs these cases locally and in CI:
permitweave test-policy examples/policy.yml examples/policy-tests.yml
permitweave test-policy examples/policy.yml examples/policy-tests.yml \
--format junit --output policy-contracts.xmlA changed policy that breaks an expected allow, deny, or approval decision exits with code 1. JSON is convenient for automation, while JUnit integrates with CI test reporting. This feature deliberately does not call an LLM or execute any tool.
A policy file on disk is not automatically an approved policy. PermitWeave v0.3 can package the canonical policy document into an Ed25519-signed JSON envelope containing a key ID, issuer, creation time, expiry, and policy digest. decide-bundle verifies the signature and expiry before compiling or evaluating the embedded policy:
permitweave keygen --private-key .permitweave/private.pem \
--public-key .permitweave/public.pem
permitweave sign-policy examples/policy.yml .permitweave/policy.bundle.json \
--private-key .permitweave/private.pem \
--key-id release-key-2026 --issuer security-team \
--expires-at 2030-01-01T00:00:00Z
permitweave verify-bundle .permitweave/policy.bundle.json \
--public-key .permitweave/public.pem
permitweave decide-bundle .permitweave/policy.bundle.json \
examples/read-request.json --public-key .permitweave/public.pemVerification fails closed for tampering, an invalid digest, an expired or not-yet-valid bundle, an invalid key, or a signature made by another key. Private keys are operator inputs and must be stored outside Git; the public-key trust root is explicit and PermitWeave never fetches keys from the network. The unsigned commands remain useful for local development, but production policy loading should use verified bundles.
Before signing or deploying a candidate policy, compare it with the current baseline over a typed request corpus. The report classifies unchanged behavior, newly allowed or denied calls, approval changes, reason-code changes, and rule-match changes. Use --fail-on to make a forbidden behavioral delta fail CI.
permitweave impact examples/policy.yml examples/policy-candidate.yml \
examples/impact-corpus.yml --output .permitweave/impact.json \
--fail-on NEWLY_ALLOWED --fail-on NEWLY_DENIEDThe analysis is deterministic and reuses the production decision engine; it never executes tools, contacts a network, or calls an LLM. Its conclusion is limited to the supplied corpus, so it is a change-review signal rather than a proof of equivalence over every possible request.
See docs/impact-analysis.md for the schema, classifications, and CI guidance.
Some controls depend on session history rather than one request in isolation. v0.6 simulates an ordered request trace with deterministic in-memory counters. A rule can set max_calls_per_session; later matching calls receive SESSION_CALL_LIMIT and are reported as blocked without executing any tool:
- id: allow-read-once
effect: ALLOW
tools: [read_repository]
required_capabilities: [repo:read]
side_effects: [PURE]
max_calls_per_session: 1Run the example sequence as a review artifact or CI gate:
permitweave sequence examples/session-policy.yml examples/session-sequence.yml \
--output .permitweave/session-sequence.json --fail-on-blockThe example intentionally blocks the second repository read. The report includes per-step decisions, post-step counters, aggregate counts, first blocked position, and a canonical digest. It is a deterministic simulation, not a distributed session store, replay-protection mechanism, sandbox, or substitute for enforcement by the actual tool executor. See docs/session-sequences.md.
The evidence command creates one portable JSON artifact for change review or incident analysis. It records the verified bundle digest, signer key ID, issuer, policy digest and expiry, contract-suite status, receipt-chain status, and redacted decision summaries. It does not include private keys or request arguments. Pass --generated-at when a reproducible artifact is required; verify-evidence recomputes the pack digest without executing tools.
permitweave test-policy examples/policy.yml examples/policy-tests.yml \
--output .permitweave/contracts.json
permitweave evidence .permitweave/policy.bundle.json .permitweave/evidence.json \
--public-key .permitweave/public.pem \
--contract-report .permitweave/contracts.json \
--receipts .permitweave/receipts.jsonl \
--generated-at 2026-08-19T12:00:00Z
permitweave verify-evidence .permitweave/evidence.jsonThe pack is evidence material, not a compliance certification and not proof that the policy content is substantively safe. It is designed to connect existing deterministic artifacts without requiring a database, hosted dashboard, or remote attestation service.
CLI / HTTP adapter
|
v
Boundary validation -> ToolCallRequest -> CompiledPolicy -> DecisionEngine
|
+------------------------------+------------------+
| |
explain (no I/O) decide -> redaction
|
hash-linked receipt
Policy contract suite -> same DecisionEngine -> JSON/JUnit CI report
The domain models and decision engine do not depend on FastAPI, Click, filesystem paths, subprocesses, sockets, or model APIs. Adapters are responsible for parsing and transport; the application layer coordinates use cases; the receipt sink is append-only and verifiable.
PermitWeave never executes a requested tool, follows a URL, imports user modules, evaluates expressions, or forwards credentials. Policy and request inputs are bounded and validated at the edge. Receipts contain redacted request data, not the original payload. The default server binding is localhost. These controls reduce risk but do not replace process isolation, authorization at the executor, network egress controls, or a sandbox for untrusted code.
make install
make test
make contract-test
make lint
make typecheck
make audit
make buildTests cover policy precedence, default deny, approval gates, destructive side effects, malformed input, redaction, receipt integrity, API behavior, contract mismatches, JUnit output, and regression paths. Run make benchmark to measure the local decision path; the script reports median, p95, and maximum latency without turning one machine's result into a universal performance claim. A representative local run over 1,000 decisions reported median 0.0069 ms and p95 0.0108 ms; rerun it on your hardware before using the numbers operationally.
The next compatible slices are ToolAtlas manifest import, an MCP adapter, SQLite indexing, OpenTelemetry spans, and a distributed gateway mode. v0.6 adds history-aware sequence simulation while keeping session state explicit, bounded, and offline-first. Each feature must preserve the domain contract and keep the core free of execution privileges.
Contributions should include a focused test and preserve deterministic output. Read CONTRIBUTING.md, SECURITY.md, and CODE_OF_CONDUCT.md before opening an issue or pull request. Please report vulnerabilities privately according to the security policy.
MIT © 2026 Abdulaziz Alqudimi