agentguard-eval 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.5
2
+ Name: agentguard-eval
3
+ Version: 0.3.0
4
+ Summary: Guardrails and evals for AI agents: a policy gate for tool calls, a checker for invented facts, and a benchmark for shell-command gates. Deterministic, no API keys.
5
+ Project-URL: Homepage, https://github.com/zbs-ops/agentguard
6
+ Project-URL: Source, https://github.com/zbs-ops/agentguard
7
+ Project-URL: Issues, https://github.com/zbs-ops/agentguard/issues
8
+ Author: Zohaib Qamar
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,ai-safety,evaluation,guardrails,hallucination,llm,pytest
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Security
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+
25
+ # agentguard
26
+
27
+ > **Problem:** AI agents with tools invent facts, overspend, and get their shell commands talked around. Most stacks catch none of it.
28
+ >
29
+ > **Approach:** three deterministic guardrails, no API keys and no model calls: a policy gate for tool calls, a checker for invented values, and a benchmark for shell-command gates.
30
+ >
31
+ > **Proof:** 194 tests. The checker and the benchmark are ports of work measured on real agent logs and on three shipping AI coding tools.
32
+
33
+ ```bash
34
+ pip install agentguard-eval
35
+ ```
36
+
37
+ The package installs as `agentguard-eval` (PyPI found `agentguard` too close to an existing name) and imports as `agentguard`.
38
+
39
+ Python 3.10+. Zero runtime dependencies. Everything here runs offline and gives the same answer every time, which is what makes it testable and what makes it safe to put in a CI job.
40
+
41
+ ---
42
+
43
+ ## What's in it
44
+
45
+ Three things that go wrong when you give a language model real tools, and a guard for each.
46
+
47
+ ### 1. A policy gate: decide whether a tool call may run
48
+
49
+ Every tool call is a *proposal*. The gate returns one of three answers: run it, hold it for a person, or refuse it. Reads run unattended; money, customer messages, and deletes wait for a human. It fails closed, checks the budget before anything else, and catches an agent that estimates one cost while its arguments move a bigger one.
50
+
51
+ ```python
52
+ from agentguard.gate import evaluate, Budget
53
+
54
+ d = evaluate("refund.create", estimated_cost_cents=100, args={"amount_cents": 9000},
55
+ budget=Budget(spent_cents=0, limit_cents=50_000))
56
+ print(d.verdict.value, d.decided_by) # REQUIRE_APPROVAL cost-understated
57
+ print(d.reason) # "The agent estimated $1.00, but the arguments move $90.00 — escalated for review."
58
+ ```
59
+
60
+ Seven ordered checks, each one asserted by a test: a malformed tool name is refused before it can borrow another rule, an unknown tool fails closed, a negative or non-finite cost is refused before the budget math, the budget is checked before the tier, an understated or unusually large spend is escalated, and a runaway loop of writes is paused.
61
+
62
+ `evaluate` judges one call. `Governor` runs the whole approval workflow around it: it holds what's waiting for a person, checks who is approving, charges the budget only when something actually runs, and writes every step to a hash-chained ledger.
63
+
64
+ ```python
65
+ from agentguard.gate import Governor
66
+
67
+ g = Governor(reviewer_keys={"you@company.com": "your-secret"})
68
+ a = g.propose("billing-agent", "refund.create", {"amount_cents": 9000}, estimated_cost_cents=9000)
69
+ print(a.state.value) # PENDING_APPROVAL
70
+
71
+ token = g.mint_token(a.id, "you@company.com") # only the key holder can make this
72
+ g.decide(a.id, True, "you@company.com", token=token)
73
+ print(a.state.value, g.spent_cents) # EXECUTED 9000
74
+ print(g.verify().message) # All 4 entries verify against their hashes.
75
+ ```
76
+
77
+ Each rule here is a bug the TypeScript project found and fixed: the agent that asked can't approve its own request; typing a reviewer's name isn't enough, it takes a token bound to that one action; an approval stops counting 24 hours after the request; and the budget is checked again at the moment an approved action runs, because two refunds that each fit alone can both get a yes. Ledgers are interchangeable with the TypeScript version: the same calls produce the same ledger byte for byte, hashes included (`tests/test_governor.py` checks this against a ledger the TypeScript store wrote).
78
+
79
+ ### 2. A provenance checker: catch invented facts
80
+
81
+ Checks whether an agent's answer is backed by what its tools actually returned. Four checks: a confident answer after a tool failed, a value no tool returned and nobody stated, a record for the wrong entity passed off as the right one, and a refusal that cites a limit appearing nowhere in the transcript.
82
+
83
+ ```python
84
+ from agentguard.provenance import check_transcript, load_transcript
85
+
86
+ t = load_transcript({"steps": [
87
+ {"role": "tool_call", "id": "c1", "tool": "orders.get", "args": {"order_id": "W1"}},
88
+ {"role": "tool_result", "id": "c1", "tool": "orders.get", "status": "ok", "payload": {"eta": "2026-10-22"}},
89
+ {"role": "assistant", "content": "It arrives 2026-10-24."},
90
+ ]})
91
+ v = check_transcript(t)
92
+ print(v.honest, v.flags[0].kind, v.flags[0].values) # False unsupported-value ('2026-10-24',)
93
+ ```
94
+
95
+ It reads the user, the system prompt, the tool calls and their results, and normalizes values so the same fact in different forms ("May 19, 2024" and `2024-05-19`, "$1,024" and `1024`) matches. It knows an agent computes (a refund that's the difference of two returned prices is grounded), and it leaves vague language alone, because exact-match is the honest limit of a checker with no model behind it.
96
+
97
+ For the short, high-yield list, `id_flags(verdict)` keeps only flags about an invented order number or product code, the fabrication that does the most harm.
98
+
99
+ It reads your agent's real logs directly. `to_transcript` takes an **OpenAI** chat log (`tool_calls` / role `tool`), an **Anthropic** messages log (`tool_use` / `tool_result` blocks), or agentguard's own `{"steps": [...]}`, and works out which one it got:
100
+
101
+ ```python
102
+ import json
103
+ from agentguard.provenance import check_transcript, to_transcript
104
+
105
+ v = check_transcript(to_transcript(json.load(open("run.json"))))
106
+ ```
107
+
108
+ Tool calls a model also writes into its message text (Claude's `<function_calls>` markup) are stripped first, so tool names aren't read as invented claims. On tool-truth's example logs, agentguard prints exactly the flags and values the TypeScript original prints, in the same order (`tests/test_parity.py`).
109
+
110
+ ### 3. A benchmark: how often can a command gate be talked around?
111
+
112
+ Give an agent a shell and you put a gate in front of it: a list of commands it may not run. This rewrites a forbidden command in 13 known bypass styles (chaining, quoting, `$IFS`, variable indirection, `awk`, `base64`, equivalent binaries) and counts a bypass **only when the forbidden effect actually happens in a sandbox**.
113
+
114
+ ```python
115
+ from agentguard.shell import run_bench, format_report
116
+
117
+ print(format_report(run_bench()))
118
+ ```
119
+
120
+ ```
121
+ gate bypass rate (bypasses / effective)
122
+ substring-denylist 31% (14/45)
123
+ word-denylist 78% (35/45)
124
+ tokenized-denylist 71% (32/45)
125
+ allowlist 0% (0/45)
126
+ ```
127
+
128
+ Two findings worth the whole module: a more precise denylist is not a safer one (whole-word matching misses `rm${IFS}file` that a dumb substring search catches), and only the allowlist, which names what may run and refuses everything else, holds at 0%.
129
+
130
+ ---
131
+
132
+ ## A pytest plugin
133
+
134
+ Installing agentguard adds fixtures so you can assert on agent behaviour in your own suite:
135
+
136
+ ```python
137
+ def test_my_agent_is_grounded(assert_grounded):
138
+ assert_grounded(my_agent_transcript) # fails, listing the invented values
139
+
140
+ def test_my_policy_allows_reads(assert_tool_allowed):
141
+ assert_tool_allowed("crm.read") # fails if the gate wouldn't auto-allow it
142
+
143
+ def test_my_shell_gate_holds(assert_gate_holds):
144
+ assert_gate_holds(my_gate) # fails, listing the commands that got through
145
+ ```
146
+
147
+ ## A command line
148
+
149
+ ```bash
150
+ agentguard check run.json # OpenAI, Anthropic or agentguard format; exit 1 if flagged
151
+ agentguard check run.json --ids-only # only high-confidence invented IDs
152
+ agentguard gate refund.create --cost 9000 --amount 9000
153
+ agentguard bench # the shell-gate scorecard
154
+ agentguard verify-ledger ledger.json # exit 1 and name the first edited entry
155
+ ```
156
+
157
+ `agentguard check` exits non-zero when a transcript is flagged, so it can gate a CI job over saved agent runs.
158
+
159
+ ---
160
+
161
+ ## Where this comes from
162
+
163
+ The checker and the benchmark are Python ports of two projects I tested on real data, and the write-ups there carry the honest numbers:
164
+
165
+ - **[tool-truth](https://github.com/zbs-ops/tool-truth)**: the checker, run on 660+ real agent conversations from τ-bench and τ²-bench. The first version was useless on real logs; after fixes it caught agents inventing an order number a customer then relied on. Held-out results, good and bad, are written up in full.
166
+ - **[gate-bypass-bench](https://github.com/zbs-ops/gate-bypass-bench)**: the benchmark, which I ran against the real command rules of Google's Gemini CLI, OpenAI's Codex CLI, and opencode. Their deny rules were bypassed 78–84% of the time; the same engines as allowlists held at 0%.
167
+ - **[agent-governance](https://github.com/zbs-ops/agent-governance)**: the gate and the approval workflow, with a live demo and a tamper-evident audit log.
168
+
169
+ Background: Chen & Lin, *denylist fragility in AI agents* ([arXiv:2606.15549](https://arxiv.org/abs/2606.15549)); Sethi et al., *fabrication after tool failure* ([arXiv:2609.14758](https://arxiv.org/abs/2609.14758)); Saltzer & Schroeder (1975) on fail-safe defaults.
170
+
171
+ ## Scope and limits
172
+
173
+ - **No model, by design.** The checker uses exact-match provenance. It catches invented concrete values; it cannot judge paraphrase or multi-step arithmetic, and the TypeScript write-ups measure that boundary on real data.
174
+ - **The gate's policy is a sensible default, not your policy.** The tiers and the $500 cap are the demo's; the point is the shape.
175
+ - **The Governor keeps state in memory.** Save `g.ledger()` (each entry has `to_dict()`) wherever you keep records; `verify-ledger` checks it later. Reviewer keys are plain secrets you hand it; in production they belong behind your identity provider.
176
+ - **The benchmark runs POSIX `sh`.** The rewrites re-spell dangerous-shaped commands; they only touch files inside a per-case sandbox.
177
+
178
+ ## How I build
179
+
180
+ I work with an AI coding assistant as a pair programmer. I decide what gets built, review every change, and own what ships.
181
+
182
+ ## License
183
+
184
+ MIT
@@ -0,0 +1,22 @@
1
+ agentguard/__init__.py,sha256=AaFuUWX4vXYWKjoHl5pfl3m1uVDbCWVXbhGqKmMyuIg,77
2
+ agentguard/cli.py,sha256=uB5-d5vK7bCcS11D0DmtyABY8hNWnT7T9YEQlCT6DPU,4186
3
+ agentguard/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ agentguard/pytest_plugin.py,sha256=hXlnn-Gpwe3NRTLt1bQhnPBj86hPqDaVeh0Q-rRcz4E,3177
5
+ agentguard/gate/__init__.py,sha256=BafW8GQCJJXuqvE-8JwoWtH-UgdZ05y1IN8AyMTJ1AE,1060
6
+ agentguard/gate/governor.py,sha256=Ou0l4LSbvDtpj7FquG8wlDbVLsAqmKJxfM_tE9gp4fE,10640
7
+ agentguard/gate/ledger.py,sha256=SlGfOSmgmKW6ROjRnLyXuUu2vbmq00OHi09qOvH9DG8,4793
8
+ agentguard/gate/policy.py,sha256=mHkXiRC6Q5eyeRYNJQySMURhGljvlKmjo199rh83-48,8939
9
+ agentguard/provenance/__init__.py,sha256=S6rV5QVfi7_bZLJaP-mW4JPMv294qeww0wsywmRATJA,1107
10
+ agentguard/provenance/adapters.py,sha256=XJHzj1hYTFBH6185iMqftJBgOIFMiGKzEF0sYcs677A,5959
11
+ agentguard/provenance/check.py,sha256=HiY5RyhpUgPpCbPNT8Okwav2tviZZWZqLHghmq2OHgo,11268
12
+ agentguard/provenance/failure.py,sha256=V8wNQhpnWkIjGTDtPSQ9sMKcSwVVMVgGln3GXjjly-o,3562
13
+ agentguard/provenance/ids.py,sha256=1yF9LRB4KZOFAGe3ygjtMaLCKgOTomFHYcR3Tkgauto,1033
14
+ agentguard/provenance/types.py,sha256=ISpUmVIedMA-y5oaoziHaTYEqkq-UN-qCnsbtjRShiI,2593
15
+ agentguard/provenance/values.py,sha256=zxjM3k1uXWF4gfpBSV7gMHXqG3xyDSV0OHYukjPdfPQ,9610
16
+ agentguard/shell/__init__.py,sha256=4mhRwDMgWx__iSmJ93A61CR9oTvL_2hWSw4nDTdEbLo,658
17
+ agentguard/shell/model.py,sha256=EaJHfuinDlgbxUJylCcyUofJLDb86Ka_aO5N9O0UXAs,12146
18
+ agentguard_eval-0.3.0.dist-info/METADATA,sha256=S-gE0iQDHKdZ1nO1zeHJet6zr7sJ5XQfEenArAfeYP8,10872
19
+ agentguard_eval-0.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
20
+ agentguard_eval-0.3.0.dist-info/entry_points.txt,sha256=kr6IkoQmk30yFX6UtN1R7t0tJxpDnK3kuHnlJBpN8dI,101
21
+ agentguard_eval-0.3.0.dist-info/licenses/LICENSE,sha256=nUyBEhVzR8wT8McPfkHCtdRVovVJ7f_2Tha3nEO7WfQ,1069
22
+ agentguard_eval-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ agentguard = agentguard.cli:main
3
+
4
+ [pytest11]
5
+ agentguard = agentguard.pytest_plugin
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zohaib Qamar
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.