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.
Files changed (101) hide show
  1. parapetai_agent-0.9.0/.gitignore +38 -0
  2. parapetai_agent-0.9.0/LICENSE +21 -0
  3. parapetai_agent-0.9.0/PKG-INFO +442 -0
  4. parapetai_agent-0.9.0/README.md +380 -0
  5. parapetai_agent-0.9.0/conformance/README.md +16 -0
  6. parapetai_agent-0.9.0/examples/README.md +158 -0
  7. parapetai_agent-0.9.0/examples/adk_sample_01/README.md +98 -0
  8. parapetai_agent-0.9.0/examples/adk_webapp/README.md +120 -0
  9. parapetai_agent-0.9.0/examples/maf_cli/README.md +138 -0
  10. parapetai_agent-0.9.0/examples/maf_sample_01/README.md +78 -0
  11. parapetai_agent-0.9.0/examples/maf_sample_02/README.md +46 -0
  12. parapetai_agent-0.9.0/examples/maf_sample_03/README.md +34 -0
  13. parapetai_agent-0.9.0/examples/maf_sample_04/README.md +46 -0
  14. parapetai_agent-0.9.0/examples/maf_sample_05/README.md +48 -0
  15. parapetai_agent-0.9.0/examples/maf_sample_06/README.md +55 -0
  16. parapetai_agent-0.9.0/examples/maf_sample_07/README.md +42 -0
  17. parapetai_agent-0.9.0/examples/quickdemo_adk/README.md +102 -0
  18. parapetai_agent-0.9.0/examples/quickdemo_maf/README.md +103 -0
  19. parapetai_agent-0.9.0/examples/same_prompt_every_framework/README.md +87 -0
  20. parapetai_agent-0.9.0/examples/ungoverned_vs_governed/README.md +35 -0
  21. parapetai_agent-0.9.0/gateway/README.md +54 -0
  22. parapetai_agent-0.9.0/gateway/deploy/azure/README.md +173 -0
  23. parapetai_agent-0.9.0/gateway/tests/__init__.py +0 -0
  24. parapetai_agent-0.9.0/gateway/tests/test_credential_forwarding.py +64 -0
  25. parapetai_agent-0.9.0/gateway/tests/test_fingerprint.py +140 -0
  26. parapetai_agent-0.9.0/gateway/tests/test_mcp_multi_target.py +173 -0
  27. parapetai_agent-0.9.0/gateway/tests/test_mcp_oauth.py +253 -0
  28. parapetai_agent-0.9.0/gateway/tests/test_observations.py +131 -0
  29. parapetai_agent-0.9.0/gateway/tests/test_prompt_logging.py +103 -0
  30. parapetai_agent-0.9.0/gateway/tests/test_response_relay.py +42 -0
  31. parapetai_agent-0.9.0/gateway/tests/test_review_approvals.py +349 -0
  32. parapetai_agent-0.9.0/gateway/tests/test_review_responses.py +128 -0
  33. parapetai_agent-0.9.0/gateway/tests/test_streaming.py +228 -0
  34. parapetai_agent-0.9.0/mcp-server/LICENSE +21 -0
  35. parapetai_agent-0.9.0/mcp-server/README.md +69 -0
  36. parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/adk/README.md +211 -0
  37. parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/langgraph/README.md +221 -0
  38. parapetai_agent-0.9.0/mcp-server/src/parapetai_mcp/skills/quickdemo/templates/maf/README.md +212 -0
  39. parapetai_agent-0.9.0/mcp-server/tests/test_audit.py +282 -0
  40. parapetai_agent-0.9.0/pyproject.toml +202 -0
  41. parapetai_agent-0.9.0/src/parapetai_agent/__init__.py +261 -0
  42. parapetai_agent-0.9.0/src/parapetai_agent/_exceptions.py +46 -0
  43. parapetai_agent-0.9.0/src/parapetai_agent/_hhem.py +113 -0
  44. parapetai_agent-0.9.0/src/parapetai_agent/adk.py +1163 -0
  45. parapetai_agent-0.9.0/src/parapetai_agent/content_checks.py +244 -0
  46. parapetai_agent-0.9.0/src/parapetai_agent/control_plane.py +810 -0
  47. parapetai_agent-0.9.0/src/parapetai_agent/corroboration.py +207 -0
  48. parapetai_agent-0.9.0/src/parapetai_agent/govern.py +725 -0
  49. parapetai_agent-0.9.0/src/parapetai_agent/governance_runtime.py +527 -0
  50. parapetai_agent-0.9.0/src/parapetai_agent/groundedness.py +257 -0
  51. parapetai_agent-0.9.0/src/parapetai_agent/identity.py +51 -0
  52. parapetai_agent-0.9.0/src/parapetai_agent/identity_middleware.py +171 -0
  53. parapetai_agent-0.9.0/src/parapetai_agent/identity_store.py +210 -0
  54. parapetai_agent-0.9.0/src/parapetai_agent/langgraph.py +574 -0
  55. parapetai_agent-0.9.0/src/parapetai_agent/maf.py +1647 -0
  56. parapetai_agent-0.9.0/src/parapetai_agent/otel/__init__.py +0 -0
  57. parapetai_agent-0.9.0/src/parapetai_agent/otel/openinference.py +114 -0
  58. parapetai_agent-0.9.0/src/parapetai_agent/pep_identity.py +118 -0
  59. parapetai_agent-0.9.0/src/parapetai_agent/policy/__init__.py +0 -0
  60. parapetai_agent-0.9.0/src/parapetai_agent/policy/cost_tracker.py +185 -0
  61. parapetai_agent-0.9.0/src/parapetai_agent/policy/default_policies/00-base.cedar +32 -0
  62. parapetai_agent-0.9.0/src/parapetai_agent/policy/default_policies/entities.json +1 -0
  63. parapetai_agent-0.9.0/src/parapetai_agent/policy/engine.py +670 -0
  64. parapetai_agent-0.9.0/src/parapetai_agent/policy/hooks.py +265 -0
  65. parapetai_agent-0.9.0/src/parapetai_agent/policy/pricing.py +110 -0
  66. parapetai_agent-0.9.0/src/parapetai_agent/providers/__init__.py +0 -0
  67. parapetai_agent-0.9.0/src/parapetai_agent/providers/parsers.py +324 -0
  68. parapetai_agent-0.9.0/src/parapetai_agent/py.typed +0 -0
  69. parapetai_agent-0.9.0/src/parapetai_agent/response_judge.py +466 -0
  70. parapetai_agent-0.9.0/src/parapetai_agent/scoped_data.py +343 -0
  71. parapetai_agent-0.9.0/src/parapetai_agent/signing.py +26 -0
  72. parapetai_agent-0.9.0/src/parapetai_agent/token_identity.py +214 -0
  73. parapetai_agent-0.9.0/src/parapetai_agent/vendor_calls.py +118 -0
  74. parapetai_agent-0.9.0/tests/conftest.py +62 -0
  75. parapetai_agent-0.9.0/tests/test_adk.py +967 -0
  76. parapetai_agent-0.9.0/tests/test_approvals.py +352 -0
  77. parapetai_agent-0.9.0/tests/test_conformance_frameworks.py +282 -0
  78. parapetai_agent-0.9.0/tests/test_content_checks.py +262 -0
  79. parapetai_agent-0.9.0/tests/test_control_plane_client.py +393 -0
  80. parapetai_agent-0.9.0/tests/test_corroboration.py +152 -0
  81. parapetai_agent-0.9.0/tests/test_cost_policy_integration.py +108 -0
  82. parapetai_agent-0.9.0/tests/test_cost_pricing.py +70 -0
  83. parapetai_agent-0.9.0/tests/test_cost_tracker.py +121 -0
  84. parapetai_agent-0.9.0/tests/test_govern.py +332 -0
  85. parapetai_agent-0.9.0/tests/test_govern_control_plane.py +258 -0
  86. parapetai_agent-0.9.0/tests/test_governance_surface_parity.py +165 -0
  87. parapetai_agent-0.9.0/tests/test_groundedness.py +226 -0
  88. parapetai_agent-0.9.0/tests/test_hhem.py +59 -0
  89. parapetai_agent-0.9.0/tests/test_hooks.py +369 -0
  90. parapetai_agent-0.9.0/tests/test_identity_middleware.py +177 -0
  91. parapetai_agent-0.9.0/tests/test_identity_store.py +92 -0
  92. parapetai_agent-0.9.0/tests/test_langgraph.py +350 -0
  93. parapetai_agent-0.9.0/tests/test_litellm_judge.py +292 -0
  94. parapetai_agent-0.9.0/tests/test_maf.py +2801 -0
  95. parapetai_agent-0.9.0/tests/test_openinference.py +60 -0
  96. parapetai_agent-0.9.0/tests/test_pep_identity.py +344 -0
  97. parapetai_agent-0.9.0/tests/test_policy_engine_stages.py +221 -0
  98. parapetai_agent-0.9.0/tests/test_policy_review.py +325 -0
  99. parapetai_agent-0.9.0/tests/test_response_judge.py +218 -0
  100. parapetai_agent-0.9.0/tests/test_token_identity.py +127 -0
  101. 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
+ [![CI](https://github.com/Parapet-run/parapet-agenticai-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Parapet-run/parapet-agenticai-sdk/actions/workflows/ci.yml)
66
+ [![Docs](https://github.com/Parapet-run/parapet-agenticai-sdk/actions/workflows/docs.yml/badge.svg)](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.