agent-intent-guard 0.1.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.
- agent_intent_guard-0.1.0/PKG-INFO +485 -0
- agent_intent_guard-0.1.0/README.md +469 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/PKG-INFO +485 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/SOURCES.txt +45 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/dependency_links.txt +1 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/entry_points.txt +3 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/requires.txt +6 -0
- agent_intent_guard-0.1.0/agent_intent_guard.egg-info/top_level.txt +1 -0
- agent_intent_guard-0.1.0/intent_guard/__init__.py +3 -0
- agent_intent_guard-0.1.0/intent_guard/cli.py +188 -0
- agent_intent_guard-0.1.0/intent_guard/proxy.py +159 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/__init__.py +39 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/decision_cache.py +50 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/engine.py +611 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/log_redactor.py +47 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/mcp_proxy.py +352 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/providers.py +382 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/rate_limiter.py +122 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/response_guard.py +147 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/semantic_eval.py +92 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/tool_snapshot.py +48 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/validator.py +290 -0
- agent_intent_guard-0.1.0/intent_guard/sdk/watcher.py +63 -0
- agent_intent_guard-0.1.0/pyproject.toml +36 -0
- agent_intent_guard-0.1.0/setup.cfg +4 -0
- agent_intent_guard-0.1.0/setup.py +3 -0
- agent_intent_guard-0.1.0/tests/test_advisory_mode.py +100 -0
- agent_intent_guard-0.1.0/tests/test_claude_pretooluse_hook_integration.py +108 -0
- agent_intent_guard-0.1.0/tests/test_decision_cache.py +81 -0
- agent_intent_guard-0.1.0/tests/test_encoded_payloads.py +59 -0
- agent_intent_guard-0.1.0/tests/test_hook_integration.py +142 -0
- agent_intent_guard-0.1.0/tests/test_hot_reload.py +132 -0
- agent_intent_guard-0.1.0/tests/test_injection_detection.py +104 -0
- agent_intent_guard-0.1.0/tests/test_integration_phases.py +393 -0
- agent_intent_guard-0.1.0/tests/test_log_redaction.py +177 -0
- agent_intent_guard-0.1.0/tests/test_ollama_provider_live_semantic.py +381 -0
- agent_intent_guard-0.1.0/tests/test_path_traversal.py +47 -0
- agent_intent_guard-0.1.0/tests/test_policy_validation.py +193 -0
- agent_intent_guard-0.1.0/tests/test_proxy_stdio.py +147 -0
- agent_intent_guard-0.1.0/tests/test_rate_limiting.py +300 -0
- agent_intent_guard-0.1.0/tests/test_response_inspection.py +64 -0
- agent_intent_guard-0.1.0/tests/test_rubric_scoring.py +415 -0
- agent_intent_guard-0.1.0/tests/test_secret_pii_scanning.py +119 -0
- agent_intent_guard-0.1.0/tests/test_semantic_eval.py +54 -0
- agent_intent_guard-0.1.0/tests/test_starter_templates.py +73 -0
- agent_intent_guard-0.1.0/tests/test_system_policy_webhook.py +189 -0
- agent_intent_guard-0.1.0/tests/test_tool_snapshot_detection.py +63 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-intent-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP tool-call guardrails with static and semantic policy checks
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: PyYAML>=6.0.0
|
|
12
|
+
Requires-Dist: requests>=2.31.0
|
|
13
|
+
Requires-Dist: litellm>=1.82.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# IntentGuard
|
|
18
|
+
|
|
19
|
+
IntentGuard is a Python guardrail layer for MCP tool calls. It runs as a proxy between an agent client and an MCP server, enforcing both static policy checks and optional semantic intent checks before a tool call is forwarded.
|
|
20
|
+
|
|
21
|
+
## What is implemented (MVP)
|
|
22
|
+
|
|
23
|
+
The current implementation covers all 4 roadmap phases from `agent.md`:
|
|
24
|
+
|
|
25
|
+
1. **CLI Interceptor (Phase 1)**
|
|
26
|
+
`intent_guard/proxy.py` + `intent_guard/sdk/mcp_proxy.py` implement a stdio proxy that intercepts `tools/call` JSON-RPC requests and can block/allow calls.
|
|
27
|
+
2. **Static Engine (Phase 2)**
|
|
28
|
+
`intent_guard/sdk/engine.py` loads YAML policy and enforces:
|
|
29
|
+
- `forbidden_tools`
|
|
30
|
+
- `protected_paths` (glob/fnmatch style)
|
|
31
|
+
- `max_tokens_per_call`
|
|
32
|
+
- `custom_policies` (tool-specific argument requirements/forbidden arguments)
|
|
33
|
+
3. **Semantic Guardrail Providers (Phase 3)**
|
|
34
|
+
`intent_guard/sdk/providers.py` supports:
|
|
35
|
+
- `OllamaProvider` (`POST /api/generate`)
|
|
36
|
+
- `LiteLLMProvider` (`litellm.completion`) using `LLM_MODEL` and `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` from env
|
|
37
|
+
Both providers include retries (exponential backoff + jitter) and a circuit breaker.
|
|
38
|
+
4. **Pause & Resume Feedback Loop (Phase 4)**
|
|
39
|
+
`terminal_approval_prompt` provides interactive approval for flagged calls (`Allow? [y/N]`).
|
|
40
|
+
|
|
41
|
+
## Repository layout
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
intent_guard/
|
|
45
|
+
├── __init__.py
|
|
46
|
+
├── proxy.py
|
|
47
|
+
└── sdk/
|
|
48
|
+
├── __init__.py
|
|
49
|
+
├── engine.py
|
|
50
|
+
├── mcp_proxy.py
|
|
51
|
+
└── providers.py
|
|
52
|
+
schema/
|
|
53
|
+
└── policy.yaml
|
|
54
|
+
tests/
|
|
55
|
+
├── conftest.py
|
|
56
|
+
└── test_integration_phases.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 -m venv .venv
|
|
63
|
+
.venv/bin/pip install -r requirements.txt
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Run tests
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
.venv/bin/pytest -q
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run live Ollama semantic tests only (requires local Ollama + `llama3.1:8b` available):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
.venv/bin/pytest -q -m runOllamaProvider
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If local model responses are slow, increase timeout (seconds):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
OLLAMA_TIMEOUT_SECONDS=120 .venv/bin/pytest -q -m runOllamaProvider
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The live semantic suite defaults to `OLLAMA_RAW=false` and bounded generation tuned for `llama3.1:8b`. You can tune:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
OLLAMA_TIMEOUT_SECONDS=60 OLLAMA_NUM_PREDICT=256 OLLAMA_RAW=false \
|
|
88
|
+
.venv/bin/pytest -q -m runOllamaProvider
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Integration tests cover all phases:
|
|
92
|
+
- phase 1: interception and logging behavior
|
|
93
|
+
- phase 2: static policy blocking
|
|
94
|
+
- phase 3: semantic provider flow (mocked Ollama HTTP call)
|
|
95
|
+
- phase 4: approval allow/deny behavior
|
|
96
|
+
|
|
97
|
+
## Policy file
|
|
98
|
+
|
|
99
|
+
Use `schema/policy.yaml` as a starting point:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
static_rules:
|
|
103
|
+
forbidden_tools: ["delete_database", "purge_all"]
|
|
104
|
+
protected_paths: ["/etc/*", ".env", "src/auth/*"]
|
|
105
|
+
max_tokens_per_call: 4000
|
|
106
|
+
rate_limits:
|
|
107
|
+
enabled: 1 # required to turn rate limiting on; 0/false bypasses checks
|
|
108
|
+
default:
|
|
109
|
+
max_calls: 60
|
|
110
|
+
window_seconds: 60
|
|
111
|
+
by_tool:
|
|
112
|
+
write_file:
|
|
113
|
+
max_calls: 10
|
|
114
|
+
window_seconds: 60
|
|
115
|
+
|
|
116
|
+
custom_policies:
|
|
117
|
+
- tool_name: write_file
|
|
118
|
+
args:
|
|
119
|
+
all_present: ["path", "content"]
|
|
120
|
+
should_not_present: ["sudo"]
|
|
121
|
+
|
|
122
|
+
semantic_rules:
|
|
123
|
+
provider: ollama # or litellm
|
|
124
|
+
mode: enforce # off | enforce | advisory
|
|
125
|
+
prompt_version: "v2"
|
|
126
|
+
guardrail_model: llama3.1:8b
|
|
127
|
+
critical_intent_threshold: 0.85
|
|
128
|
+
retry_attempts: 2
|
|
129
|
+
retry_base_delay_seconds: 0.25
|
|
130
|
+
retry_max_delay_seconds: 2.0
|
|
131
|
+
retry_jitter_ratio: 0.2
|
|
132
|
+
circuit_breaker_failures: 3
|
|
133
|
+
circuit_breaker_reset_seconds: 30
|
|
134
|
+
provider_fail_mode:
|
|
135
|
+
default: advisory # fail-open
|
|
136
|
+
by_tool:
|
|
137
|
+
delete_database: enforce # fail-closed
|
|
138
|
+
constraints:
|
|
139
|
+
- intent: modify_source_code
|
|
140
|
+
allowed_scope: Actions must only affect UI components or styles.
|
|
141
|
+
forbidden_scope: Should not modify database schemas or auth logic.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Rubric scoring (v2)
|
|
145
|
+
|
|
146
|
+
Set `prompt_version: "v2"` to switch from opaque LLM-assigned scores to
|
|
147
|
+
multi-signal rubric scoring. Instead of asking the LLM for a single confidence
|
|
148
|
+
number, the engine asks concrete yes/no questions across multiple dimensions
|
|
149
|
+
and computes the score deterministically from the answers.
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
semantic_rules:
|
|
153
|
+
prompt_version: "v2"
|
|
154
|
+
critical_intent_threshold: 0.85
|
|
155
|
+
scoring:
|
|
156
|
+
dimensions:
|
|
157
|
+
tool_task_alignment:
|
|
158
|
+
weight: 0.25
|
|
159
|
+
argument_scope_compliance:
|
|
160
|
+
weight: 0.30
|
|
161
|
+
no_forbidden_scope_violation:
|
|
162
|
+
weight: 0.30
|
|
163
|
+
no_side_effect_risk:
|
|
164
|
+
weight: 0.15
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Default dimensions (used when `scoring` is omitted):
|
|
168
|
+
|
|
169
|
+
| Dimension | Question | Default weight |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `tool_task_alignment` | Is this tool appropriate for the stated task? | 0.25 |
|
|
172
|
+
| `argument_scope_compliance` | Are arguments within the allowed scope? | 0.30 |
|
|
173
|
+
| `no_forbidden_scope_violation` | Do arguments avoid the forbidden scope? | 0.30 |
|
|
174
|
+
| `no_side_effect_risk` | Is the call free of destructive/exfil risk? | 0.15 |
|
|
175
|
+
|
|
176
|
+
Score formula: `Σ(weight × pass) / Σ(weight)`. With 4 equal-pass dimensions
|
|
177
|
+
the score is 1.0; any single failure drops below the 0.85 threshold.
|
|
178
|
+
|
|
179
|
+
Decisions include `dimension_scores` with per-dimension `passed` and
|
|
180
|
+
`evidence` for full auditability.
|
|
181
|
+
|
|
182
|
+
## CLI usage
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
INTENT_GUARD_TASK="Only update frontend styles" \
|
|
186
|
+
python -m intent_guard.proxy \
|
|
187
|
+
--policy schema/policy.yaml \
|
|
188
|
+
--target "npx @modelcontextprotocol/server-filesystem /path/to/repo" \
|
|
189
|
+
--model llama3.1:8b \
|
|
190
|
+
--approval-webhook "https://approval.internal/intent-guard" \
|
|
191
|
+
--approval-timeout 10 \
|
|
192
|
+
--approval-default-action deny
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Flags
|
|
196
|
+
- `--policy`: YAML policy path
|
|
197
|
+
- `--target`: target MCP server command
|
|
198
|
+
- `--model`: optional Ollama model name for semantic checks
|
|
199
|
+
- `--task`: optional task context (or set `INTENT_GUARD_TASK`)
|
|
200
|
+
- `--ask-approval`: prompt user before allowing flagged calls
|
|
201
|
+
- `--approval-webhook`: call this webhook for non-interactive approval decisions
|
|
202
|
+
- `--approval-timeout`: timeout (seconds) for webhook approvals
|
|
203
|
+
- `--approval-default-action`: `allow` or `deny` when webhook approval times out/fails
|
|
204
|
+
|
|
205
|
+
## Native hook integration
|
|
206
|
+
|
|
207
|
+
IntentGuard can run as the policy engine behind native hooks in Claude Code, Copilot, and Cursor.
|
|
208
|
+
|
|
209
|
+
### Evaluate command
|
|
210
|
+
|
|
211
|
+
Use the unified command:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
intent-guard evaluate --policy schema/policy.yaml
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Input:
|
|
218
|
+
- Reads a hook payload JSON object from stdin
|
|
219
|
+
- Supports generic keys like `tool_name`, `arguments`, `task_context`
|
|
220
|
+
- Also supports nested payloads (`params.name`, `params.arguments`) and common aliases (`tool_input`, `args`, `prompt`)
|
|
221
|
+
|
|
222
|
+
Output:
|
|
223
|
+
- Prints a `GuardDecision` JSON object to stdout
|
|
224
|
+
- Exit code `0` for allow, `1` for block, `2` for invalid input
|
|
225
|
+
|
|
226
|
+
### Hook config templates
|
|
227
|
+
|
|
228
|
+
Template files are shipped under `hooks/`:
|
|
229
|
+
- `hooks/claude-code/settings.json`
|
|
230
|
+
- `hooks/copilot/hooks.json`
|
|
231
|
+
- `hooks/cursor/hooks.json`
|
|
232
|
+
|
|
233
|
+
Each template invokes:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
cat | intent-guard evaluate --policy schema/policy.yaml
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
This lets platform-native hooks call IntentGuard directly instead of wrapping only MCP servers.
|
|
240
|
+
|
|
241
|
+
## Encoded payload detection
|
|
242
|
+
|
|
243
|
+
Static checks can decode and normalize argument payloads before matching:
|
|
244
|
+
- URL decoding
|
|
245
|
+
- Unicode normalization (NFKC)
|
|
246
|
+
- Base64 decoding (when valid)
|
|
247
|
+
|
|
248
|
+
Enable or disable via:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
static_rules:
|
|
252
|
+
decode_arguments: true
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
When enabled, injection, sensitive-data, and protected-path checks run against decoded variants to catch obfuscated bypasses.
|
|
256
|
+
|
|
257
|
+
## Response-side inspection
|
|
258
|
+
|
|
259
|
+
IntentGuard can inspect MCP server responses before forwarding them to the client.
|
|
260
|
+
|
|
261
|
+
Configure `response_rules` in policy:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
response_rules:
|
|
265
|
+
action: block # block | warn | redact
|
|
266
|
+
detect_base64: true
|
|
267
|
+
patterns:
|
|
268
|
+
- name: "GitHub Token"
|
|
269
|
+
pattern: "gh[ps]_[A-Za-z0-9_]{36,}"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Behavior:
|
|
273
|
+
- `block`: return JSON-RPC error and suppress original response
|
|
274
|
+
- `warn`: forward response and log warning decision
|
|
275
|
+
- `redact`: redact matched text and forward sanitized response
|
|
276
|
+
|
|
277
|
+
## Tool description change detection (rug-pull protection)
|
|
278
|
+
|
|
279
|
+
IntentGuard can snapshot MCP `tools/list` metadata and detect changes over time.
|
|
280
|
+
|
|
281
|
+
Configure:
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
tool_change_rules:
|
|
285
|
+
enabled: true
|
|
286
|
+
action: warn # warn | block
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Behavior:
|
|
290
|
+
- On first `tools/list`, stores snapshot in `.intent-guard/tool-snapshots/<server-hash>.json`
|
|
291
|
+
- On subsequent `tools/list`, compares `name`, `description`, and `inputSchema`
|
|
292
|
+
- `warn`: log warning and continue
|
|
293
|
+
- `block`: block response when drift is detected
|
|
294
|
+
|
|
295
|
+
### Semantic mode and provider failure behavior
|
|
296
|
+
|
|
297
|
+
`semantic_rules.mode` controls normal semantic enforcement:
|
|
298
|
+
- `off`: semantic check disabled
|
|
299
|
+
- `enforce`: semantic failures block tool calls
|
|
300
|
+
- `advisory`: semantic failures are logged as warnings but calls are allowed
|
|
301
|
+
|
|
302
|
+
`semantic_rules.provider_fail_mode` controls behavior when semantic provider is unavailable:
|
|
303
|
+
- supports `default` and per-tool `by_tool` override
|
|
304
|
+
- values use the same mode set: `off|enforce|advisory`
|
|
305
|
+
|
|
306
|
+
Behavior matrix for tool criticality tiers (example mapping):
|
|
307
|
+
|
|
308
|
+
| Tool tier | `provider_fail_mode` | Outcome on provider outage |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| Critical tools | `enforce` | Fail-closed (block + approval required) |
|
|
311
|
+
| Standard tools | `advisory` | Fail-open with warning decision |
|
|
312
|
+
| Low-risk tools | `off` | Fail-open without warning severity |
|
|
313
|
+
|
|
314
|
+
Define tiers by assigning tools in `provider_fail_mode.by_tool`.
|
|
315
|
+
|
|
316
|
+
`semantic_rules.prompt_version` is copied into every semantic decision and log entry as `semantic_prompt_version` so prompt changes are auditable.
|
|
317
|
+
|
|
318
|
+
### Semantic decision caching
|
|
319
|
+
|
|
320
|
+
To reduce repeated provider calls for identical semantic evaluations:
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
semantic_rules:
|
|
324
|
+
decision_cache:
|
|
325
|
+
enabled: true
|
|
326
|
+
max_size: 256
|
|
327
|
+
ttl_seconds: 300
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Cache key uses `(tool_name, arguments, task_context)`. Static checks always run; only semantic verdicts are cached.
|
|
331
|
+
|
|
332
|
+
### LiteLLM provider
|
|
333
|
+
|
|
334
|
+
To use the API provider, set in `.env` (or process env):
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
LLM_MODEL=claude-3-5-sonnet-20241022
|
|
338
|
+
ANTHROPIC_API_KEY=...
|
|
339
|
+
# or OPENAI_API_KEY=...
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Then set `semantic_rules.provider: litellm` (or just set `LLM_MODEL` and omit explicit provider).
|
|
343
|
+
|
|
344
|
+
### CI break-glass options
|
|
345
|
+
|
|
346
|
+
- `INTENT_GUARD_BREAK_GLASS_TOKEN`: if set, flagged calls are auto-approved with override metadata.
|
|
347
|
+
- `INTENT_GUARD_BREAK_GLASS_SIGNED_TOKEN` + `INTENT_GUARD_BREAK_GLASS_SIGNING_KEY`: optional HMAC-signed break-glass token for CI. Token format is `<base64url(json payload)>.<base64url(signature)>` where signature is `HMAC-SHA256(payload_part, signing_key)` and payload contains future `exp` (unix timestamp), for example `{"exp": 4102444800}`.
|
|
348
|
+
- `INTENT_GUARD_APPROVAL_AUTH_TOKEN`: bearer token added to webhook approval requests.
|
|
349
|
+
|
|
350
|
+
## SDK usage (Python)
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from intent_guard import IntentGuardSDK
|
|
354
|
+
|
|
355
|
+
guard = IntentGuardSDK(
|
|
356
|
+
policy_path="schema/policy.yaml",
|
|
357
|
+
local_model="llama3.1:8b",
|
|
358
|
+
task_context="Only modify UI components"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
decision = guard.evaluate("write_file", {"path": "src/auth/config.py"})
|
|
362
|
+
print(decision.allowed, decision.reason)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## GuardDecision contract (stable)
|
|
366
|
+
|
|
367
|
+
`GuardDecision` now includes machine-readable metadata for enforcement and analytics:
|
|
368
|
+
|
|
369
|
+
- `decision_id` (UUID)
|
|
370
|
+
- `code`
|
|
371
|
+
- `severity`
|
|
372
|
+
- `policy_name`
|
|
373
|
+
- `policy_version`
|
|
374
|
+
- `rule_id`
|
|
375
|
+
- `timestamp` (UTC ISO-8601)
|
|
376
|
+
- `override` (`who`/`why`/`ttl`, when manually approved)
|
|
377
|
+
- `semantic_prompt_version` (when semantic checks are applied)
|
|
378
|
+
|
|
379
|
+
Backward compatibility:
|
|
380
|
+
- Existing fields `allowed`, `reason`, `requires_approval`, `semantic_score` are unchanged.
|
|
381
|
+
- New fields are always present with safe defaults, so existing consumers can ignore them.
|
|
382
|
+
|
|
383
|
+
## Semantic eval harness
|
|
384
|
+
|
|
385
|
+
IntentGuard ships a lightweight semantic eval harness used in tests to measure model behavior on known-safe and known-unsafe tool calls.
|
|
386
|
+
|
|
387
|
+
- Dataset fixtures: `tests/fixtures/semantic_eval_dataset.json`
|
|
388
|
+
- Replay verdicts: `tests/fixtures/semantic_eval_verdicts.json`
|
|
389
|
+
- Metrics computed: precision, recall, accuracy
|
|
390
|
+
|
|
391
|
+
This enables reproducible regression checks for semantic policy quality.
|
|
392
|
+
|
|
393
|
+
Versioning/migration strategy:
|
|
394
|
+
- Keep parsing logic tolerant of unknown fields.
|
|
395
|
+
- Use `policy_version` + `code` + `rule_id` for downstream contract evolution and dashboards.
|
|
396
|
+
- Prefer adding new fields over changing/removing existing field semantics.
|
|
397
|
+
|
|
398
|
+
## Usage examples with popular tools
|
|
399
|
+
|
|
400
|
+
### 1) Claude Code (MCP server proxy)
|
|
401
|
+
|
|
402
|
+
Configure the MCP server command to run through IntentGuard:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"mcpServers": {
|
|
407
|
+
"filesystem": {
|
|
408
|
+
"command": "python",
|
|
409
|
+
"args": [
|
|
410
|
+
"-m",
|
|
411
|
+
"intent_guard.proxy",
|
|
412
|
+
"--policy",
|
|
413
|
+
"schema/policy.yaml",
|
|
414
|
+
"--target",
|
|
415
|
+
"npx @modelcontextprotocol/server-filesystem /path/to/repo",
|
|
416
|
+
"--ask-approval"
|
|
417
|
+
],
|
|
418
|
+
"env": {
|
|
419
|
+
"INTENT_GUARD_TASK": "Refactor UI only; do not touch auth or database"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 2) Codex (MCP command wrapping)
|
|
427
|
+
|
|
428
|
+
For Codex setups that support MCP server command configuration, point the server command to IntentGuard first, then to your real MCP server as `--target`:
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
python -m intent_guard.proxy \
|
|
432
|
+
--policy schema/policy.yaml \
|
|
433
|
+
--target "npx @modelcontextprotocol/server-filesystem /path/to/repo" \
|
|
434
|
+
--ask-approval
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Use that command as the configured MCP server entry in your Codex environment.
|
|
438
|
+
|
|
439
|
+
### 3) LangSmith / LangChain workflows
|
|
440
|
+
|
|
441
|
+
Use IntentGuard before each tool execution and keep normal LangSmith tracing:
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from langsmith import traceable
|
|
445
|
+
from intent_guard import IntentGuardSDK
|
|
446
|
+
|
|
447
|
+
guard = IntentGuardSDK(
|
|
448
|
+
policy_path="schema/policy.yaml",
|
|
449
|
+
task_context="Only update docs and UI text"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
@traceable(name="guarded_tool_call")
|
|
453
|
+
def guarded_call(tool_name: str, args: dict, tool_callable):
|
|
454
|
+
decision = guard.evaluate(tool_name, args)
|
|
455
|
+
if not decision.allowed:
|
|
456
|
+
raise PermissionError(f"IntentGuard blocked: {decision.reason}")
|
|
457
|
+
return tool_callable(**args)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
This keeps execution decisions visible in traces while enforcing IntentGuard policy at runtime.
|
|
461
|
+
|
|
462
|
+
## Build and publish (pip / Artifactory)
|
|
463
|
+
|
|
464
|
+
Build source and wheel distributions:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
python3 -m venv .venv
|
|
468
|
+
.venv/bin/pip install -U pip build twine
|
|
469
|
+
.venv/bin/python -m build
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Publish to your Artifactory PyPI repository:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
export TWINE_USERNAME="<artifactory-username>"
|
|
476
|
+
export TWINE_PASSWORD="<artifactory-password-or-token>"
|
|
477
|
+
.venv/bin/python -m twine upload \
|
|
478
|
+
--repository-url "https://<artifactory-host>/artifactory/api/pypi/<pypi-repo>/local" \
|
|
479
|
+
dist/*
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Integration testing and Docker
|
|
483
|
+
|
|
484
|
+
Current integration tests are in-process (`tests/test_integration_phases.py`) and do not require a database or cache service.
|
|
485
|
+
If a future change adds external DB/cache dependencies, run those services in Docker for tests (same pattern as `temp-noob/rule-engine`) so test setup remains reproducible.
|