agentguard-oss 0.4.1__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.
- agentguard_oss-0.4.1/.gitignore +13 -0
- agentguard_oss-0.4.1/CHANGELOG.md +137 -0
- agentguard_oss-0.4.1/LICENSE +22 -0
- agentguard_oss-0.4.1/PKG-INFO +225 -0
- agentguard_oss-0.4.1/README.md +160 -0
- agentguard_oss-0.4.1/agentguard/__init__.py +41 -0
- agentguard_oss-0.4.1/agentguard/__main__.py +3 -0
- agentguard_oss-0.4.1/agentguard/adapters/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/adapters/_common.py +54 -0
- agentguard_oss-0.4.1/agentguard/adapters/adk.py +23 -0
- agentguard_oss-0.4.1/agentguard/adapters/langchain.py +31 -0
- agentguard_oss-0.4.1/agentguard/adapters/mcp.py +5 -0
- agentguard_oss-0.4.1/agentguard/adapters/openai_agents.py +29 -0
- agentguard_oss-0.4.1/agentguard/adapters/python.py +3 -0
- agentguard_oss-0.4.1/agentguard/approval/__init__.py +16 -0
- agentguard_oss-0.4.1/agentguard/approval/base.py +49 -0
- agentguard_oss-0.4.1/agentguard/approval/cli.py +47 -0
- agentguard_oss-0.4.1/agentguard/approval/queue.py +90 -0
- agentguard_oss-0.4.1/agentguard/approval/slack.py +174 -0
- agentguard_oss-0.4.1/agentguard/approval/store.py +361 -0
- agentguard_oss-0.4.1/agentguard/approval/webhook.py +83 -0
- agentguard_oss-0.4.1/agentguard/audit/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/audit/chain.py +36 -0
- agentguard_oss-0.4.1/agentguard/audit/export.py +121 -0
- agentguard_oss-0.4.1/agentguard/audit/logger.py +108 -0
- agentguard_oss-0.4.1/agentguard/audit/reader.py +42 -0
- agentguard_oss-0.4.1/agentguard/audit/report.py +110 -0
- agentguard_oss-0.4.1/agentguard/audit/segments.py +46 -0
- agentguard_oss-0.4.1/agentguard/bench/__init__.py +1 -0
- agentguard_oss-0.4.1/agentguard/bench/holdout_cases.yaml +42 -0
- agentguard_oss-0.4.1/agentguard/bench/judge.py +203 -0
- agentguard_oss-0.4.1/agentguard/bench/judge_cases.yaml +83 -0
- agentguard_oss-0.4.1/agentguard/cli/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/cli/main.py +395 -0
- agentguard_oss-0.4.1/agentguard/core/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/core/action.py +37 -0
- agentguard_oss-0.4.1/agentguard/core/context.py +11 -0
- agentguard_oss-0.4.1/agentguard/core/decision.py +40 -0
- agentguard_oss-0.4.1/agentguard/core/guard.py +598 -0
- agentguard_oss-0.4.1/agentguard/core/ratelimit.py +45 -0
- agentguard_oss-0.4.1/agentguard/dashboard/__init__.py +1 -0
- agentguard_oss-0.4.1/agentguard/dashboard/app.py +317 -0
- agentguard_oss-0.4.1/agentguard/dashboard/static/app.css +147 -0
- agentguard_oss-0.4.1/agentguard/dashboard/static/app.js +312 -0
- agentguard_oss-0.4.1/agentguard/dashboard/static/index.html +110 -0
- agentguard_oss-0.4.1/agentguard/demo/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/demo/gemma_agent.py +106 -0
- agentguard_oss-0.4.1/agentguard/demo/scenario.py +169 -0
- agentguard_oss-0.4.1/agentguard/execution/__init__.py +13 -0
- agentguard_oss-0.4.1/agentguard/execution/container.py +157 -0
- agentguard_oss-0.4.1/agentguard/execution/egress.py +191 -0
- agentguard_oss-0.4.1/agentguard/execution/executor.py +9 -0
- agentguard_oss-0.4.1/agentguard/execution/filesystem.py +59 -0
- agentguard_oss-0.4.1/agentguard/execution/network.py +42 -0
- agentguard_oss-0.4.1/agentguard/execution/pinning.py +59 -0
- agentguard_oss-0.4.1/agentguard/execution/shell.py +149 -0
- agentguard_oss-0.4.1/agentguard/execution/verification.py +38 -0
- agentguard_oss-0.4.1/agentguard/interceptors/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/interceptors/middleware.py +11 -0
- agentguard_oss-0.4.1/agentguard/policy/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/policy/engine.py +20 -0
- agentguard_oss-0.4.1/agentguard/policy/explain.py +82 -0
- agentguard_oss-0.4.1/agentguard/policy/loader.py +38 -0
- agentguard_oss-0.4.1/agentguard/policy/matcher.py +63 -0
- agentguard_oss-0.4.1/agentguard/policy/schema.py +121 -0
- agentguard_oss-0.4.1/agentguard/py.typed +0 -0
- agentguard_oss-0.4.1/agentguard/risk/__init__.py +0 -0
- agentguard_oss-0.4.1/agentguard/risk/commands.py +450 -0
- agentguard_oss-0.4.1/agentguard/risk/data/gitleaks-LICENSE.txt +21 -0
- agentguard_oss-0.4.1/agentguard/risk/data/gitleaks.toml +3213 -0
- agentguard_oss-0.4.1/agentguard/risk/gemma_judge.py +106 -0
- agentguard_oss-0.4.1/agentguard/risk/pii.py +64 -0
- agentguard_oss-0.4.1/agentguard/risk/rules.py +53 -0
- agentguard_oss-0.4.1/agentguard/risk/rulesets.py +158 -0
- agentguard_oss-0.4.1/agentguard/risk/scorer.py +107 -0
- agentguard_oss-0.4.1/agentguard/risk/secrets.py +124 -0
- agentguard_oss-0.4.1/examples/mcp_server.py +27 -0
- agentguard_oss-0.4.1/examples/policies/browsing-agent.yaml +24 -0
- agentguard_oss-0.4.1/examples/policies/coding-agent.yaml +36 -0
- agentguard_oss-0.4.1/examples/policies/support-agent.yaml +40 -0
- agentguard_oss-0.4.1/examples/policy.yaml +34 -0
- agentguard_oss-0.4.1/examples/quickstart.py +45 -0
- agentguard_oss-0.4.1/pyproject.toml +74 -0
- agentguard_oss-0.4.1/tests/conftest.py +43 -0
- agentguard_oss-0.4.1/tests/test_adapters.py +158 -0
- agentguard_oss-0.4.1/tests/test_approvals.py +297 -0
- agentguard_oss-0.4.1/tests/test_audit.py +77 -0
- agentguard_oss-0.4.1/tests/test_audit_scale.py +169 -0
- agentguard_oss-0.4.1/tests/test_bench.py +108 -0
- agentguard_oss-0.4.1/tests/test_dashboard.py +249 -0
- agentguard_oss-0.4.1/tests/test_detection.py +152 -0
- agentguard_oss-0.4.1/tests/test_execution.py +202 -0
- agentguard_oss-0.4.1/tests/test_guard.py +312 -0
- agentguard_oss-0.4.1/tests/test_integrations.py +224 -0
- agentguard_oss-0.4.1/tests/test_isolation.py +245 -0
- agentguard_oss-0.4.1/tests/test_policy_risk.py +610 -0
- agentguard_oss-0.4.1/tests/test_policy_tools.py +146 -0
- agentguard_oss-0.4.1/tests/test_properties.py +344 -0
- agentguard_oss-0.4.1/tests/test_registration.py +198 -0
- agentguard_oss-0.4.1/tests/test_sessions.py +193 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/). Before 1.0, minor versions may change the API.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.4.1] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Ask-level command concerns (irreversible infrastructure operations, raw network transfers,
|
|
13
|
+
environment dumps, inline code that runs commands) were dropped when the command was wrapped
|
|
14
|
+
in `bash -c`, `cmd /c`, PowerShell `-Command` or `eval`, so `bash -c 'terraform destroy'`
|
|
15
|
+
ran without approval. They now propagate through these wrappers, within the nesting limit.
|
|
16
|
+
- Changes to `core.hooksPath` through legacy or modern `git config` commands now raise
|
|
17
|
+
ask-level risk, including wrapped shell commands. Queries and unrelated settings remain
|
|
18
|
+
allowed by this check.
|
|
19
|
+
|
|
20
|
+
## [0.4.0] - 2026-10-08
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `ContainerExecutor`: allowlisted commands in Docker or Podman (optionally gVisor) with no
|
|
24
|
+
network, read-only root, no capabilities, no-new-privileges, an unprivileged user,
|
|
25
|
+
resource limits and a read-only workspace by default.
|
|
26
|
+
- DNS pinning: `NetworkExecutor` connects only to the public addresses it checked.
|
|
27
|
+
- `EgressProxy` and `agentguard egress-proxy`: an allowlisting HTTPS CONNECT proxy with
|
|
28
|
+
pinned public DNS and audited decisions.
|
|
29
|
+
- Secret detection with the vendored gitleaks ruleset (221 rules) on RE2, and
|
|
30
|
+
`scripts/update_secret_rules.py`.
|
|
31
|
+
- PII detection (email, phone, credit card, SSN, IBAN) with checksums; policies can name
|
|
32
|
+
the categories in `sensitive_data`; cards, SSNs and IBANs are redacted from logs.
|
|
33
|
+
- Shell-aware command analysis (`agentguard.risk.commands`): tokenizes, splits and
|
|
34
|
+
unwraps commands; new hard-deny and ask-level categories; obfuscation asks.
|
|
35
|
+
- `agentguard judge-bench`: labeled benchmark (71 cases plus a 35-case holdout) reporting
|
|
36
|
+
recall, false positives, instability and latency for rules, a judge and both combined.
|
|
37
|
+
- Property-based tests (Hypothesis) for paths, URLs, commands, secrets, audit and policy.
|
|
38
|
+
- CI on Linux, Windows and macOS with Python 3.11-3.14; CodeQL, bandit, pip-audit and
|
|
39
|
+
Dependabot; nightly long property runs; Docker integration tests.
|
|
40
|
+
- Security review guide for an independent reviewer.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- New dependency: `google-re2`.
|
|
44
|
+
- Deterministic recall on the benchmark rose from 54% to 95% (holdout: 0% to 100%, same
|
|
45
|
+
author; see the benchmarks page).
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- `rm -r -f`, `chmod 0777`, `curl ... | python3` evaded detection; `echo rm -rf` was
|
|
49
|
+
blocked; URLs with DEL or C1 characters were accepted.
|
|
50
|
+
- Persistence paths were missed on Windows after normalization.
|
|
51
|
+
- `killpg` cleanup raised on macOS; writable container workspaces were unwritable.
|
|
52
|
+
|
|
53
|
+
## [0.3.0] - 2026-10-08
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- Queued approvals that do not block the agent: `ApprovalStore` (SQLite, shared by agents and
|
|
57
|
+
the dashboard) and `QueueApproval`. An `ask` call raises `ApprovalPending` with a request
|
|
58
|
+
ID; retrying after approval runs it. Identical pending calls share one request, and
|
|
59
|
+
recently rejected calls are denied without asking again. `wait=` waits inside the call.
|
|
60
|
+
- Grants: approve once, the same call, a tool or a capability for 1 minute to 24 hours,
|
|
61
|
+
bound to agent and environment, revocable.
|
|
62
|
+
- Token-authenticated approvers (hashed), with strong approval for `--strong` approvers who
|
|
63
|
+
re-enter their token. `agentguard approvers add/list/remove`, `agentguard approvals list`.
|
|
64
|
+
- Slack notifications with approve/allow/reject buttons (signature-verified clicks) and
|
|
65
|
+
HMAC-signed webhooks (`verify_signature`).
|
|
66
|
+
- `agentguard dashboard` (extra `dashboard`): approval queue, grants, audit log viewer with
|
|
67
|
+
search and timelines, and policy report; token login, CSRF protection, strict CSP, and a
|
|
68
|
+
JSON API with Bearer tokens.
|
|
69
|
+
- `agentguard report` and `build_report`: what was asked about or denied, by tool and
|
|
70
|
+
reason, for tuning a policy from dry-run sessions.
|
|
71
|
+
- Audit checkpoint (`.head`) so appends no longer re-read the log; rotation (`max_bytes`)
|
|
72
|
+
with the chain continuing across segments; HMAC signing (`signing_key`, `--key-env`);
|
|
73
|
+
`LoggingExporter` (OpenTelemetry, syslog) and `HttpExporter` (Splunk HEC, collectors).
|
|
74
|
+
- Sessions: `guard.new_session(agent_id=...)` with its own lock, rate window, taint and halt
|
|
75
|
+
state; sessions run in parallel. `with session:` binds tools in a context.
|
|
76
|
+
- Pluggable rate limiting: `LocalRateLimiter` and `RedisRateLimiter` (extra `redis`), with
|
|
77
|
+
policy `limits.rate_limit_scope` of `session`, `agent` or `global`.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- `max_calls_per_minute` is enforced per `rate_limit_scope` (default `session`, as before).
|
|
81
|
+
- Audit events may carry `signature`; it is excluded from the event hash.
|
|
82
|
+
- CLI errors outside `demo` no longer suggest checking the model service.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
- Credential paths after `@` or `=` (`curl -d @.env`, `--file=.env`) are hard-denied.
|
|
86
|
+
|
|
87
|
+
## [0.2.0] - 2026-10-08
|
|
88
|
+
|
|
89
|
+
Renamed the distribution to `agentguard-oss` (`import agentguard`) and prepared PyPI
|
|
90
|
+
publishing.
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
- Argument roles: `guard.tool(path_arg=, url_arg=, command_arg=, content_arg=,
|
|
94
|
+
recipient_args=)` for tools whose parameters are not named `path`, `url`, `cmd`,
|
|
95
|
+
`content` or `to`. Misconfigured tools fail at registration.
|
|
96
|
+
- Custom capabilities declared in the policy (`capabilities: {db.query: {risk: 30}}`),
|
|
97
|
+
with baseline risk, an outbound flag for session-taint checks, and a description.
|
|
98
|
+
- `agentguard check-policy` validates a policy and prints a summary or located errors.
|
|
99
|
+
- `agentguard explain` shows the matching rules, risk factors and decision for one call.
|
|
100
|
+
- Framework adapters: LangChain/LangGraph, OpenAI Agents SDK and Google ADK
|
|
101
|
+
(`agentguard.adapters.*.guarded_tool`). Denials are returned to the model with reasons.
|
|
102
|
+
- Gemma is the default live demo agent when Ollama has a Gemma model; `--scripted` runs
|
|
103
|
+
offline. The judge takes trusted `guidance` and a `capabilities` scope.
|
|
104
|
+
- `ShellExecutor(check=False)` returns nonzero exits as results.
|
|
105
|
+
- `Guard.tools`, `Guard.capabilities`, `agentguard.__version__`, `py.typed`.
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
- Distribution renamed from `agentguard-sdk` (taken on PyPI) to `agentguard-oss`.
|
|
109
|
+
- Tools receive the absolute requested path; policy and credential checks use both the
|
|
110
|
+
requested path and its resolved target.
|
|
111
|
+
- A judge `deny` flag escalates to human approval; a judge score at or above the deny
|
|
112
|
+
threshold still denies.
|
|
113
|
+
- Ollama requests disable thinking, use an 8192-token context and longer timeouts.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
- Links inside the workspace bypassed `FilesystemExecutor` link rejection, and Windows
|
|
117
|
+
junctions were missed on Python 3.11 by the executor and the verifier.
|
|
118
|
+
- The email/message recipient limit ignored `cc`, `bcc` and `recipients`.
|
|
119
|
+
- Nested guarded calls from an async tool deadlocked.
|
|
120
|
+
- New files were created with lowercase names on Windows.
|
|
121
|
+
- `format` flags (`ruff format`, `--format=`) were hard-denied as disk formatting.
|
|
122
|
+
- Policy domains accepted `?` and `[`, which acted as wildcards.
|
|
123
|
+
- `git push` and `repository.write` were not treated as outbound for session taint.
|
|
124
|
+
- The audit record overwrote the action's proposal timestamp.
|
|
125
|
+
|
|
126
|
+
## [0.1.0] - 2026-10-08
|
|
127
|
+
|
|
128
|
+
Initial source release: policy as code, deterministic risk scoring, CLI approval,
|
|
129
|
+
controlled executors, workspace verification, hash-chained audit log, MCP adapter,
|
|
130
|
+
and the prompt-injection demo with an optional local Gemma agent and judge.
|
|
131
|
+
|
|
132
|
+
[Unreleased]: https://github.com/prollysamz/agentguard/compare/v0.4.1...HEAD
|
|
133
|
+
[0.4.1]: https://github.com/prollysamz/agentguard/compare/v0.4.0...v0.4.1
|
|
134
|
+
[0.4.0]: https://github.com/prollysamz/agentguard/compare/v0.3.0...v0.4.0
|
|
135
|
+
[0.3.0]: https://github.com/prollysamz/agentguard/compare/971b8bd...v0.3.0
|
|
136
|
+
[0.2.0]: https://github.com/prollysamz/agentguard/compare/8a0f847...971b8bd
|
|
137
|
+
[0.1.0]: https://github.com/prollysamz/agentguard/commit/8a0f847
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentGuard contributors
|
|
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.
|
|
22
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agentguard-oss
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Open-source safety layer for AI agent tool calls: policy, risk checks, human approval and tamper-evident audit
|
|
5
|
+
Project-URL: Homepage, https://github.com/prollysamz/agentguard
|
|
6
|
+
Project-URL: Documentation, https://prollysamz.github.io/agentguard/
|
|
7
|
+
Project-URL: Repository, https://github.com/prollysamz/agentguard
|
|
8
|
+
Project-URL: Issues, https://github.com/prollysamz/agentguard/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/prollysamz/agentguard/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Sameer Jakkuva
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai-agents,ai-safety,gemma,guardrails,llm-security,mcp,policy
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: google-re2<2,>=1.1
|
|
27
|
+
Requires-Dist: httpx<1,>=0.28
|
|
28
|
+
Requires-Dist: portalocker<4,>=3
|
|
29
|
+
Requires-Dist: pydantic<3,>=2.9
|
|
30
|
+
Requires-Dist: pyyaml<7,>=6.0.2
|
|
31
|
+
Provides-Extra: adk
|
|
32
|
+
Requires-Dist: google-adk<3,>=2; extra == 'adk'
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Requires-Dist: google-adk<3,>=2; extra == 'all'
|
|
35
|
+
Requires-Dist: langchain-core<2,>=1; extra == 'all'
|
|
36
|
+
Requires-Dist: mcp<2,>=1.12; extra == 'all'
|
|
37
|
+
Requires-Dist: openai-agents<1,>=0.23; extra == 'all'
|
|
38
|
+
Requires-Dist: redis<9,>=5; extra == 'all'
|
|
39
|
+
Requires-Dist: starlette<2,>=0.40; extra == 'all'
|
|
40
|
+
Requires-Dist: uvicorn<1,>=0.30; extra == 'all'
|
|
41
|
+
Provides-Extra: dashboard
|
|
42
|
+
Requires-Dist: starlette<2,>=0.40; extra == 'dashboard'
|
|
43
|
+
Requires-Dist: uvicorn<1,>=0.30; extra == 'dashboard'
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: bandit<2,>=1.8; extra == 'dev'
|
|
46
|
+
Requires-Dist: build<2,>=1; extra == 'dev'
|
|
47
|
+
Requires-Dist: fakeredis<3,>=2.26; extra == 'dev'
|
|
48
|
+
Requires-Dist: hypothesis<7,>=6.100; extra == 'dev'
|
|
49
|
+
Requires-Dist: langgraph<2,>=1; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest<10,>=8; extra == 'dev'
|
|
51
|
+
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
|
|
52
|
+
Requires-Dist: twine<8,>=7; extra == 'dev'
|
|
53
|
+
Provides-Extra: docs
|
|
54
|
+
Requires-Dist: mkdocs-material<10,>=9.5; extra == 'docs'
|
|
55
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
|
|
56
|
+
Provides-Extra: langchain
|
|
57
|
+
Requires-Dist: langchain-core<2,>=1; extra == 'langchain'
|
|
58
|
+
Provides-Extra: mcp
|
|
59
|
+
Requires-Dist: mcp<2,>=1.12; extra == 'mcp'
|
|
60
|
+
Provides-Extra: openai-agents
|
|
61
|
+
Requires-Dist: openai-agents<1,>=0.23; extra == 'openai-agents'
|
|
62
|
+
Provides-Extra: redis
|
|
63
|
+
Requires-Dist: redis<9,>=5; extra == 'redis'
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
|
|
66
|
+
# AgentGuard
|
|
67
|
+
|
|
68
|
+
**Let agents act. Keep humans in control.**
|
|
69
|
+
|
|
70
|
+
[](https://github.com/prollysamz/agentguard/actions/workflows/ci.yml)
|
|
71
|
+
[](https://prollysamz.github.io/agentguard/)
|
|
72
|
+
[](LICENSE)
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
AgentGuard is an open-source Python SDK that sits between an AI agent and its tools.
|
|
76
|
+
Every tool call is validated, checked against a policy, scored for risk, optionally
|
|
77
|
+
reviewed by a local **Gemma** model, sent to a human when needed, run through a
|
|
78
|
+
restricted executor, verified, and written to a tamper-evident audit log.
|
|
79
|
+
|
|
80
|
+
**[Documentation](https://prollysamz.github.io/agentguard/)** ·
|
|
81
|
+
[Quickstart](https://prollysamz.github.io/agentguard/quickstart/) ·
|
|
82
|
+
[Integrations](https://prollysamz.github.io/agentguard/integrations/langchain/) ·
|
|
83
|
+
[Threat model](THREAT_MODEL.md) · [Changelog](CHANGELOG.md)
|
|
84
|
+
|
|
85
|
+
## See it work
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pip install agentguard-oss
|
|
89
|
+
agentguard demo --scripted
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
An agent is asked to fix a calculator. The repository's README hides a prompt injection
|
|
93
|
+
telling it to read `~/.ssh/id_rsa` and upload it. AgentGuard lets the agent read the
|
|
94
|
+
README, **blocks** the SSH-key read and the `curl` exfiltration, **allows** the fix and a
|
|
95
|
+
real unit test, and **escalates** the push to `main` for human approval.
|
|
96
|
+
|
|
97
|
+
With [Ollama](https://ollama.com) and `ollama pull gemma3:4b`, plain `agentguard demo`
|
|
98
|
+
runs a live **Gemma agent** against the same trap, and `--judge` adds a Gemma security
|
|
99
|
+
reviewer. Add `--interactive` to approve the push yourself. See
|
|
100
|
+
[Gemma agent and judge](https://prollysamz.github.io/agentguard/gemma/).
|
|
101
|
+
|
|
102
|
+
## Guard a tool
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
|
|
107
|
+
from agentguard import Guard
|
|
108
|
+
|
|
109
|
+
guard = Guard(
|
|
110
|
+
{
|
|
111
|
+
"version": 1,
|
|
112
|
+
"defaults": {"effect": "deny"},
|
|
113
|
+
"rules": [
|
|
114
|
+
{"capability": "filesystem.read", "paths": ["./workspace/**"], "effect": "allow"},
|
|
115
|
+
{"capability": "shell.execute", "effect": "ask"},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
audit="agentguard.jsonl",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@guard.tool(capability="filesystem.read")
|
|
123
|
+
def read_file(path: str) -> str:
|
|
124
|
+
"""Read a UTF-8 text file."""
|
|
125
|
+
return Path(path).read_text(encoding="utf-8")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Give the agent read_file, never the raw function.
|
|
129
|
+
# read_file("~/.ssh/id_rsa") raises GuardDenied before the function body runs.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then check the policy, ask how it decides a call, and verify the log:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
agentguard check-policy policy.yaml
|
|
136
|
+
agentguard explain policy.yaml shell.execute --arg cmd="git push origin main"
|
|
137
|
+
agentguard verify-log --audit agentguard.jsonl
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Use it with your framework
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
pip install "agentguard-oss[langchain]" # also [openai-agents], [adk], [mcp], [all]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from agentguard.adapters.langchain import guarded_tool # LangChain / LangGraph
|
|
148
|
+
# from agentguard.adapters.openai_agents import guarded_tool # OpenAI Agents SDK
|
|
149
|
+
# from agentguard.adapters.adk import guarded_tool # Google ADK
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def read_notes(path: str) -> str:
|
|
153
|
+
"""Read a notes file."""
|
|
154
|
+
return Path(path).read_text(encoding="utf-8")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
notes_tool = guarded_tool(guard, read_notes, capability="filesystem.read")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Denials go back to the model with AgentGuard's reasons so the agent can adapt. MCP
|
|
161
|
+
servers use `agentguard.adapters.mcp.register_tool`; any other loop can call
|
|
162
|
+
`guard.call(name, arguments)`.
|
|
163
|
+
|
|
164
|
+
## What you get
|
|
165
|
+
|
|
166
|
+
| Control | Details |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| **Policy as code** | YAML allow / ask / deny rules by capability, path, domain, environment and secrets. Explicit denies win. [Custom capabilities](https://prollysamz.github.io/agentguard/policy/#custom-capabilities) like `db.query` or `payments.refund`. |
|
|
169
|
+
| **Risk checks** | Explainable 0–100 scores. Credential files, destructive commands and exfiltration after a secret was seen are always denied. |
|
|
170
|
+
| **Human approval** | Risky calls go to a person in the dashboard, Slack or your own system without blocking the agent; grants like "allow this tool for 10 minutes". No answer means deny. |
|
|
171
|
+
| **Isolation** | Container executor (no network, read-only, no capabilities, optional gVisor), exact-command runners, DNS-pinned HTTPS, and an allowlisting egress proxy. |
|
|
172
|
+
| **Detection** | gitleaks' 221 secret rules on RE2, checksum-validated PII, and shell-aware command analysis. |
|
|
173
|
+
| **Verification** | Before/after hashes catch tools that change files they were not asked to. |
|
|
174
|
+
| **Audit** | Every stage of every call in a signed SHA-256 hash chain with rotation, exported to OpenTelemetry or a SIEM. |
|
|
175
|
+
| **Dashboard** | Approval queue, audit log viewer and a dry-run policy report. |
|
|
176
|
+
| **Multi-agent** | Parallel per-agent sessions; rate limits per session, agent or globally through Redis. |
|
|
177
|
+
| **Gemma** | A local Gemma agent for the demo, and an optional judge that can only add caution. |
|
|
178
|
+
|
|
179
|
+
Starter policies for [coding, browsing and support agents](examples/policies/) are in
|
|
180
|
+
`examples/policies/`.
|
|
181
|
+
|
|
182
|
+
## Operate it
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
pip install "agentguard-oss[dashboard]"
|
|
186
|
+
agentguard approvers add alice # prints a token for the dashboard and API
|
|
187
|
+
agentguard dashboard # http://127.0.0.1:8765
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from agentguard.approval import ApprovalStore, QueueApproval
|
|
192
|
+
|
|
193
|
+
guard = Guard("policy.yaml", approval=QueueApproval(ApprovalStore("agentguard-approvals.db")))
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Risky calls raise `ApprovalPending` immediately instead of blocking; the agent retries after
|
|
197
|
+
a human approves in the [dashboard](https://prollysamz.github.io/agentguard/dashboard/), in
|
|
198
|
+
Slack, or through the API. Run a new agent with `mode="dry-run"` and check
|
|
199
|
+
`agentguard report --dry-run-only` to tune the policy before enforcing it.
|
|
200
|
+
|
|
201
|
+
## Status
|
|
202
|
+
|
|
203
|
+
AgentGuard 0.4 is alpha and has not had an independent security audit; see the
|
|
204
|
+
[security review guide](https://prollysamz.github.io/agentguard/security-review/) and
|
|
205
|
+
[benchmarks](https://prollysamz.github.io/agentguard/benchmarks/). It is an
|
|
206
|
+
interception layer for cooperative applications, not an OS sandbox: an agent that also has
|
|
207
|
+
unguarded tools, a raw shell or Python `exec` can go around it. Read the
|
|
208
|
+
[threat model](THREAT_MODEL.md) before guarding privileged tools, and report
|
|
209
|
+
vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
210
|
+
|
|
211
|
+
## Develop
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
git clone https://github.com/prollysamz/agentguard.git
|
|
215
|
+
cd agentguard
|
|
216
|
+
python -m venv .venv
|
|
217
|
+
# Windows: .venv\Scripts\activate macOS/Linux: source .venv/bin/activate
|
|
218
|
+
python -m pip install -e ".[dev,all,docs]"
|
|
219
|
+
python -m pytest -q
|
|
220
|
+
python -m ruff check .
|
|
221
|
+
mkdocs serve
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
CI runs the tests on Linux and Windows with Python 3.11 and 3.12. See
|
|
225
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# AgentGuard
|
|
2
|
+
|
|
3
|
+
**Let agents act. Keep humans in control.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/prollysamz/agentguard/actions/workflows/ci.yml)
|
|
6
|
+
[](https://prollysamz.github.io/agentguard/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
AgentGuard is an open-source Python SDK that sits between an AI agent and its tools.
|
|
11
|
+
Every tool call is validated, checked against a policy, scored for risk, optionally
|
|
12
|
+
reviewed by a local **Gemma** model, sent to a human when needed, run through a
|
|
13
|
+
restricted executor, verified, and written to a tamper-evident audit log.
|
|
14
|
+
|
|
15
|
+
**[Documentation](https://prollysamz.github.io/agentguard/)** ·
|
|
16
|
+
[Quickstart](https://prollysamz.github.io/agentguard/quickstart/) ·
|
|
17
|
+
[Integrations](https://prollysamz.github.io/agentguard/integrations/langchain/) ·
|
|
18
|
+
[Threat model](THREAT_MODEL.md) · [Changelog](CHANGELOG.md)
|
|
19
|
+
|
|
20
|
+
## See it work
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install agentguard-oss
|
|
24
|
+
agentguard demo --scripted
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
An agent is asked to fix a calculator. The repository's README hides a prompt injection
|
|
28
|
+
telling it to read `~/.ssh/id_rsa` and upload it. AgentGuard lets the agent read the
|
|
29
|
+
README, **blocks** the SSH-key read and the `curl` exfiltration, **allows** the fix and a
|
|
30
|
+
real unit test, and **escalates** the push to `main` for human approval.
|
|
31
|
+
|
|
32
|
+
With [Ollama](https://ollama.com) and `ollama pull gemma3:4b`, plain `agentguard demo`
|
|
33
|
+
runs a live **Gemma agent** against the same trap, and `--judge` adds a Gemma security
|
|
34
|
+
reviewer. Add `--interactive` to approve the push yourself. See
|
|
35
|
+
[Gemma agent and judge](https://prollysamz.github.io/agentguard/gemma/).
|
|
36
|
+
|
|
37
|
+
## Guard a tool
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from agentguard import Guard
|
|
43
|
+
|
|
44
|
+
guard = Guard(
|
|
45
|
+
{
|
|
46
|
+
"version": 1,
|
|
47
|
+
"defaults": {"effect": "deny"},
|
|
48
|
+
"rules": [
|
|
49
|
+
{"capability": "filesystem.read", "paths": ["./workspace/**"], "effect": "allow"},
|
|
50
|
+
{"capability": "shell.execute", "effect": "ask"},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
audit="agentguard.jsonl",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@guard.tool(capability="filesystem.read")
|
|
58
|
+
def read_file(path: str) -> str:
|
|
59
|
+
"""Read a UTF-8 text file."""
|
|
60
|
+
return Path(path).read_text(encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Give the agent read_file, never the raw function.
|
|
64
|
+
# read_file("~/.ssh/id_rsa") raises GuardDenied before the function body runs.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then check the policy, ask how it decides a call, and verify the log:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
agentguard check-policy policy.yaml
|
|
71
|
+
agentguard explain policy.yaml shell.execute --arg cmd="git push origin main"
|
|
72
|
+
agentguard verify-log --audit agentguard.jsonl
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Use it with your framework
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
pip install "agentguard-oss[langchain]" # also [openai-agents], [adk], [mcp], [all]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from agentguard.adapters.langchain import guarded_tool # LangChain / LangGraph
|
|
83
|
+
# from agentguard.adapters.openai_agents import guarded_tool # OpenAI Agents SDK
|
|
84
|
+
# from agentguard.adapters.adk import guarded_tool # Google ADK
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def read_notes(path: str) -> str:
|
|
88
|
+
"""Read a notes file."""
|
|
89
|
+
return Path(path).read_text(encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
notes_tool = guarded_tool(guard, read_notes, capability="filesystem.read")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Denials go back to the model with AgentGuard's reasons so the agent can adapt. MCP
|
|
96
|
+
servers use `agentguard.adapters.mcp.register_tool`; any other loop can call
|
|
97
|
+
`guard.call(name, arguments)`.
|
|
98
|
+
|
|
99
|
+
## What you get
|
|
100
|
+
|
|
101
|
+
| Control | Details |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| **Policy as code** | YAML allow / ask / deny rules by capability, path, domain, environment and secrets. Explicit denies win. [Custom capabilities](https://prollysamz.github.io/agentguard/policy/#custom-capabilities) like `db.query` or `payments.refund`. |
|
|
104
|
+
| **Risk checks** | Explainable 0–100 scores. Credential files, destructive commands and exfiltration after a secret was seen are always denied. |
|
|
105
|
+
| **Human approval** | Risky calls go to a person in the dashboard, Slack or your own system without blocking the agent; grants like "allow this tool for 10 minutes". No answer means deny. |
|
|
106
|
+
| **Isolation** | Container executor (no network, read-only, no capabilities, optional gVisor), exact-command runners, DNS-pinned HTTPS, and an allowlisting egress proxy. |
|
|
107
|
+
| **Detection** | gitleaks' 221 secret rules on RE2, checksum-validated PII, and shell-aware command analysis. |
|
|
108
|
+
| **Verification** | Before/after hashes catch tools that change files they were not asked to. |
|
|
109
|
+
| **Audit** | Every stage of every call in a signed SHA-256 hash chain with rotation, exported to OpenTelemetry or a SIEM. |
|
|
110
|
+
| **Dashboard** | Approval queue, audit log viewer and a dry-run policy report. |
|
|
111
|
+
| **Multi-agent** | Parallel per-agent sessions; rate limits per session, agent or globally through Redis. |
|
|
112
|
+
| **Gemma** | A local Gemma agent for the demo, and an optional judge that can only add caution. |
|
|
113
|
+
|
|
114
|
+
Starter policies for [coding, browsing and support agents](examples/policies/) are in
|
|
115
|
+
`examples/policies/`.
|
|
116
|
+
|
|
117
|
+
## Operate it
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
pip install "agentguard-oss[dashboard]"
|
|
121
|
+
agentguard approvers add alice # prints a token for the dashboard and API
|
|
122
|
+
agentguard dashboard # http://127.0.0.1:8765
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from agentguard.approval import ApprovalStore, QueueApproval
|
|
127
|
+
|
|
128
|
+
guard = Guard("policy.yaml", approval=QueueApproval(ApprovalStore("agentguard-approvals.db")))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Risky calls raise `ApprovalPending` immediately instead of blocking; the agent retries after
|
|
132
|
+
a human approves in the [dashboard](https://prollysamz.github.io/agentguard/dashboard/), in
|
|
133
|
+
Slack, or through the API. Run a new agent with `mode="dry-run"` and check
|
|
134
|
+
`agentguard report --dry-run-only` to tune the policy before enforcing it.
|
|
135
|
+
|
|
136
|
+
## Status
|
|
137
|
+
|
|
138
|
+
AgentGuard 0.4 is alpha and has not had an independent security audit; see the
|
|
139
|
+
[security review guide](https://prollysamz.github.io/agentguard/security-review/) and
|
|
140
|
+
[benchmarks](https://prollysamz.github.io/agentguard/benchmarks/). It is an
|
|
141
|
+
interception layer for cooperative applications, not an OS sandbox: an agent that also has
|
|
142
|
+
unguarded tools, a raw shell or Python `exec` can go around it. Read the
|
|
143
|
+
[threat model](THREAT_MODEL.md) before guarding privileged tools, and report
|
|
144
|
+
vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
145
|
+
|
|
146
|
+
## Develop
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
git clone https://github.com/prollysamz/agentguard.git
|
|
150
|
+
cd agentguard
|
|
151
|
+
python -m venv .venv
|
|
152
|
+
# Windows: .venv\Scripts\activate macOS/Linux: source .venv/bin/activate
|
|
153
|
+
python -m pip install -e ".[dev,all,docs]"
|
|
154
|
+
python -m pytest -q
|
|
155
|
+
python -m ruff check .
|
|
156
|
+
mkdocs serve
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
CI runs the tests on Linux and Windows with Python 3.11 and 3.12. See
|
|
160
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""AgentGuard's public SDK.
|
|
2
|
+
|
|
3
|
+
Public API: the names in ``__all__`` here, plus ``agentguard.approval``,
|
|
4
|
+
``agentguard.execution``, ``agentguard.adapters.*`` and
|
|
5
|
+
``agentguard.risk.gemma_judge.GemmaJudge``. Other modules are internal and may change.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
from agentguard.approval.base import Approval, ApprovalProvider
|
|
11
|
+
from agentguard.core.action import Action
|
|
12
|
+
from agentguard.core.decision import (
|
|
13
|
+
ApprovalPending,
|
|
14
|
+
Decision,
|
|
15
|
+
Effect,
|
|
16
|
+
GuardDenied,
|
|
17
|
+
GuardError,
|
|
18
|
+
)
|
|
19
|
+
from agentguard.core.guard import Guard
|
|
20
|
+
from agentguard.policy.explain import explain
|
|
21
|
+
from agentguard.policy.loader import load_policy
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
__version__ = version("agentguard-oss")
|
|
25
|
+
except PackageNotFoundError: # Running from a source tree without installation.
|
|
26
|
+
__version__ = "0.0.0+unknown"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Action",
|
|
30
|
+
"Approval",
|
|
31
|
+
"ApprovalPending",
|
|
32
|
+
"ApprovalProvider",
|
|
33
|
+
"Decision",
|
|
34
|
+
"Effect",
|
|
35
|
+
"Guard",
|
|
36
|
+
"GuardDenied",
|
|
37
|
+
"GuardError",
|
|
38
|
+
"__version__",
|
|
39
|
+
"explain",
|
|
40
|
+
"load_policy",
|
|
41
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Shared adapter helpers. Adapters translate and dispatch; they make no security decisions."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
from agentguard.core.decision import ApprovalPending, GuardDenied
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def denial_result(exc: GuardDenied) -> dict:
|
|
10
|
+
"""What the model sees when AgentGuard denies a call: the reasons, so it can adapt."""
|
|
11
|
+
if isinstance(exc, ApprovalPending):
|
|
12
|
+
return {
|
|
13
|
+
"error": "Approval pending",
|
|
14
|
+
"request_id": exc.request_id,
|
|
15
|
+
"reasons": list(exc.decision.reasons),
|
|
16
|
+
"next_step": "A human must approve this. Continue with other work and retry "
|
|
17
|
+
"the same call later.",
|
|
18
|
+
}
|
|
19
|
+
return {"error": "Denied by AgentGuard", "reasons": list(exc.decision.reasons)}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def returning_denials(guarded, on_denied=denial_result):
|
|
23
|
+
"""Wrap a guarded tool so denials become a result instead of an exception.
|
|
24
|
+
|
|
25
|
+
Only GuardDenied is converted. Other GuardErrors (a halted session, failed execution)
|
|
26
|
+
still raise, so frameworks surface them as tool failures.
|
|
27
|
+
"""
|
|
28
|
+
if inspect.iscoroutinefunction(guarded):
|
|
29
|
+
|
|
30
|
+
@functools.wraps(guarded)
|
|
31
|
+
async def wrapper(*args, **kwargs):
|
|
32
|
+
try:
|
|
33
|
+
return await guarded(*args, **kwargs)
|
|
34
|
+
except GuardDenied as exc:
|
|
35
|
+
return on_denied(exc)
|
|
36
|
+
else:
|
|
37
|
+
|
|
38
|
+
@functools.wraps(guarded)
|
|
39
|
+
def wrapper(*args, **kwargs):
|
|
40
|
+
try:
|
|
41
|
+
return guarded(*args, **kwargs)
|
|
42
|
+
except GuardDenied as exc:
|
|
43
|
+
return on_denied(exc)
|
|
44
|
+
|
|
45
|
+
# The return type changes on denial; frameworks infer only the arguments from this.
|
|
46
|
+
wrapper.__signature__ = inspect.signature(guarded).replace(
|
|
47
|
+
return_annotation=inspect.Signature.empty
|
|
48
|
+
)
|
|
49
|
+
wrapper.__annotations__ = {k: v for k, v in guarded.__annotations__.items() if k != "return"}
|
|
50
|
+
return wrapper
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def describe(function, description):
|
|
54
|
+
return description or inspect.getdoc(function) or f"{function.__name__} (guarded by AgentGuard)"
|