parapetai-agent 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parapetai_agent-0.9.0/.gitignore +38 -0
- parapetai_agent-0.9.0/LICENSE +21 -0
- parapetai_agent-0.9.0/PKG-INFO +442 -0
- parapetai_agent-0.9.0/README.md +380 -0
- parapetai_agent-0.9.0/conformance/README.md +16 -0
- parapetai_agent-0.9.0/examples/README.md +158 -0
- parapetai_agent-0.9.0/examples/adk_sample_01/README.md +98 -0
- parapetai_agent-0.9.0/examples/adk_webapp/README.md +120 -0
- parapetai_agent-0.9.0/examples/maf_cli/README.md +138 -0
- parapetai_agent-0.9.0/examples/maf_sample_01/README.md +78 -0
- parapetai_agent-0.9.0/examples/maf_sample_02/README.md +46 -0
- parapetai_agent-0.9.0/examples/maf_sample_03/README.md +34 -0
- parapetai_agent-0.9.0/examples/maf_sample_04/README.md +46 -0
- parapetai_agent-0.9.0/examples/maf_sample_05/README.md +48 -0
- parapetai_agent-0.9.0/examples/maf_sample_06/README.md +55 -0
- parapetai_agent-0.9.0/examples/maf_sample_07/README.md +42 -0
- parapetai_agent-0.9.0/examples/quickdemo_adk/README.md +102 -0
- parapetai_agent-0.9.0/examples/quickdemo_maf/README.md +103 -0
- parapetai_agent-0.9.0/examples/same_prompt_every_framework/README.md +87 -0
- parapetai_agent-0.9.0/examples/ungoverned_vs_governed/README.md +35 -0
- parapetai_agent-0.9.0/gateway/README.md +54 -0
- parapetai_agent-0.9.0/gateway/deploy/azure/README.md +173 -0
- parapetai_agent-0.9.0/gateway/tests/__init__.py +0 -0
- parapetai_agent-0.9.0/gateway/tests/test_credential_forwarding.py +64 -0
- parapetai_agent-0.9.0/gateway/tests/test_fingerprint.py +140 -0
- parapetai_agent-0.9.0/gateway/tests/test_mcp_multi_target.py +173 -0
- parapetai_agent-0.9.0/gateway/tests/test_mcp_oauth.py +253 -0
- parapetai_agent-0.9.0/gateway/tests/test_observations.py +131 -0
- parapetai_agent-0.9.0/gateway/tests/test_prompt_logging.py +103 -0
- parapetai_agent-0.9.0/gateway/tests/test_response_relay.py +42 -0
- parapetai_agent-0.9.0/gateway/tests/test_review_approvals.py +349 -0
- parapetai_agent-0.9.0/gateway/tests/test_review_responses.py +128 -0
- parapetai_agent-0.9.0/gateway/tests/test_streaming.py +228 -0
- parapetai_agent-0.9.0/mcp-server/LICENSE +21 -0
- parapetai_agent-0.9.0/mcp-server/README.md +69 -0
- parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/adk/README.md +211 -0
- parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/langgraph/README.md +221 -0
- parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/maf/README.md +212 -0
- parapetai_agent-0.9.0/mcp-server/tests/test_audit.py +282 -0
- parapetai_agent-0.9.0/pyproject.toml +202 -0
- parapetai_agent-0.9.0/src/parapetai_agent/__init__.py +261 -0
- parapetai_agent-0.9.0/src/parapetai_agent/_exceptions.py +46 -0
- parapetai_agent-0.9.0/src/parapetai_agent/_hhem.py +113 -0
- parapetai_agent-0.9.0/src/parapetai_agent/adk.py +1163 -0
- parapetai_agent-0.9.0/src/parapetai_agent/content_checks.py +244 -0
- parapetai_agent-0.9.0/src/parapetai_agent/control_plane.py +810 -0
- parapetai_agent-0.9.0/src/parapetai_agent/corroboration.py +207 -0
- parapetai_agent-0.9.0/src/parapetai_agent/govern.py +725 -0
- parapetai_agent-0.9.0/src/parapetai_agent/governance_runtime.py +527 -0
- parapetai_agent-0.9.0/src/parapetai_agent/groundedness.py +257 -0
- parapetai_agent-0.9.0/src/parapetai_agent/identity.py +51 -0
- parapetai_agent-0.9.0/src/parapetai_agent/identity_middleware.py +171 -0
- parapetai_agent-0.9.0/src/parapetai_agent/identity_store.py +210 -0
- parapetai_agent-0.9.0/src/parapetai_agent/langgraph.py +574 -0
- parapetai_agent-0.9.0/src/parapetai_agent/maf.py +1647 -0
- parapetai_agent-0.9.0/src/parapetai_agent/otel/__init__.py +0 -0
- parapetai_agent-0.9.0/src/parapetai_agent/otel/openinference.py +114 -0
- parapetai_agent-0.9.0/src/parapetai_agent/pep_identity.py +118 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/__init__.py +0 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/cost_tracker.py +185 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/default_policies/00-base.cedar +32 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/default_policies/entities.json +1 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/engine.py +670 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/hooks.py +265 -0
- parapetai_agent-0.9.0/src/parapetai_agent/policy/pricing.py +110 -0
- parapetai_agent-0.9.0/src/parapetai_agent/providers/__init__.py +0 -0
- parapetai_agent-0.9.0/src/parapetai_agent/providers/parsers.py +324 -0
- parapetai_agent-0.9.0/src/parapetai_agent/py.typed +0 -0
- parapetai_agent-0.9.0/src/parapetai_agent/response_judge.py +466 -0
- parapetai_agent-0.9.0/src/parapetai_agent/scoped_data.py +343 -0
- parapetai_agent-0.9.0/src/parapetai_agent/signing.py +26 -0
- parapetai_agent-0.9.0/src/parapetai_agent/token_identity.py +214 -0
- parapetai_agent-0.9.0/src/parapetai_agent/vendor_calls.py +118 -0
- parapetai_agent-0.9.0/tests/conftest.py +62 -0
- parapetai_agent-0.9.0/tests/test_adk.py +967 -0
- parapetai_agent-0.9.0/tests/test_approvals.py +352 -0
- parapetai_agent-0.9.0/tests/test_conformance_frameworks.py +282 -0
- parapetai_agent-0.9.0/tests/test_content_checks.py +262 -0
- parapetai_agent-0.9.0/tests/test_control_plane_client.py +393 -0
- parapetai_agent-0.9.0/tests/test_corroboration.py +152 -0
- parapetai_agent-0.9.0/tests/test_cost_policy_integration.py +108 -0
- parapetai_agent-0.9.0/tests/test_cost_pricing.py +70 -0
- parapetai_agent-0.9.0/tests/test_cost_tracker.py +121 -0
- parapetai_agent-0.9.0/tests/test_govern.py +332 -0
- parapetai_agent-0.9.0/tests/test_govern_control_plane.py +258 -0
- parapetai_agent-0.9.0/tests/test_governance_surface_parity.py +165 -0
- parapetai_agent-0.9.0/tests/test_groundedness.py +226 -0
- parapetai_agent-0.9.0/tests/test_hhem.py +59 -0
- parapetai_agent-0.9.0/tests/test_hooks.py +369 -0
- parapetai_agent-0.9.0/tests/test_identity_middleware.py +177 -0
- parapetai_agent-0.9.0/tests/test_identity_store.py +92 -0
- parapetai_agent-0.9.0/tests/test_langgraph.py +350 -0
- parapetai_agent-0.9.0/tests/test_litellm_judge.py +292 -0
- parapetai_agent-0.9.0/tests/test_maf.py +2801 -0
- parapetai_agent-0.9.0/tests/test_openinference.py +60 -0
- parapetai_agent-0.9.0/tests/test_pep_identity.py +344 -0
- parapetai_agent-0.9.0/tests/test_policy_engine_stages.py +221 -0
- parapetai_agent-0.9.0/tests/test_policy_review.py +325 -0
- parapetai_agent-0.9.0/tests/test_response_judge.py +218 -0
- parapetai_agent-0.9.0/tests/test_token_identity.py +127 -0
- parapetai_agent-0.9.0/tests/test_vendor_calls.py +119 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
# Envs
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
.env
|
|
12
|
+
# Tooling caches
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
# Built docs site (mkdocs build output) -- generated by CI, never committed
|
|
17
|
+
site/
|
|
18
|
+
# Lockfile (this is a library, not an app)
|
|
19
|
+
uv.lock
|
|
20
|
+
# PEP identity / secrets — never commit
|
|
21
|
+
.parapetai/
|
|
22
|
+
*.key
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Stale backups from extraction — never commit
|
|
27
|
+
_to_delete/
|
|
28
|
+
*.bak
|
|
29
|
+
# git bundle transfer artifacts. They carry full history, so an accidental
|
|
30
|
+
# `git add -A` in a PUBLIC repo is how private history would leak.
|
|
31
|
+
*.bundle
|
|
32
|
+
|
|
33
|
+
# gateway/deploy/azure -- generated state from running the deploy scripts
|
|
34
|
+
# (the provisioned control-plane agent identity, the last-applied Container
|
|
35
|
+
# App manifest for debugging). Real values, not templates -- .env.example
|
|
36
|
+
# alongside them IS meant to be committed.
|
|
37
|
+
gateway/deploy/azure/.last-manifest-*.yaml
|
|
38
|
+
gateway/deploy/azure/.gateway-agent.env
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parapet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: parapetai-agent
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Open-source in-process governance for AI agent frameworks: Cedar-governed model/tool calls via build_middleware()/GovernedAgent, plus the Cedar engine, request/decision shapes, PEP<->control-plane protocol client, and Ed25519 PEP identity they run on.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Parapet-run/parapet-agenticai-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/Parapet-run/parapet-agenticai-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/Parapet-run/parapet-agenticai-sdk/issues
|
|
8
|
+
Author: Parapet
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent,authorization,cedar,governance,guardrails,llm,policy
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: cedarpy<5.0,>=4.0.0
|
|
23
|
+
Requires-Dist: cryptography<51.0,>=49.0
|
|
24
|
+
Requires-Dist: httpx<1.0,>=0.28
|
|
25
|
+
Requires-Dist: opentelemetry-api<2.0,>=1.20
|
|
26
|
+
Requires-Dist: structlog<26.0,>=24.4
|
|
27
|
+
Provides-Extra: adk
|
|
28
|
+
Requires-Dist: google-adk<3.0,>=2.7; extra == 'adk'
|
|
29
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0,>=1.27; extra == 'adk'
|
|
30
|
+
Requires-Dist: opentelemetry-sdk<2.0,>=1.27; extra == 'adk'
|
|
31
|
+
Provides-Extra: corroboration
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-aiohttp-client<1.0,>=0.48; extra == 'corroboration'
|
|
33
|
+
Requires-Dist: opentelemetry-instrumentation-grpc<1.0,>=0.48; extra == 'corroboration'
|
|
34
|
+
Requires-Dist: opentelemetry-instrumentation-httpx<1.0,>=0.48; extra == 'corroboration'
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.48; extra == 'corroboration'
|
|
36
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3<1.0,>=0.48; extra == 'corroboration'
|
|
37
|
+
Requires-Dist: opentelemetry-sdk<2.0,>=1.27; extra == 'corroboration'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
40
|
+
Requires-Dist: opentelemetry-sdk<2.0,>=1.20; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
43
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
45
|
+
Provides-Extra: docs
|
|
46
|
+
Requires-Dist: mkdocs-material<10.0,>=9.5; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocs<2.0,>=1.6; extra == 'docs'
|
|
48
|
+
Provides-Extra: judge
|
|
49
|
+
Requires-Dist: litellm<2.0,>=1.60; extra == 'judge'
|
|
50
|
+
Provides-Extra: langgraph
|
|
51
|
+
Requires-Dist: langchain<2.0,>=1.3; extra == 'langgraph'
|
|
52
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0,>=1.27; extra == 'langgraph'
|
|
53
|
+
Requires-Dist: opentelemetry-sdk<2.0,>=1.27; extra == 'langgraph'
|
|
54
|
+
Provides-Extra: maf
|
|
55
|
+
Requires-Dist: agent-framework<2.0,>=1.13; extra == 'maf'
|
|
56
|
+
Requires-Dist: mcp<2.0,>=1.24; extra == 'maf'
|
|
57
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0,>=1.27; extra == 'maf'
|
|
58
|
+
Requires-Dist: opentelemetry-sdk<2.0,>=1.27; extra == 'maf'
|
|
59
|
+
Provides-Extra: web
|
|
60
|
+
Requires-Dist: starlette<2.0,>=0.38; extra == 'web'
|
|
61
|
+
Description-Content-Type: text/markdown
|
|
62
|
+
|
|
63
|
+
# parapet-agenticai-sdk
|
|
64
|
+
|
|
65
|
+
[](https://github.com/Parapet-run/parapet-agenticai-sdk/actions/workflows/ci.yml)
|
|
66
|
+
[](https://parapet-run.github.io/parapet-agenticai-sdk/)
|
|
67
|
+
|
|
68
|
+
📖 **[Full documentation](https://parapet-run.github.io/parapet-agenticai-sdk/)** — installation, quickstart, framework guides (MAF/ADK), the `parapetai-mcp` CLI, and the complete API reference.
|
|
69
|
+
|
|
70
|
+
**In-process runtime governance for AI agents.** Wrap the agent you already
|
|
71
|
+
have, and every model call and tool call becomes a [Cedar](https://www.cedarpolicy.com/)
|
|
72
|
+
policy decision — **default-deny, fail-closed, content-free audit** — enforced
|
|
73
|
+
inside your process, before anything happens.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install parapetai-agent
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> Python import name: `parapetai_agent`. Repo: `Parapet-run/parapet-agenticai-sdk`.
|
|
80
|
+
|
|
81
|
+
Parapet is the enforcement point that lives *inside* the agent. A control tower
|
|
82
|
+
can observe your fleet and, at worst, kill an agent; the network gateway can
|
|
83
|
+
inspect traffic at the wire. Neither can decide — deterministically, in the
|
|
84
|
+
process, before the fact — whether *this* caller may take *this* action with
|
|
85
|
+
*these* arguments, and stop just that one call while the agent keeps working.
|
|
86
|
+
That decision is what this SDK makes.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## What it does
|
|
91
|
+
|
|
92
|
+
Three governance surfaces, one Cedar decision each, all in-process:
|
|
93
|
+
|
|
94
|
+
| Stage | Question | Mechanism |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| **Input** (`pre`) | Should the model even see this prompt? | PII / secrets / injection / profanity scanners + a Cedar `model_call` decision (topic scope, trust tier) — **before** the model is called. |
|
|
97
|
+
| **Tool call** | May the model run *this* tool with *these* args, as *this* caller? | Cedar `tool_call` authorization by name, arguments, and identity role. A denied call never executes. |
|
|
98
|
+
| **Output** (`post`) | Is the answer grounded and on-policy? | Groundedness (HHEM / lexical) + an SLM judge score the response; a Cedar `post`-stage decision applies their verdicts **before** the user sees a word. |
|
|
99
|
+
|
|
100
|
+
Every decision produces a **content-free, signed audit record** — verdict,
|
|
101
|
+
determining policy, stage, identity, latency, policy generation. Your prompts
|
|
102
|
+
and the model's responses never leave the process.
|
|
103
|
+
|
|
104
|
+
## Quickstart
|
|
105
|
+
|
|
106
|
+
### Fastest path: scaffold it with Claude Code + `parapetai-mcp`
|
|
107
|
+
|
|
108
|
+
If you're using [Claude Code](https://claude.com/claude-code), skip writing
|
|
109
|
+
any of this by hand. `parapetai-mcp` is an MCP server that logs in to a
|
|
110
|
+
control plane, provisions an agent, and either generates a runnable demo
|
|
111
|
+
project from nothing, retrofits Parapet into a project you already have, or
|
|
112
|
+
audits one for ungoverned model/tool calls — Claude does the file edits.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pipx install parapetai-mcp
|
|
116
|
+
parapetai-mcp init # installs the parapet-* skills into .claude/skills/
|
|
117
|
+
claude mcp add parapet -e PARAPETAI_CONTROL_PLANE_URL=https://app.parapet.run -- parapetai-mcp serve
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then, in Claude Code:
|
|
121
|
+
|
|
122
|
+
- **"Build me a Parapet demo"** → the `parapet-quickdemo` skill generates a
|
|
123
|
+
small, runnable, identity-based governance project from scratch (Google
|
|
124
|
+
ADK, Microsoft Agent Framework, or LangGraph/LangChain — your choice),
|
|
125
|
+
against a real control plane you can click into.
|
|
126
|
+
- **"Add Parapet to my agent"** (in an existing `agent_framework` or
|
|
127
|
+
`google.adk` project) → the `parapet-maf` / `parapet-adk` skill
|
|
128
|
+
provisions an agent and instruments your existing code.
|
|
129
|
+
- **"Audit my codebase for governance risks"** → the `parapet-audit` skill
|
|
130
|
+
runs a local, read-only static scan (no control-plane call) for
|
|
131
|
+
ungoverned model/tool calls, scored high/medium/low; `parapet-audit-fix`
|
|
132
|
+
then wraps the flagged sites in `GovernedAgent`/`GovernedRunner` as a
|
|
133
|
+
separate, explicit step.
|
|
134
|
+
|
|
135
|
+
No `pipx`? `brew install pipx` (macOS) or
|
|
136
|
+
`python3 -m pip install --user pipx && pipx ensurepath`. Full reference:
|
|
137
|
+
[parapetai-mcp docs](https://parapet-run.github.io/parapet-agenticai-sdk/cli/parapetai-mcp/).
|
|
138
|
+
|
|
139
|
+
### Any framework — `Governor`
|
|
140
|
+
|
|
141
|
+
Three calls at whatever hook points your framework already has. No adapter, no
|
|
142
|
+
framework dependency — works with LangGraph, CrewAI, the OpenAI Agents SDK, or
|
|
143
|
+
a plain `while` loop:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from parapetai_agent import Governor, GovernanceDenied
|
|
147
|
+
|
|
148
|
+
# Policy authored in the control plane, pulled and kept fresh in the background.
|
|
149
|
+
gov = Governor.from_control_plane(
|
|
150
|
+
"https://control.parapet.example",
|
|
151
|
+
agent_secret="...", # issued once at provisioning
|
|
152
|
+
policy_dir="./policies", # seed + where the last-known-good bundle lives
|
|
153
|
+
persist_policy_dir="./policies",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
gov.check_input(prompt, roles=["OrderViewer"]) # before the model
|
|
157
|
+
gov.authorize_tool("delete_incident", {...}) # before a tool runs -> may raise
|
|
158
|
+
gov.check_output(answer, sources=[doc]) # after the model
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Every decision is evaluated **locally, in-process** — the control plane is
|
|
162
|
+
never on the decision path, so it can be down without blocking a call. When it
|
|
163
|
+
*is* unreachable at startup, the agent falls back to the last bundle on disk
|
|
164
|
+
and keeps enforcing it; with nothing on disk there is no policy to enforce, and
|
|
165
|
+
it fails closed rather than running ungoverned.
|
|
166
|
+
|
|
167
|
+
For local development or an air-gapped install, use
|
|
168
|
+
`Governor.from_policy_dir("./policies")` instead — same three calls, policy
|
|
169
|
+
from files you manage.
|
|
170
|
+
|
|
171
|
+
Denials raise `GovernanceDenied`; pass `raise_on_deny=False` to get the
|
|
172
|
+
`Decision` back and branch on it yourself.
|
|
173
|
+
|
|
174
|
+
### Three outcomes, not two — allow, deny, **review**
|
|
175
|
+
|
|
176
|
+
A policy can hold a call for a person instead of refusing it outright. Annotate
|
|
177
|
+
a `forbid` with `@action("review")` in the control plane, and the SDK raises
|
|
178
|
+
`GovernanceReviewRequired` with a ticket you can wait on:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from parapetai_agent import Governor, GovernanceReviewRequired
|
|
182
|
+
|
|
183
|
+
gov = Governor.from_control_plane(policy_dir="./policies")
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
gov.authorize_tool("transition_issue", {"issue": "INC-42", "state": "closed"})
|
|
187
|
+
except GovernanceReviewRequired as held:
|
|
188
|
+
print(held.review_id) # queued for a human, agent NOT blocked
|
|
189
|
+
|
|
190
|
+
if gov.wait_for_approval(held, timeout=300): # opt in to blocking
|
|
191
|
+
transition_issue("INC-42") # approved — valid for THIS call, once
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The parts worth knowing:
|
|
195
|
+
|
|
196
|
+
- **A held call is a deny until someone approves it.** `Decision.allowed` stays
|
|
197
|
+
`False`, and `GovernanceReviewRequired` **subclasses `GovernanceDenied`** — so
|
|
198
|
+
code written before approvals existed keeps blocking a held call. Upgrading
|
|
199
|
+
the SDK can never start executing one.
|
|
200
|
+
- **It does not block by default.** You get a ticket and continue;
|
|
201
|
+
`wait_for_approval()` is opt-in. It returns `False` for *every* non-approval
|
|
202
|
+
(denied, expired, timed out, control plane unreachable), so there is one thing
|
|
203
|
+
to check and the safe answer is the default.
|
|
204
|
+
- **A grant is single-use and bound to that exact call.** Approving "close
|
|
205
|
+
INC-42" cannot be replayed onto INC-43, and cannot be spent twice.
|
|
206
|
+
- **An unreachable control plane cannot soften a decision.** Cedar still decides
|
|
207
|
+
locally; if the queue is unreachable, `review_id` is `None` and the call stays
|
|
208
|
+
denied. Approvals are something a connected PEP gains, never something local
|
|
209
|
+
enforcement depends on.
|
|
210
|
+
- **Prompts are never sent to the queue.** A tool call's arguments are shown to
|
|
211
|
+
the approver (they are what the policy matched on); a `check_input` /
|
|
212
|
+
`check_output` call sends only a digest.
|
|
213
|
+
|
|
214
|
+
See **[docs/adr/0009](docs/adr/0009-approval-loop.md)** for the design.
|
|
215
|
+
|
|
216
|
+
### A specific framework — `GovernedAgent` / `GovernedRunner`
|
|
217
|
+
|
|
218
|
+
Pick your framework and install its extra; the rest of the interface stays the
|
|
219
|
+
same — same `agent_id=` / `policy_dir=` / `control_plane_url=` kwargs, same
|
|
220
|
+
`GovernanceDenied`, same identity API, whichever you choose. `maf` and `adk`
|
|
221
|
+
are independent: installing one never pulls in the other's SDK.
|
|
222
|
+
|
|
223
|
+
**Microsoft Agent Framework** (`pip install parapetai-agent[maf]`) —
|
|
224
|
+
`GovernedAgent` is a drop-in replacement for `agent_framework.Agent`:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from parapetai_agent import GovernedAgent as Agent, GovernanceDenied
|
|
228
|
+
|
|
229
|
+
agent = Agent(
|
|
230
|
+
name="support",
|
|
231
|
+
instructions="Help the customer.",
|
|
232
|
+
tools=[lookup_order],
|
|
233
|
+
agent_id="pa-e3931c464751",
|
|
234
|
+
control_plane_url="https://control.parapet.example",
|
|
235
|
+
agent_secret="...", # issued once at provisioning
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
result = await agent.run("Where is order 1234?")
|
|
240
|
+
except GovernanceDenied as denied:
|
|
241
|
+
print(denied.reason) # e.g. "servicenow_destructive_denied"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Already have your own middleware chain? `build_middleware()` returns the same
|
|
245
|
+
governance as a plain middleware:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from parapetai_agent import build_middleware
|
|
249
|
+
|
|
250
|
+
mw = build_middleware(
|
|
251
|
+
agent_id="pa-e3931c464751",
|
|
252
|
+
control_plane_url="https://control.parapet.example",
|
|
253
|
+
agent_secret="...",
|
|
254
|
+
)
|
|
255
|
+
agent = SomeFrameworkAgent(..., middleware=[mw])
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Google ADK** (`pip install parapetai-agent[adk]`):
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from parapetai_agent.adk import GovernedRunner as Runner
|
|
262
|
+
|
|
263
|
+
runner = Runner(
|
|
264
|
+
app_name="support",
|
|
265
|
+
agent=root_agent,
|
|
266
|
+
session_service=session_service,
|
|
267
|
+
agent_id="pa-e3931c464751",
|
|
268
|
+
control_plane_url="https://control.parapet.example",
|
|
269
|
+
agent_secret="...",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
async for event in runner.run_async(user_id="alice", session_id=sid, new_message=message):
|
|
273
|
+
if event.error_code == "governance_denied":
|
|
274
|
+
print(event.error_message)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`GovernedAgent` and `GovernedRunner` are drop-in replacements for each
|
|
278
|
+
framework's own `Agent`/`Runner`. The class differs because each framework puts
|
|
279
|
+
its governable seam in a different place (MAF: `Agent(middleware=[...])`; ADK:
|
|
280
|
+
`Runner(plugins=[...])`), not because the integration differs. Building your own
|
|
281
|
+
chain instead? `build_middleware()` (MAF) and `build_plugin()` (ADK) return the
|
|
282
|
+
same governance to wire in yourself. Reaching for
|
|
283
|
+
`google.adk.runners.InMemoryRunner`? `parapetai_agent.adk.InMemoryGovernedRunner`
|
|
284
|
+
mirrors it exactly — same in-memory session/artifact/memory defaults, plus
|
|
285
|
+
governance.
|
|
286
|
+
|
|
287
|
+
## Can't change the app? Use the gateway
|
|
288
|
+
|
|
289
|
+
The SDK and the [gateway](gateway/) are the **same enforcement role in two
|
|
290
|
+
form factors** — both evaluate the same Cedar engine locally, in-process.
|
|
291
|
+
Embed the SDK when you can modify the agent; run the gateway when you can't,
|
|
292
|
+
or when the agent isn't Python at all.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
uvx parapetai-gateway # or run the container
|
|
296
|
+
export OPENAI_BASE_URL=http://localhost:8080/a/<agent-id>/v1 # in the app
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
That is the whole integration — no code change, and it works for a Node, Go,
|
|
300
|
+
or Java agent that could never `pip install` anything. They live in one repo
|
|
301
|
+
deliberately: the gateway imports this package's engine, parsers, and identity,
|
|
302
|
+
so splitting them is how the engine forks.
|
|
303
|
+
|
|
304
|
+
## Identity
|
|
305
|
+
|
|
306
|
+
Decisions are made about a **caller**, not just an agent. Bind one:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from parapetai_agent import set_identity, use_identity
|
|
310
|
+
|
|
311
|
+
set_identity("alice", claims={"oid": "..."}, roles=["OrderViewer"])
|
|
312
|
+
with use_identity("alice"):
|
|
313
|
+
await agent.run(...)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
In a web app, install `parapetai-agent[web]` and add `IdentityMiddleware`, which
|
|
317
|
+
lifts the caller identity off the incoming request (JWT/OIDC) automatically.
|
|
318
|
+
|
|
319
|
+
## Control plane — integration is an HTTP API
|
|
320
|
+
|
|
321
|
+
The SDK is useful stand-alone (point `policy_dir=` at local Cedar files), but in
|
|
322
|
+
production it speaks a small signed HTTP protocol to a **control plane** that
|
|
323
|
+
distributes policy and receives the audit stream. The SDK is the client; the
|
|
324
|
+
control plane is a separate service.
|
|
325
|
+
|
|
326
|
+
- **Policy in:** the SDK pulls a signed policy **bundle** (`GET /api/v1/bundle`),
|
|
327
|
+
caches it to disk, and hot-loads it into the engine. Requests are signed with
|
|
328
|
+
the agent's Ed25519 key; bundle freshness is an ETag (`304 Not Modified`).
|
|
329
|
+
- **Presence:** a periodic heartbeat (`POST /api/v1/fleet/heartbeat`) reports the
|
|
330
|
+
enforcing policy generation and can carry a key-rotation signal back.
|
|
331
|
+
- **Audit / telemetry out:** content-free decision records reach the control
|
|
332
|
+
plane as OTLP spans and logs (`POST /v1/traces`, `/v1/logs`) — see below.
|
|
333
|
+
|
|
334
|
+
Full endpoint reference, auth, and the signing contract: **[docs/CONTROL_PLANE_API.md](docs/CONTROL_PLANE_API.md)**.
|
|
335
|
+
|
|
336
|
+
## OTel to the control plane
|
|
337
|
+
|
|
338
|
+
Governance decisions are emitted as OpenTelemetry spans and shipped to the
|
|
339
|
+
control plane's OTLP receiver — the same standard OTLP/HTTP wire format any
|
|
340
|
+
collector speaks, so you can fan out to your own backend too.
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from parapetai_agent import configure_otel
|
|
344
|
+
|
|
345
|
+
configure_otel(
|
|
346
|
+
service_name="support-agent",
|
|
347
|
+
otlp_endpoint="https://control.parapet.example", # -> /v1/traces, /v1/logs
|
|
348
|
+
agent_secret="...", # sent as Bearer, identifies the agent
|
|
349
|
+
)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`build_middleware()` calls this **for you** once `control_plane_url` / `agent_secret`
|
|
353
|
+
resolve — `otlp_endpoint` defaults to `PARAPETAI_OTLP_ENDPOINT`, else the control
|
|
354
|
+
plane host. The spans are content-free by construction. Details and the span
|
|
355
|
+
schema: **[docs/OBSERVABILITY.md](docs/OBSERVABILITY.md)**.
|
|
356
|
+
|
|
357
|
+
## Extras
|
|
358
|
+
|
|
359
|
+
| Extra | Brings in | For |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| `maf` | `agent-framework`, `mcp`, OpenTelemetry SDK + OTLP exporter | Microsoft Agent Framework integration and OTel export |
|
|
362
|
+
| `adk` | `google-adk`, OpenTelemetry SDK + OTLP exporter | Google ADK integration and OTel export |
|
|
363
|
+
| `web` | `starlette` | `IdentityMiddleware`, JWT bearer extraction |
|
|
364
|
+
| `judge` | `litellm` | The provider-agnostic SLM-judge backend — Anthropic, Bedrock, Vertex, Groq, Ollama. Not needed for the default `slm` backend, which speaks the OpenAI wire. |
|
|
365
|
+
| _(base)_ | `cedarpy`, `httpx`, `cryptography`, `opentelemetry-api` | Cedar engine, control-plane protocol client, Ed25519 PEP identity |
|
|
366
|
+
|
|
367
|
+
The base install never imports a web framework or an agent framework — a CLI
|
|
368
|
+
script or background worker can depend on it without pulling either in.
|
|
369
|
+
|
|
370
|
+
## Invariants
|
|
371
|
+
|
|
372
|
+
These are security properties, not defaults you can tune away:
|
|
373
|
+
|
|
374
|
+
- **Fail closed.** An unparsed payload, an evaluation error, or a missing policy
|
|
375
|
+
denies. No exception path becomes an implicit allow.
|
|
376
|
+
- **Cedar is default-deny.** No matching `permit` is a Deny; `forbid` always
|
|
377
|
+
beats `permit`.
|
|
378
|
+
- **A bad bundle never empties the policy set.** Reload keeps the previous
|
|
379
|
+
policies on failure.
|
|
380
|
+
- **Prompt content is never logged** unless you explicitly opt in. The decision
|
|
381
|
+
audit record is content-free by construction, not by configuration.
|
|
382
|
+
- **A REVIEW is a deny, not a soft allow.** `Decision.allowed` is `False` for
|
|
383
|
+
`effect == "review"`, so a held call does not execute and any caller that
|
|
384
|
+
only checks `allowed` blocks it exactly as it blocks a denial. A review needs
|
|
385
|
+
unanimity: if any determining policy is a plain `forbid`, the deny stays hard.
|
|
386
|
+
See [ADR 0008](docs/adr/0008-review-decision-outcome.md).
|
|
387
|
+
|
|
388
|
+
## Project layout
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
src/parapetai_agent/
|
|
392
|
+
govern.py # Governor — the framework-neutral entry point (any framework)
|
|
393
|
+
_exceptions.py # GovernanceDenied / GovernanceReviewRequired — catchable
|
|
394
|
+
# without importing any framework
|
|
395
|
+
maf.py # GovernedAgent, build_middleware, configure_otel — the MAF integration
|
|
396
|
+
policy/ # Cedar engine, request/decision shapes, stage split
|
|
397
|
+
content_checks.py # PII / secrets / injection / profanity scanners (input guardrails)
|
|
398
|
+
groundedness.py # output groundedness (lexical default, HHEM optional)
|
|
399
|
+
_hhem.py # Vectara HHEM-2.1 backend (local or in-VPC service)
|
|
400
|
+
response_judge.py # SLM judge (rubric-scored output evals)
|
|
401
|
+
identity.py, identity_middleware.py, token_identity.py, identity_store.py
|
|
402
|
+
pep_identity.py # Ed25519 PEP keypair (load/create/rotate)
|
|
403
|
+
signing.py # the exact bytes a PEP and control plane sign/verify
|
|
404
|
+
control_plane.py # PEP -> control-plane HTTP client (bundle pull, heartbeat, key register)
|
|
405
|
+
otel/ # OpenInference span conventions
|
|
406
|
+
gateway/ # the PROXY PEP -- same Cedar engine, for apps that can't embed
|
|
407
|
+
mcp-server/ # parapetai-mcp: MCP server + SKILL.md for Claude Code
|
|
408
|
+
tests/ # pytest suite
|
|
409
|
+
conformance/ # per-framework proof the block happens in the real runtime
|
|
410
|
+
policies/ # Cedar sources used as engine fixtures
|
|
411
|
+
examples/ # runnable integrations: maf_sample_01..07, maf_cli,
|
|
412
|
+
# adk_sample_01, adk_webapp, ungoverned_vs_governed.
|
|
413
|
+
# The ones taking control_plane_url + agent_secret
|
|
414
|
+
# exercise provisioned identity end to end.
|
|
415
|
+
docs/ # ADRs, observability, the MAF integration pattern
|
|
416
|
+
docs/ # API + observability + architecture references, plus ADRs
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Docs
|
|
420
|
+
|
|
421
|
+
📖 **[parapet-run.github.io/parapet-agenticai-sdk](https://parapet-run.github.io/parapet-agenticai-sdk/)**
|
|
422
|
+
— installation, quickstart, framework guides, the `parapetai-mcp` CLI/skills
|
|
423
|
+
reference, and the full `Governor` / `GovernedAgent` / `GovernedRunner` /
|
|
424
|
+
`Decision` API reference, all in one hosted site. The pages below are the
|
|
425
|
+
same source files, browsable directly in the repo:
|
|
426
|
+
|
|
427
|
+
- [Architecture](docs/ARCHITECTURE.md) — stages, fail-closed, the trust boundary
|
|
428
|
+
- [Control-plane API](docs/CONTROL_PLANE_API.md) — the HTTP protocol the SDK speaks
|
|
429
|
+
- [Observability / OTel](docs/OBSERVABILITY.md) — decisions as content-free spans
|
|
430
|
+
- [Groundedness / HHEM](docs/GROUNDEDNESS_HHEM.md) — the output-faithfulness backends
|
|
431
|
+
- [ADR 0006](docs/adr/0006-cedar-policy-stage-and-action-annotations.md) — `@stage` / `@action` policy annotations
|
|
432
|
+
- [ADR 0008](docs/adr/0008-review-decision-outcome.md) — REVIEW as a third decision outcome
|
|
433
|
+
- [Examples](examples/) — a runnable authorization demo (base install, no model)
|
|
434
|
+
- [Contributing](CONTRIBUTING.md)
|
|
435
|
+
|
|
436
|
+
## Links
|
|
437
|
+
|
|
438
|
+
- Docs: https://parapet-run.github.io/parapet-agenticai-sdk/
|
|
439
|
+
- Source: https://github.com/Parapet-run/parapet-agenticai-sdk
|
|
440
|
+
- Issues: https://github.com/Parapet-run/parapet-agenticai-sdk/issues
|
|
441
|
+
|
|
442
|
+
MIT licensed.
|