Skip to content

Commit 58ec7cc

Browse files
docs: add Tutorial 48 - Intent-Based Authorization (#1824)
* feat: add agent-rag-governance package for retrieval access control Closes #1700 Adds a new `agent-rag-governance` package that closes the governance gap between write-time memory protection (MemoryGuard) and output-quality checks (ContentGovernance) by enforcing policy at retrieval time. The package wraps any LangChain-compatible retriever with a `RAGGovernor` that enforces four controls on every retrieval call: - Collection access control: per-agent allow/deny lists block cross-tenant data leaks (e.g. a customer-facing agent querying `hr_records`) - Rate limiting: sliding-window cap on retrievals/min prevents runaway retrieval loops with no circuit breaker - Content scanning: PII (email, phone, SSN, credit card) and prompt-injection pattern detection on retrieved chunks before they reach the LLM, reusing the same regex taxonomy as `memory_guard` - Audit logging: structured JSON-lines record per call with query stored as SHA-256 hash only, enabling EU AI Act traceability requirements No required framework dependencies — LangChain is an optional extra. Pure Python sliding-window rate limiter (no Redis). Drop-in wrapper supports both `.invoke()` (LangChain v0.2+) and `.get_relevant_documents()` (v0.1 compat). 35 tests across 5 test files, all passing. Added to CI lint, test, and build-pypi matrices. Signed-off-by: prashansapkota <prashan.sapkota3456@gmail.com> * docs: add Tutorial 48 - Intent-Based Authorization Closes #1823 Signed-off-by: Prashan Sapkota <prashan.sapkota3456@gmail.com> Signed-off-by: prashansapkota <prashan.sapkota3456@gmail.com> --------- Signed-off-by: prashansapkota <prashan.sapkota3456@gmail.com> Signed-off-by: Prashan Sapkota <prashan.sapkota3456@gmail.com>
1 parent 9dc13ec commit 58ec7cc

2 files changed

Lines changed: 354 additions & 0 deletions

File tree

agent-governance-python/agent-os/docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Learn by doing with our Jupyter notebooks:
3030

3131
- [Using Message Bus Adapters](tutorials/message-bus-adapters.md) - Connect agents with Redis, Kafka, NATS
3232
- [Creating Custom Tools](tutorials/custom-tools.md) - Build safe tools for agents
33+
- [Intent-Based Authorization](tutorials/48-intent-based-authorization.md) - Declare, approve, and verify agent actions with drift detection
3334

3435
### 🏗️ Architecture
3536

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# Tutorial 48: Intent-Based Authorization
2+
3+
> **Declare what your agent will do before it does it, and automatically detect when it strays.**
4+
5+
Intent-Based Authorization is a governance layer that sits between an agent and its actions. Before execution begins, the agent declares a *plan* (`declare_intent`). A reviewer or system approves it (`approve_intent`). Each action is checked against that plan (`check_action`). When the session ends, the system compares planned vs. actual (`verify_intent`) and surfaces any drift.
6+
7+
This tutorial covers:
8+
9+
1. [The core lifecycle](#1-core-lifecycle-declare-approve-execute-verify)
10+
2. [Drift detection: SOFT_BLOCK vs HARD_BLOCK](#2-drift-detection-policies)
11+
3. [Child intent scope narrowing for multi-agent systems](#3-child-intent-scope-narrowing)
12+
4. [Running the complete demo](#4-running-the-demo)
13+
14+
---
15+
16+
## Prerequisites
17+
18+
```bash
19+
pip install agent-os-kernel
20+
```
21+
22+
Python 3.9+ required.
23+
24+
---
25+
26+
## 1. Core Lifecycle: Declare, Approve, Execute, Verify
27+
28+
### Step 1: Declare Intent
29+
30+
The agent announces what it plans to do. Nothing runs yet.
31+
32+
```python
33+
import asyncio
34+
from agent_os.intent import DriftPolicy, IntentAction, IntentManager
35+
from agent_os.stateless import MemoryBackend
36+
37+
async def main():
38+
manager = IntentManager(backend=MemoryBackend())
39+
40+
intent = await manager.declare_intent(
41+
agent_id="payment-agent",
42+
planned_actions=[
43+
IntentAction(action="read_balance"),
44+
IntentAction(action="transfer_funds", params_schema={"max_amount": 1000}),
45+
],
46+
drift_policy=DriftPolicy.SOFT_BLOCK,
47+
ttl_seconds=300,
48+
)
49+
50+
print(f"Intent ID: {intent.intent_id}")
51+
print(f"State: {intent.state.value}") # declared
52+
print(f"Actions: {intent.planned_action_names}")
53+
```
54+
55+
`IntentAction` accepts an optional `params_schema`, a dict of parameter constraints the action must satisfy. `ttl_seconds` sets an expiry; the intent is rejected if approval or execution happens after it expires.
56+
57+
### Step 2: Approve Intent
58+
59+
Approval moves the intent from `declared` to `approved`, signalling that a human reviewer or automated policy gate has signed off.
60+
61+
```python
62+
intent = await manager.approve_intent(intent.intent_id)
63+
print(f"State: {intent.state.value}") # approved
64+
```
65+
66+
In production this step would be wired to a human-in-the-loop approval queue or a policy engine.
67+
68+
### Step 3: Check Actions at Runtime
69+
70+
Before each action executes, call `check_action`. The manager records the result and transitions the intent to `executing` on the first call.
71+
72+
```python
73+
# Planned action - allowed
74+
check = await manager.check_action(
75+
intent.intent_id,
76+
"read_balance",
77+
{},
78+
"payment-agent",
79+
"req-001",
80+
)
81+
print(f"read_balance: {'ALLOWED' if check.allowed else 'BLOCKED'}")
82+
print(f" was_planned: {check.was_planned}")
83+
84+
# Unplanned action - drift detected
85+
check = await manager.check_action(
86+
intent.intent_id,
87+
"delete_account",
88+
{},
89+
"payment-agent",
90+
"req-002",
91+
)
92+
print(f"delete_account: {'ALLOWED' if check.allowed else 'BLOCKED'}")
93+
if check.drift_policy_applied:
94+
print(f" policy: {check.drift_policy_applied.value}")
95+
print(f" penalty: -{check.trust_penalty} trust points")
96+
```
97+
98+
`IntentCheckResult` fields:
99+
100+
| Field | Type | Description |
101+
|---|---|---|
102+
| `allowed` | `bool` | Whether the action may proceed |
103+
| `was_planned` | `bool` | Whether the action was in the declared plan |
104+
| `drift_policy_applied` | `DriftPolicy \| None` | Policy triggered on drift |
105+
| `trust_penalty` | `float` | Trust score deduction (default 50.0) |
106+
| `reason` | `str` | Human-readable explanation |
107+
108+
### Step 4: Verify Intent
109+
110+
`verify_intent` closes the session and produces a structured audit report comparing planned vs. actual.
111+
112+
```python
113+
verification = await manager.verify_intent(intent.intent_id)
114+
115+
print(f"Final state: {verification.state.value}") # violated or completed
116+
print(f"Planned: {verification.planned_actions}")
117+
print(f"Executed: {verification.executed_actions}")
118+
print(f"Unplanned: {verification.unplanned_actions}")
119+
print(f"Missed: {verification.missed_actions}")
120+
print(f"Drift events: {verification.total_drift_events}")
121+
print(f"Trust penalty: {verification.total_trust_penalty}")
122+
123+
asyncio.run(main())
124+
```
125+
126+
The intent transitions to `completed` when there are no drift events, or `violated` when drift was detected.
127+
128+
`IntentVerification` fields:
129+
130+
| Field | Description |
131+
|---|---|
132+
| `planned_actions` | Actions declared before execution |
133+
| `executed_actions` | Actions that ran and succeeded |
134+
| `unplanned_actions` | Executed actions not in the plan |
135+
| `missed_actions` | Planned actions that never ran |
136+
| `total_drift_events` | Count of drift detections |
137+
| `total_trust_penalty` | Cumulative trust score deducted |
138+
| `duration_seconds` | Seconds from approval to verification |
139+
140+
---
141+
142+
## 2. Drift Detection Policies
143+
144+
There are three `DriftPolicy` values. Set the policy at `declare_intent` time.
145+
146+
### SOFT_BLOCK (default)
147+
148+
Unplanned actions are *allowed* but flagged. A trust penalty is applied and a `DriftEvent` is recorded. Use this when continuity matters more than strict enforcement.
149+
150+
```python
151+
intent = await manager.declare_intent(
152+
agent_id="payment-agent",
153+
planned_actions=[IntentAction(action="read_balance")],
154+
drift_policy=DriftPolicy.SOFT_BLOCK,
155+
)
156+
intent = await manager.approve_intent(intent.intent_id)
157+
158+
check = await manager.check_action(
159+
intent.intent_id, "send_notification", {}, "payment-agent", "req-003"
160+
)
161+
print(check.allowed) # True - action proceeds
162+
print(check.trust_penalty) # 50.0 - penalty recorded
163+
```
164+
165+
### HARD_BLOCK
166+
167+
Unplanned actions are *denied outright*. Use this for compliance-critical agents where no deviation is acceptable.
168+
169+
```python
170+
from agent_os.intent import DriftPolicy, IntentAction, IntentManager
171+
from agent_os.stateless import MemoryBackend
172+
173+
async def hard_block_demo():
174+
manager = IntentManager(backend=MemoryBackend())
175+
176+
intent = await manager.declare_intent(
177+
agent_id="compliance-agent",
178+
planned_actions=[IntentAction(action="generate_report")],
179+
drift_policy=DriftPolicy.HARD_BLOCK,
180+
)
181+
intent = await manager.approve_intent(intent.intent_id)
182+
183+
# Planned - allowed
184+
check = await manager.check_action(
185+
intent.intent_id, "generate_report", {}, "compliance-agent", "req-010"
186+
)
187+
print(f"generate_report: {'ALLOWED' if check.allowed else 'BLOCKED'}") # ALLOWED
188+
189+
# Unplanned - blocked
190+
check = await manager.check_action(
191+
intent.intent_id, "send_email", {}, "compliance-agent", "req-011"
192+
)
193+
print(f"send_email: {'ALLOWED' if check.allowed else 'BLOCKED'}") # BLOCKED
194+
```
195+
196+
### RE_DECLARE
197+
198+
Unplanned actions are denied, and the agent must declare a new intent before continuing. Use this when scope changes require a full re-review cycle.
199+
200+
```python
201+
intent = await manager.declare_intent(
202+
agent_id="my-agent",
203+
planned_actions=[IntentAction(action="read_config")],
204+
drift_policy=DriftPolicy.RE_DECLARE,
205+
)
206+
```
207+
208+
---
209+
210+
## 3. Child Intent Scope Narrowing
211+
212+
In multi-agent orchestration, an orchestrator declares a broad intent and delegates sub-tasks to specialist agents. Child intents must be a *subset* of the parent's planned actions; they cannot expand scope.
213+
214+
```python
215+
from agent_os.intent import IntentAction, IntentManager, IntentScopeError
216+
from agent_os.stateless import MemoryBackend
217+
218+
async def multi_agent_demo():
219+
manager = IntentManager(backend=MemoryBackend())
220+
221+
# Orchestrator declares the full scope
222+
parent = await manager.declare_intent(
223+
agent_id="orchestrator",
224+
planned_actions=[
225+
IntentAction(action="read_balance"),
226+
IntentAction(action="transfer_funds"),
227+
IntentAction(action="generate_report"),
228+
],
229+
)
230+
parent = await manager.approve_intent(parent.intent_id)
231+
print(f"Parent scope: {parent.planned_action_names}")
232+
233+
# Sub-agent gets only the actions it needs
234+
child = await manager.declare_intent(
235+
agent_id="report-agent",
236+
planned_actions=[IntentAction(action="generate_report")],
237+
parent_intent_id=parent.intent_id,
238+
)
239+
print(f"Child scope: {child.planned_action_names}") # {'generate_report'}
240+
241+
# A rogue agent trying to exceed parent scope is rejected
242+
try:
243+
await manager.declare_intent(
244+
agent_id="rogue-agent",
245+
planned_actions=[IntentAction(action="delete_everything")],
246+
parent_intent_id=parent.intent_id,
247+
)
248+
except IntentScopeError as e:
249+
print(f"Scope violation blocked: {e}")
250+
```
251+
252+
`IntentScopeError` is raised at `declare_intent` time, before the intent is stored, so there is no window in which the child intent exists with excess scope.
253+
254+
You can also use the convenience method `create_child_intent`, which inherits the parent's drift policy by default:
255+
256+
```python
257+
child = await manager.create_child_intent(
258+
parent_intent_id=parent.intent_id,
259+
agent_id="report-agent",
260+
planned_actions=[IntentAction(action="generate_report")],
261+
)
262+
```
263+
264+
---
265+
266+
## 4. Running the Demo
267+
268+
A complete, runnable script is included in the repository:
269+
270+
```bash
271+
pip install agent-os-kernel
272+
python examples/intent-auth/intent_auth_demo.py
273+
```
274+
275+
Expected output:
276+
277+
```
278+
============================================================
279+
Intent-Based Authorization Demo
280+
============================================================
281+
282+
--- Step 1: Declare Intent ---
283+
Intent ID: intent:...
284+
State: declared
285+
Actions: {'read_balance', 'transfer_funds'}
286+
287+
--- Step 2: Approve Intent ---
288+
State: approved
289+
290+
--- Step 3: Execute Actions ---
291+
read_balance: ALLOWED (planned)
292+
delete_account: ALLOWED (DRIFT!)
293+
policy: soft_block
294+
penalty: -50.0 trust points
295+
296+
--- Step 4: Verify Intent ---
297+
Final state: violated
298+
Planned: ['read_balance', 'transfer_funds']
299+
Executed: ['read_balance', 'delete_account']
300+
Unplanned: ['delete_account']
301+
Missed: ['transfer_funds']
302+
Drift events: 1
303+
Trust penalty: 50.0
304+
...
305+
```
306+
307+
---
308+
309+
## Intent Lifecycle States
310+
311+
```
312+
DECLARED --approve--> APPROVED --first action--> EXECUTING
313+
|
314+
+----------------+
315+
v v
316+
COMPLETED VIOLATED
317+
(no drift) (drift found)
318+
319+
Any state --ttl expired--> EXPIRED
320+
```
321+
322+
| State | Description |
323+
|---|---|
324+
| `declared` | Intent created, awaiting approval |
325+
| `approved` | Approved, ready for execution |
326+
| `executing` | First `check_action` call received |
327+
| `completed` | `verify_intent` called, no drift |
328+
| `violated` | `verify_intent` called, drift detected |
329+
| `expired` | TTL elapsed before completion |
330+
331+
---
332+
333+
## Key Classes
334+
335+
| Class / Function | Purpose |
336+
|---|---|
337+
| `IntentManager(backend)` | Main entry point; all methods are async |
338+
| `IntentAction(action, params_schema)` | Declares one planned action |
339+
| `DriftPolicy` | Enum: `SOFT_BLOCK`, `HARD_BLOCK`, `RE_DECLARE` |
340+
| `IntentCheckResult` | Return value of `check_action` |
341+
| `IntentVerification` | Return value of `verify_intent` |
342+
| `IntentScopeError` | Raised when child intent exceeds parent scope |
343+
| `MemoryBackend` | In-process backend for development and testing |
344+
345+
For production deployments, replace `MemoryBackend` with `RedisBackend` to share intent state across multiple agent replicas.
346+
347+
---
348+
349+
## Next Steps
350+
351+
- **[5-Minute Quickstart](5-minute-quickstart.md)** - set up your first governed agent
352+
- **[30-Minute Deep Dive](30-minute-deep-dive.md)** - trust scoring, policy engines, and receipts
353+
- **[Custom Tools Tutorial](custom-tools.md)** - register tools that are checked against intent at runtime

0 commit comments

Comments
 (0)