weaver-kernel 0.7.0__tar.gz → 0.8.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.
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/.github/workflows/ci.yml +1 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/CHANGELOG.md +68 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/Makefile +1 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/PKG-INFO +42 -1
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/README.md +41 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/architecture.md +7 -2
- weaver_kernel-0.8.0/docs/security.md +95 -0
- weaver_kernel-0.8.0/docs/tutorial.md +293 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/examples/basic_cli.py +1 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/examples/billing_demo.py +2 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/examples/http_driver_demo.py +1 -0
- weaver_kernel-0.8.0/examples/tutorial.py +235 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/pyproject.toml +1 -1
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/__init__.py +2 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/enums.py +8 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/errors.py +19 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/handles.py +125 -8
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/kernel.py +55 -4
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/models.py +18 -1
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/policy.py +77 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/policy_reasons.py +25 -0
- weaver_kernel-0.8.0/tests/test_firewall_boundary.py +274 -0
- weaver_kernel-0.8.0/tests/test_handles.py +320 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_kernel.py +3 -1
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_logging.py +2 -2
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_policy.py +111 -0
- weaver_kernel-0.7.0/docs/security.md +0 -42
- weaver_kernel-0.7.0/tests/test_handles.py +0 -147
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/.claude/CLAUDE.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/.github/copilot-instructions.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/.github/workflows/publish.yml +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/.gitignore +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/AGENTS.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/CONTRIBUTING.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/LICENSE +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/RELEASE.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/agent-context/architecture.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/agent-context/invariants.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/agent-context/lessons-learned.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/agent-context/review-checklist.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/agent-context/workflows.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/capabilities.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/context_firewall.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/docs/integrations.md +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/examples/policies/default.toml +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/examples/policies/default.yaml +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/adapters/__init__.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/adapters/_base.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/adapters/anthropic.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/adapters/openai.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/__init__.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/base.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/http.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/mcp.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/mcp_support.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/drivers/memory.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/__init__.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/budget_manager.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/budgets.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/redaction.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/summarize.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/token_counting.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/firewall/transform.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/policy_dsl.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/py.typed +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/registry.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/router.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/tokens.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/src/agent_kernel/trace.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/conftest.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_adapters.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_drivers.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_firewall.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_mcp_driver.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_models.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_redaction.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_registry.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_router.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_tokens.py +0 -0
- {weaver_kernel-0.7.0 → weaver_kernel-0.8.0}/tests/test_trace.py +0 -0
|
@@ -7,6 +7,74 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- "Secure your first MCP tool in 5 minutes" tutorial: new
|
|
14
|
+
[`docs/tutorial.md`](docs/tutorial.md) walks a new reader from install to a
|
|
15
|
+
working invocation, covering registration, principals, grants, the three
|
|
16
|
+
LLM-safe response modes (`summary` / `table` / `handle_only`), handle
|
|
17
|
+
expansion, policy denial with stable `reason_code`, and `explain()`
|
|
18
|
+
audit. The admin-only `raw` mode is described but not exercised by the
|
|
19
|
+
walkthrough. Companion runnable example
|
|
20
|
+
[`examples/tutorial.py`](examples/tutorial.py) uses `InMemoryDriver`
|
|
21
|
+
(offline, zero external deps) and is exercised by `make example` and CI;
|
|
22
|
+
it now `assert`s that no PII field leaks into the LLM-safe Frame so a
|
|
23
|
+
firewall regression fails the build. (#46)
|
|
24
|
+
- README "How this relates to neighboring projects" section: a neutral
|
|
25
|
+
boundaries table covering `AgentFence` (external CLI/proxy gate),
|
|
26
|
+
`contextweaver` (context compilation library), `ChainWeaver`
|
|
27
|
+
(deterministic flow orchestrator), and `weaver-spec` (specification +
|
|
28
|
+
conformance suite), plus a "When *not* to use this" callout. (#71)
|
|
29
|
+
- Grant-constraint enforcement on handle expansion (#76). `Handle` now carries
|
|
30
|
+
the `principal_id` and `constraints` from the original grant, persisted at
|
|
31
|
+
handle creation time by `HandleStore.store`. `HandleStore.expand` rechecks
|
|
32
|
+
these against the requested expand query:
|
|
33
|
+
- A request `limit` larger than the grant's `max_rows` is rejected with
|
|
34
|
+
`HandleConstraintViolation` (`reason_code = handle_constraint_violation`).
|
|
35
|
+
- A request `fields` entry outside `allowed_fields` is rejected; an
|
|
36
|
+
unscoped expand applies `allowed_fields` as the default projection.
|
|
37
|
+
- A request filter that disagrees with the grant's `scope` is rejected;
|
|
38
|
+
the scope filter is otherwise AND-merged so the caller cannot bypass it.
|
|
39
|
+
- A `principal_id` parameter that does not match the handle's stored
|
|
40
|
+
principal raises `HandleConstraintViolation`
|
|
41
|
+
(`reason_code = handle_principal_mismatch`).
|
|
42
|
+
- `SensitivityTag.MEMORY` and memory-action policy rules in
|
|
43
|
+
`DefaultPolicyEngine` (#75). Project-scoped memory reads are allowed by
|
|
44
|
+
default; sensitive-scoped reads require the `memory_reader_sensitive` role
|
|
45
|
+
(or `admin`); writes always require `memory_writer` (or `admin`). The
|
|
46
|
+
`explain()` path lists the same conditions with stable `reason_code`s.
|
|
47
|
+
- New stable `DenialReason` codes: `HANDLE_CONSTRAINT_VIOLATION`,
|
|
48
|
+
`HANDLE_PRINCIPAL_MISMATCH`, `MEMORY_WRITE_REQUIRES_WRITER`,
|
|
49
|
+
`MEMORY_SENSITIVE_READ_DENIED`.
|
|
50
|
+
- `HandleConstraintViolation` error class (subclass of `AgentKernelError`,
|
|
51
|
+
exported from `agent_kernel`) — carries an optional `reason_code` matching
|
|
52
|
+
the `DenialReason` vocabulary so handle-side and grant-side denials share
|
|
53
|
+
one set of stable codes.
|
|
54
|
+
- `Kernel.expand` accepts an optional `principal: Principal` argument that
|
|
55
|
+
is forwarded to `HandleStore.expand` for principal-mismatch checks.
|
|
56
|
+
- Memory-action input redaction (#75): `ActionTrace.args` for any capability
|
|
57
|
+
whose ID starts with `memory.` has payload-like keys (`payload`, `content`,
|
|
58
|
+
`value`, `memory`, `text`, `body`) replaced with `"[REDACTED]"`. Keys are
|
|
59
|
+
preserved so audit can confirm the action took place without exposing the
|
|
60
|
+
durable content the agent wrote or read.
|
|
61
|
+
- New `tests/test_firewall_boundary.py` (#74) — focused regression suite that
|
|
62
|
+
pushes synthetic secret/PII values through the raw → `Frame` boundary
|
|
63
|
+
end-to-end and asserts those values never appear in summary/table/raw
|
|
64
|
+
frames, are stripped by `allowed_fields`, never reach `ActionTrace.args`
|
|
65
|
+
for memory capabilities, and stay quarantined when raw mode is downgraded
|
|
66
|
+
for non-admin principals.
|
|
67
|
+
|
|
68
|
+
### Security
|
|
69
|
+
- Closes #76: handle expansion can no longer return data outside the original
|
|
70
|
+
grant's `max_rows` / `allowed_fields` / `scope`, and handle IDs are no
|
|
71
|
+
longer bearer credentials that work across principals.
|
|
72
|
+
- Closes #75: memory reads and writes are governed actions with stable
|
|
73
|
+
denial codes and trace-side redaction of durable payloads.
|
|
74
|
+
- Closes #74: redaction boundary is pinned by negative assertions against
|
|
75
|
+
fake-secret strings, catching future regressions that drop a redaction
|
|
76
|
+
step or route raw data through a new path.
|
|
77
|
+
|
|
10
78
|
## [0.7.0] - 2026-05-20
|
|
11
79
|
|
|
12
80
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weaver-kernel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Capability-based security kernel for AI agents operating in large tool ecosystems
|
|
5
5
|
Project-URL: Homepage, https://github.com/dgenio/agent-kernel
|
|
6
6
|
Project-URL: Repository, https://github.com/dgenio/agent-kernel
|
|
@@ -289,6 +289,8 @@ pip install weaver-kernel
|
|
|
289
289
|
|
|
290
290
|
> **Note:** The PyPI package is `weaver-kernel` (Weaver ecosystem), but the Python import remains `agent_kernel`.
|
|
291
291
|
|
|
292
|
+
> **New here?** [docs/tutorial.md](docs/tutorial.md) walks through register → grant → invoke → expand → explain in five minutes.
|
|
293
|
+
|
|
292
294
|
```python
|
|
293
295
|
import asyncio, os
|
|
294
296
|
os.environ["AGENT_KERNEL_SECRET"] = "my-secret"
|
|
@@ -356,6 +358,45 @@ asyncio.run(main())
|
|
|
356
358
|
|
|
357
359
|
`agent-kernel` sits **above** `contextweaver` (context compilation) and **above** raw tool execution. It provides the authorization, execution, and audit layer.
|
|
358
360
|
|
|
361
|
+
## How this relates to neighboring projects
|
|
362
|
+
|
|
363
|
+
`agent-kernel` is the embeddable runtime layer of the **Weaver ecosystem**. The
|
|
364
|
+
projects below solve adjacent problems and are designed to compose, not to
|
|
365
|
+
overlap.
|
|
366
|
+
|
|
367
|
+
| Project | Role | Where it runs | Use it when… |
|
|
368
|
+
|---|---|---|---|
|
|
369
|
+
| **agent-kernel** *(this repo)* | Embeddable library/runtime: capability registry, policy, HMAC tokens, context firewall, audit trace. | In-process inside your agent host. | You need authorization, redaction, and audit between an LLM loop and a large tool ecosystem. |
|
|
370
|
+
| [**AgentFence**](https://github.com/dgenio/AgentFence) | External CLI / local proxy that intercepts tool calls and applies a policy gate. | Out-of-process, alongside your agent. | You want a policy boundary without changing your agent code, or you need to gate a third-party agent host you can't modify. |
|
|
371
|
+
| [**contextweaver**](https://github.com/dgenio/contextweaver) | Library that selects and compiles the context an LLM receives. | In-process, before the LLM call. | You need to assemble relevant context for a prompt. It sits *under* the LLM loop; agent-kernel sits *between* the LLM and tools. |
|
|
372
|
+
| **ChainWeaver** | Orchestrator for deterministic tool chains. | In-process or as a separate service. | You need to run a multi-step deterministic flow rather than free-form LLM tool use. |
|
|
373
|
+
| [**weaver-spec**](https://github.com/dgenio/weaver-spec) | Specification: invariants, capability/token/frame contracts, conformance suite. | Not a runtime — it's docs + a contract test suite. | You're building another Weaver-compatible implementation, or you want to verify an existing one. |
|
|
374
|
+
|
|
375
|
+
A minimal architecture using `agent-kernel` as the central runtime:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
LLM / agent loop
|
|
379
|
+
│
|
|
380
|
+
▼
|
|
381
|
+
contextweaver ─► agent-kernel ─► driver ─► MCP / HTTP / A2A / internal API
|
|
382
|
+
│
|
|
383
|
+
▼
|
|
384
|
+
ActionTrace
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### When *not* to use this
|
|
388
|
+
|
|
389
|
+
- You only need a process-level policy gate around an existing agent host —
|
|
390
|
+
reach for `AgentFence` instead.
|
|
391
|
+
- You only need to compile context for a prompt — use `contextweaver`.
|
|
392
|
+
- You want a deterministic, scripted workflow with no LLM in the inner loop —
|
|
393
|
+
use `ChainWeaver`.
|
|
394
|
+
- You're writing a static analyzer or one-shot CLI scanner with no
|
|
395
|
+
per-invocation runtime — `agent-kernel` would be overkill.
|
|
396
|
+
|
|
397
|
+
See [docs/tutorial.md](docs/tutorial.md) for an end-to-end "secure your first
|
|
398
|
+
MCP tool in 5 minutes" walkthrough.
|
|
399
|
+
|
|
359
400
|
## Weaver Spec Compatibility: v0.1.0
|
|
360
401
|
|
|
361
402
|
agent-kernel is a compliant implementation of [weaver-spec v0.1.0](https://github.com/dgenio/weaver-spec).
|
|
@@ -43,6 +43,8 @@ pip install weaver-kernel
|
|
|
43
43
|
|
|
44
44
|
> **Note:** The PyPI package is `weaver-kernel` (Weaver ecosystem), but the Python import remains `agent_kernel`.
|
|
45
45
|
|
|
46
|
+
> **New here?** [docs/tutorial.md](docs/tutorial.md) walks through register → grant → invoke → expand → explain in five minutes.
|
|
47
|
+
|
|
46
48
|
```python
|
|
47
49
|
import asyncio, os
|
|
48
50
|
os.environ["AGENT_KERNEL_SECRET"] = "my-secret"
|
|
@@ -110,6 +112,45 @@ asyncio.run(main())
|
|
|
110
112
|
|
|
111
113
|
`agent-kernel` sits **above** `contextweaver` (context compilation) and **above** raw tool execution. It provides the authorization, execution, and audit layer.
|
|
112
114
|
|
|
115
|
+
## How this relates to neighboring projects
|
|
116
|
+
|
|
117
|
+
`agent-kernel` is the embeddable runtime layer of the **Weaver ecosystem**. The
|
|
118
|
+
projects below solve adjacent problems and are designed to compose, not to
|
|
119
|
+
overlap.
|
|
120
|
+
|
|
121
|
+
| Project | Role | Where it runs | Use it when… |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| **agent-kernel** *(this repo)* | Embeddable library/runtime: capability registry, policy, HMAC tokens, context firewall, audit trace. | In-process inside your agent host. | You need authorization, redaction, and audit between an LLM loop and a large tool ecosystem. |
|
|
124
|
+
| [**AgentFence**](https://github.com/dgenio/AgentFence) | External CLI / local proxy that intercepts tool calls and applies a policy gate. | Out-of-process, alongside your agent. | You want a policy boundary without changing your agent code, or you need to gate a third-party agent host you can't modify. |
|
|
125
|
+
| [**contextweaver**](https://github.com/dgenio/contextweaver) | Library that selects and compiles the context an LLM receives. | In-process, before the LLM call. | You need to assemble relevant context for a prompt. It sits *under* the LLM loop; agent-kernel sits *between* the LLM and tools. |
|
|
126
|
+
| **ChainWeaver** | Orchestrator for deterministic tool chains. | In-process or as a separate service. | You need to run a multi-step deterministic flow rather than free-form LLM tool use. |
|
|
127
|
+
| [**weaver-spec**](https://github.com/dgenio/weaver-spec) | Specification: invariants, capability/token/frame contracts, conformance suite. | Not a runtime — it's docs + a contract test suite. | You're building another Weaver-compatible implementation, or you want to verify an existing one. |
|
|
128
|
+
|
|
129
|
+
A minimal architecture using `agent-kernel` as the central runtime:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
LLM / agent loop
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
contextweaver ─► agent-kernel ─► driver ─► MCP / HTTP / A2A / internal API
|
|
136
|
+
│
|
|
137
|
+
▼
|
|
138
|
+
ActionTrace
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### When *not* to use this
|
|
142
|
+
|
|
143
|
+
- You only need a process-level policy gate around an existing agent host —
|
|
144
|
+
reach for `AgentFence` instead.
|
|
145
|
+
- You only need to compile context for a prompt — use `contextweaver`.
|
|
146
|
+
- You want a deterministic, scripted workflow with no LLM in the inner loop —
|
|
147
|
+
use `ChainWeaver`.
|
|
148
|
+
- You're writing a static analyzer or one-shot CLI scanner with no
|
|
149
|
+
per-invocation runtime — `agent-kernel` would be overkill.
|
|
150
|
+
|
|
151
|
+
See [docs/tutorial.md](docs/tutorial.md) for an end-to-end "secure your first
|
|
152
|
+
MCP tool in 5 minutes" walkthrough.
|
|
153
|
+
|
|
113
154
|
## Weaver Spec Compatibility: v0.1.0
|
|
114
155
|
|
|
115
156
|
agent-kernel is a compliant implementation of [weaver-spec v0.1.0](https://github.com/dgenio/weaver-spec).
|
|
@@ -52,8 +52,9 @@ Both built-in engines satisfy `ExplainingPolicyEngine`:
|
|
|
52
52
|
3. **DESTRUCTIVE** — requires role `admin` + `justification ≥ 15 chars`
|
|
53
53
|
4. **PII/PCI** — requires `tenant` attribute; enforces `allowed_fields` unless `pii_reader`
|
|
54
54
|
5. **SECRETS** — requires role `admin|secrets_reader` + `justification ≥ 15 chars`
|
|
55
|
-
6. **
|
|
56
|
-
7. **
|
|
55
|
+
6. **MEMORY** — `memory.read` with `scope.memory_scope == "sensitive"` requires role `memory_reader_sensitive|admin`; `memory.write` / DESTRUCTIVE memory requires role `memory_writer|admin`. Project-scoped memory reads are allowed by default. The kernel also redacts `payload`/`content`/`value`/`memory`/`text`/`body` keys from `ActionTrace.args` for any capability whose ID starts with `memory.`
|
|
56
|
+
7. **max_rows** — 50 (user), 500 (service)
|
|
57
|
+
8. **Rate limiting** — sliding-window per `(principal_id, capability_id)` (60 READ / 10 WRITE / 2 DESTRUCTIVE per 60s; service role gets 10×)
|
|
57
58
|
- **`DeclarativePolicyEngine`** — loads rules from a YAML or TOML file (or a plain dict). Supports `safety_class`, `sensitivity`, `roles`, `attributes`, `min_justification`, `intent`, and `scope` match conditions; `allow`/`deny` actions; per-rule `constraints` merged into the resulting `PolicyDecision`; configurable `default` action. Rules are evaluated top-down with first-match-wins. `pyyaml` and `tomli` are optional dependencies — `import agent_kernel` works without them; calling `from_yaml`/`from_toml` without the parser raises `PolicyConfigError` with an install hint.
|
|
58
59
|
|
|
59
60
|
#### Intent and scope on requests
|
|
@@ -96,6 +97,10 @@ Every `PolicyDecision`, `DenialExplanation`, `FailedCondition`, and `PolicyDenie
|
|
|
96
97
|
| `explicit_deny_rule` | DSL: a `deny` rule matched fully |
|
|
97
98
|
| `intent_not_allowed` | DSL: `match.intent` rejected the request's intent |
|
|
98
99
|
| `scope_not_allowed` | DSL: `match.scope` rejected the request's scope |
|
|
100
|
+
| `handle_constraint_violation` | `HandleStore.expand` request exceeded grant's `max_rows`, `allowed_fields`, or `scope` (#76) |
|
|
101
|
+
| `handle_principal_mismatch` | Handle expansion attempted by a different principal than the one the original grant was issued to (#76) |
|
|
102
|
+
| `memory_write_requires_writer` | `SensitivityTag.MEMORY` WRITE/DESTRUCTIVE without `memory_writer` or `admin` role (#75) |
|
|
103
|
+
| `memory_sensitive_read_denied` | `SensitivityTag.MEMORY` read with `scope.memory_scope == "sensitive"` without `memory_reader_sensitive` or `admin` role (#75) |
|
|
99
104
|
|
|
100
105
|
Allow-side codes (`AllowReason.*`): `default_policy_allow`, `rule_allow`, `default_fallthrough_allow`, `token_verified`.
|
|
101
106
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Security Model
|
|
2
|
+
|
|
3
|
+
## Threat model
|
|
4
|
+
|
|
5
|
+
| Threat | Mitigation |
|
|
6
|
+
|--------|-----------|
|
|
7
|
+
| Tool-space interference (agent calls wrong tool) | Capability registry + policy gate before any execution |
|
|
8
|
+
| Confused deputy attack | Tokens are bound to `principal_id` — cannot be reused by another principal |
|
|
9
|
+
| Token forgery / tampering | HMAC-SHA256 signature; any bit flip → `TokenInvalid` |
|
|
10
|
+
| Token replay after expiry | Expiry checked on every `verify()` call |
|
|
11
|
+
| Context injection via raw tool output | Firewall always transforms `RawResult → Frame`; raw data never reaches LLM by default |
|
|
12
|
+
| PII / PCI leakage | Redaction + `allowed_fields` enforcement in the firewall |
|
|
13
|
+
| Privilege escalation via WRITE/DESTRUCTIVE | Policy engine enforces role requirements |
|
|
14
|
+
| Audit evasion | Every `invoke()` creates an immutable `ActionTrace` |
|
|
15
|
+
| Handle scope escape (expand exceeds grant) | Handles persist grant constraints; `HandleStore.expand` rechecks `max_rows`, `allowed_fields`, `scope`, and principal binding (#76) |
|
|
16
|
+
| Memory exfiltration via tool output | `SensitivityTag.MEMORY` capabilities gate sensitive reads and durable writes; `ActionTrace.args` redacts payload-like fields for `memory.*` capabilities (#75) |
|
|
17
|
+
| Raw memory payload reaching audit log | Kernel strips `payload`/`content`/`value`/`memory`/`text`/`body` from `ActionTrace.args` for `memory.*` capabilities |
|
|
18
|
+
|
|
19
|
+
## Token scopes
|
|
20
|
+
|
|
21
|
+
A `CapabilityToken` binds:
|
|
22
|
+
- `capability_id` — which capability is authorized
|
|
23
|
+
- `principal_id` — who the token was issued to
|
|
24
|
+
- `constraints` — max_rows, allowed_fields, etc. (signed into the token)
|
|
25
|
+
- `expires_at` — validity window
|
|
26
|
+
|
|
27
|
+
Any change to these fields invalidates the HMAC signature.
|
|
28
|
+
|
|
29
|
+
## Confused deputy prevention
|
|
30
|
+
|
|
31
|
+
Consider an agent that obtains a token for `billing.list_invoices` then passes it to a different agent. The second agent cannot use it because `verify()` checks that `token.principal_id == expected_principal_id`.
|
|
32
|
+
|
|
33
|
+
The same principle extends to handles: every `Handle` carries the `principal_id`
|
|
34
|
+
the original grant was issued to. When `handle.principal_id` is non-empty,
|
|
35
|
+
`HandleStore.expand` rejects expansion unless the caller supplies a matching
|
|
36
|
+
`principal_id`. **An omitted or empty `principal_id` is treated as a
|
|
37
|
+
mismatch** (`HandleConstraintViolation`, `reason_code = HANDLE_PRINCIPAL_MISMATCH`),
|
|
38
|
+
so a handle ID alone is not a bearer credential — proof of the original
|
|
39
|
+
principal is always required. `Kernel.expand(..., principal=Principal(...))`
|
|
40
|
+
forwards the principal automatically.
|
|
41
|
+
|
|
42
|
+
## Handle expansion boundary
|
|
43
|
+
|
|
44
|
+
Calling `kernel.expand(handle, query=...)` does not re-run the policy engine —
|
|
45
|
+
the original grant already authorised the dataset, and handles are short-lived.
|
|
46
|
+
But the grant's _constraints_ must still apply, otherwise an over-broad
|
|
47
|
+
`expand` query would silently return data the original grant never covered.
|
|
48
|
+
|
|
49
|
+
`HandleStore.expand` rechecks the constraints the kernel persists on the handle
|
|
50
|
+
at creation time (`token.constraints`):
|
|
51
|
+
|
|
52
|
+
| Constraint | Enforced behavior on expand |
|
|
53
|
+
|------------|-----------------------------|
|
|
54
|
+
| `max_rows` | A request `limit` larger than the cap raises `HandleConstraintViolation`. An unspecified or larger implicit limit is silently clamped. |
|
|
55
|
+
| `allowed_fields` | A request `fields` entry that is not in `allowed_fields` raises `HandleConstraintViolation`. An unscoped expand applies `allowed_fields` as the default projection, so disallowed fields never leak. |
|
|
56
|
+
| `scope` (e.g. `{"region": "eu"}`) | The scope filter is AND-merged into the request filter. A request filter that disagrees on a scoped dimension raises `HandleConstraintViolation`. |
|
|
57
|
+
| `principal_id` | A mismatched `principal_id` parameter raises `HandleConstraintViolation` (`HANDLE_PRINCIPAL_MISMATCH`). |
|
|
58
|
+
|
|
59
|
+
Errors carry stable `reason_code` values (`handle_constraint_violation`,
|
|
60
|
+
`handle_principal_mismatch`) — assert on those, not on the message text.
|
|
61
|
+
|
|
62
|
+
## Memory actions
|
|
63
|
+
|
|
64
|
+
Capabilities tagged `SensitivityTag.MEMORY` represent durable agent memory
|
|
65
|
+
(project notes, session handoff, learned context). Reads of project-scoped
|
|
66
|
+
memory are allowed by default; reads of sensitive-scoped memory require an
|
|
67
|
+
explicit role. Writes always require the `memory_writer` role (or `admin`)
|
|
68
|
+
because they persist into future sessions.
|
|
69
|
+
|
|
70
|
+
| Action | Required role | Denial reason code |
|
|
71
|
+
|--------|---------------|--------------------|
|
|
72
|
+
| `memory.read` with `scope["memory_scope"] == "project"` | none | — |
|
|
73
|
+
| `memory.read` with `scope["memory_scope"] == "sensitive"` | `memory_reader_sensitive` or `admin` | `memory_sensitive_read_denied` |
|
|
74
|
+
| `memory.write` (any scope) | `memory_writer` or `admin` | `memory_write_requires_writer` |
|
|
75
|
+
| `memory.forget` (DESTRUCTIVE) | `admin` (then `memory_writer` or `admin`) | `missing_role`, then `memory_write_requires_writer` |
|
|
76
|
+
|
|
77
|
+
To prevent durable memory content from leaking into the audit log, the kernel
|
|
78
|
+
strips payload-like fields (`payload`, `content`, `value`, `memory`, `text`,
|
|
79
|
+
`body`) from `ActionTrace.args` for any capability whose ID begins with
|
|
80
|
+
`memory.`. Non-sensitive metadata keys (`key`, `id`, `scope`, ...) are
|
|
81
|
+
preserved so audit can still confirm an action took place.
|
|
82
|
+
|
|
83
|
+
## Security disclaimers
|
|
84
|
+
|
|
85
|
+
> **v0.1 is not production-hardened for real authentication.**
|
|
86
|
+
|
|
87
|
+
- HMAC tokens are tamper-evident but **not encrypted**. Do not put sensitive data in token fields.
|
|
88
|
+
- The `AGENT_KERNEL_SECRET` must be kept secret. Rotate it if compromised.
|
|
89
|
+
- The default `InMemoryDriver` has no persistence — suitable for testing only.
|
|
90
|
+
- PII redaction is heuristic (regex-based). It is not a substitute for proper data governance.
|
|
91
|
+
- Rate limiting is enforced per `(principal_id, capability_id)` pair using a sliding window.
|
|
92
|
+
Default limits: 60 READ / 10 WRITE / 2 DESTRUCTIVE invocations per 60-second window.
|
|
93
|
+
Principals with the `"service"` role receive 10× the default limits. Limits are
|
|
94
|
+
configurable via `DefaultPolicyEngine(rate_limits=...)`. There is no distributed or
|
|
95
|
+
persistent rate-limit state — limits reset on process restart.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Secure your first MCP tool in 5 minutes
|
|
2
|
+
|
|
3
|
+
This walkthrough takes a brand-new reader from `pip install` to a working,
|
|
4
|
+
authorized, audited tool invocation in roughly five minutes. Every code block
|
|
5
|
+
is copy-pasteable; the runnable companion is
|
|
6
|
+
[`examples/tutorial.py`](../examples/tutorial.py) (covered by CI).
|
|
7
|
+
|
|
8
|
+
> The PyPI package is **`weaver-kernel`** but the Python import is
|
|
9
|
+
> **`agent_kernel`**. We use both names in this document.
|
|
10
|
+
|
|
11
|
+
## What you'll learn
|
|
12
|
+
|
|
13
|
+
By the end of this page you will have seen, in this order:
|
|
14
|
+
|
|
15
|
+
1. How to register a **capability** and how its `safety_class`,
|
|
16
|
+
`sensitivity`, and `allowed_fields` shape authorization.
|
|
17
|
+
2. How a **principal** is created and why some attributes (like `tenant`)
|
|
18
|
+
are required for PII-tagged capabilities.
|
|
19
|
+
3. How to issue a signed **token** with `kernel.get_token(...)`.
|
|
20
|
+
4. How `kernel.invoke(...)` returns a bounded **Frame** in `summary`,
|
|
21
|
+
`table`, or `handle_only` modes — and why `email` never appears in any
|
|
22
|
+
of them.
|
|
23
|
+
5. How to retrieve filtered raw rows by expanding a **Handle**.
|
|
24
|
+
6. What a **policy denial** looks like and how to branch on its stable
|
|
25
|
+
`reason_code`.
|
|
26
|
+
7. How `kernel.explain(action_id)` returns an audit **ActionTrace**.
|
|
27
|
+
8. How to swap the in-process driver for a real **MCP** server.
|
|
28
|
+
|
|
29
|
+
## 0. Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install weaver-kernel
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For the MCP section near the end, also install the optional extra:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install "weaver-kernel[mcp]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Set a stable HMAC secret for the process. In production this should come
|
|
42
|
+
from a real secret store; the example uses a fixed value so the output is
|
|
43
|
+
reproducible:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import os
|
|
47
|
+
os.environ["AGENT_KERNEL_SECRET"] = "tutorial-secret-do-not-use-in-prod"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 1. Register a capability
|
|
51
|
+
|
|
52
|
+
A capability is the unit of authorization. The `safety_class` controls
|
|
53
|
+
which roles may call it. The `sensitivity` tag tells the policy and
|
|
54
|
+
firewall how to treat the data. `allowed_fields` is the projection the
|
|
55
|
+
firewall applies before any row reaches the LLM.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from agent_kernel import (
|
|
59
|
+
Capability,
|
|
60
|
+
CapabilityRegistry,
|
|
61
|
+
ImplementationRef,
|
|
62
|
+
SafetyClass,
|
|
63
|
+
SensitivityTag,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
registry = CapabilityRegistry()
|
|
67
|
+
registry.register(
|
|
68
|
+
Capability(
|
|
69
|
+
capability_id="billing.invoices.list",
|
|
70
|
+
name="List Invoices",
|
|
71
|
+
description="List recent invoices",
|
|
72
|
+
safety_class=SafetyClass.READ,
|
|
73
|
+
sensitivity=SensitivityTag.PII,
|
|
74
|
+
allowed_fields=["id", "customer_name", "amount", "status"],
|
|
75
|
+
tags=["billing", "invoices", "list"],
|
|
76
|
+
impl=ImplementationRef(driver_id="memory", operation="list_invoices"),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> `email`, `phone`, and other non-listed columns will never reach the LLM
|
|
82
|
+
> even if the driver returns them.
|
|
83
|
+
|
|
84
|
+
## 2. Wire a driver and the kernel
|
|
85
|
+
|
|
86
|
+
`InMemoryDriver` keeps the tutorial offline. The same pattern works with
|
|
87
|
+
`HTTPDriver` or `MCPDriver` — see step 8.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from agent_kernel import HMACTokenProvider, InMemoryDriver, Kernel, StaticRouter
|
|
91
|
+
from agent_kernel.drivers.base import ExecutionContext
|
|
92
|
+
|
|
93
|
+
INVOICES = [
|
|
94
|
+
{"id": "INV-001", "customer_name": "Alice", "email": "alice@example.com", "amount": 120.0, "status": "paid"},
|
|
95
|
+
{"id": "INV-002", "customer_name": "Bob", "email": "bob@example.com", "amount": 540.0, "status": "unpaid"},
|
|
96
|
+
{"id": "INV-003", "customer_name": "Carol", "email": "carol@example.com", "amount": 75.0, "status": "paid"},
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
driver = InMemoryDriver()
|
|
100
|
+
driver.register_handler("list_invoices", lambda ctx: list(INVOICES))
|
|
101
|
+
|
|
102
|
+
kernel = Kernel(
|
|
103
|
+
registry=registry,
|
|
104
|
+
token_provider=HMACTokenProvider(secret="tutorial-secret-do-not-use-in-prod"),
|
|
105
|
+
router=StaticRouter(routes={"billing.invoices.list": ["memory"]}),
|
|
106
|
+
)
|
|
107
|
+
kernel.register_driver(driver)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 3. Create a principal
|
|
111
|
+
|
|
112
|
+
The `DefaultPolicyEngine` requires a `tenant` attribute on the principal
|
|
113
|
+
for any PII-tagged capability. Without it, the grant is denied with
|
|
114
|
+
`reason_code="missing_tenant_attribute"`.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from agent_kernel import Principal
|
|
118
|
+
|
|
119
|
+
alice = Principal(principal_id="alice", roles=["reader"], attributes={"tenant": "acme"})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 4. Grant a token
|
|
123
|
+
|
|
124
|
+
`get_token` runs the policy engine and returns a signed
|
|
125
|
+
`CapabilityToken`. No token, no invocation.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from agent_kernel.models import CapabilityRequest
|
|
129
|
+
|
|
130
|
+
request = CapabilityRequest(capability_id="billing.invoices.list", goal="list recent invoices")
|
|
131
|
+
token = kernel.get_token(request, alice, justification="")
|
|
132
|
+
print(token.token_id, token.expires_at)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 5. Invoke and observe the Frame
|
|
136
|
+
|
|
137
|
+
The default `response_mode` is `"summary"`. The Frame holds compact
|
|
138
|
+
facts about the data plus a Handle the LLM can expand later.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import asyncio
|
|
142
|
+
|
|
143
|
+
frame = asyncio.run(kernel.invoke(token, principal=alice, args={"operation": "list_invoices"}))
|
|
144
|
+
for fact in frame.facts:
|
|
145
|
+
print("•", fact)
|
|
146
|
+
print("handle:", frame.handle and frame.handle.handle_id)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Try `response_mode="table"` to get a row preview that respects
|
|
150
|
+
`allowed_fields`. Try `response_mode="handle_only"` to skip the preview
|
|
151
|
+
entirely — the LLM gets only a reference. In every mode, **`email` is
|
|
152
|
+
absent** from the Frame, because it is not in `allowed_fields`.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
table_frame = asyncio.run(
|
|
156
|
+
kernel.invoke(
|
|
157
|
+
kernel.get_token(request, alice, justification=""),
|
|
158
|
+
principal=alice,
|
|
159
|
+
args={"operation": "list_invoices"},
|
|
160
|
+
response_mode="table",
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
assert all("email" not in row for row in table_frame.table_preview)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 6. Expand a Handle
|
|
167
|
+
|
|
168
|
+
Handles let the LLM stay inside its context budget while still pulling
|
|
169
|
+
specific rows or fields on demand. The expand query supports `offset`,
|
|
170
|
+
`limit`, `fields`, and an equality `filter`.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
handle_frame = asyncio.run(
|
|
174
|
+
kernel.invoke(
|
|
175
|
+
kernel.get_token(request, alice, justification=""),
|
|
176
|
+
principal=alice,
|
|
177
|
+
args={"operation": "list_invoices"},
|
|
178
|
+
response_mode="handle_only",
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
expanded = kernel.expand(
|
|
182
|
+
handle_frame.handle,
|
|
183
|
+
query={"offset": 0, "limit": 2, "fields": ["id", "amount"]},
|
|
184
|
+
principal=alice,
|
|
185
|
+
)
|
|
186
|
+
print(expanded.table_preview)
|
|
187
|
+
# [{'id': 'INV-001', 'amount': 120.0}, {'id': 'INV-002', 'amount': 540.0}]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> **Security boundary.** The `Firewall` enforces `allowed_fields` when it
|
|
191
|
+
> builds the `summary` and `table` previews, so disallowed columns never
|
|
192
|
+
> reach the LLM-safe Frame. `HandleStore.expand()` now also enforces the
|
|
193
|
+
> grant's `allowed_fields` projection: requesting a field outside the
|
|
194
|
+
> grant raises `HandleConstraintViolation`. The `principal` argument
|
|
195
|
+
> ensures handles are not bearer credentials — a handle bound to one
|
|
196
|
+
> principal cannot be expanded by another.
|
|
197
|
+
|
|
198
|
+
Asking for a disallowed field is rejected with a stable `reason_code`:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from agent_kernel.errors import HandleConstraintViolation
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
kernel.expand(handle_frame.handle, query={"fields": ["email"]}, principal=alice)
|
|
205
|
+
except HandleConstraintViolation as exc:
|
|
206
|
+
print(exc.reason_code) # 'handle_constraint_violation'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The same shape applies to `limit` over the grant's `max_rows`, a
|
|
210
|
+
`filter` that disagrees with the grant's `scope`, and a `principal`
|
|
211
|
+
mismatch (the last raises with
|
|
212
|
+
`reason_code="handle_principal_mismatch"`).
|
|
213
|
+
|
|
214
|
+
## 7. Watch policy enforcement
|
|
215
|
+
|
|
216
|
+
Add a WRITE capability and try to call it as the reader principal. The
|
|
217
|
+
denial carries both a human-readable `reason` and a stable
|
|
218
|
+
`reason_code` your code can branch on.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from agent_kernel.errors import PolicyDenied
|
|
222
|
+
|
|
223
|
+
registry.register(
|
|
224
|
+
Capability(
|
|
225
|
+
capability_id="billing.invoices.create",
|
|
226
|
+
name="Create Invoice",
|
|
227
|
+
description="Create a new invoice",
|
|
228
|
+
safety_class=SafetyClass.WRITE,
|
|
229
|
+
tags=["billing", "invoices", "create"],
|
|
230
|
+
impl=ImplementationRef(driver_id="memory", operation="create_invoice"),
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
kernel.get_token(
|
|
236
|
+
CapabilityRequest(capability_id="billing.invoices.create", goal="create an invoice"),
|
|
237
|
+
alice,
|
|
238
|
+
justification="reader trying a write — should fail",
|
|
239
|
+
)
|
|
240
|
+
except PolicyDenied as exc:
|
|
241
|
+
print(exc.reason_code) # 'missing_role'
|
|
242
|
+
print(str(exc)) # "WRITE capabilities require the 'writer' or 'admin' role..."
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Stable reason codes come from `agent_kernel.policy_reasons.DenialReason`.
|
|
246
|
+
Tests should assert on the code, not on the human-readable string.
|
|
247
|
+
|
|
248
|
+
## 8. Audit with `explain()`
|
|
249
|
+
|
|
250
|
+
Every successful invocation creates an `ActionTrace` keyed by
|
|
251
|
+
`frame.action_id`. The trace records who, what, when, and which driver
|
|
252
|
+
served the request — the auditable half of weaver-spec invariant I-02.
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
trace = kernel.explain(frame.action_id)
|
|
256
|
+
print(trace.action_id, trace.capability_id, trace.principal_id, trace.driver_id)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 9. Swap the driver for an MCP server
|
|
260
|
+
|
|
261
|
+
The kernel doesn't care whether the driver lives in-process, behind
|
|
262
|
+
HTTP, or behind an MCP server — capabilities, policy, tokens, and
|
|
263
|
+
firewall behave identically. To talk to a real MCP server, replace
|
|
264
|
+
`InMemoryDriver` with `MCPDriver` (full transport details, including
|
|
265
|
+
Streamable HTTP, live in [`docs/integrations.md`](integrations.md)):
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from agent_kernel.drivers.mcp import MCPDriver
|
|
269
|
+
|
|
270
|
+
driver = MCPDriver.from_stdio(
|
|
271
|
+
command="python",
|
|
272
|
+
args=["-m", "my_mcp_server"],
|
|
273
|
+
server_name="local-tools",
|
|
274
|
+
)
|
|
275
|
+
kernel.register_driver(driver)
|
|
276
|
+
|
|
277
|
+
# Discover the MCP server's tools and register each as an agent-kernel
|
|
278
|
+
# capability under a namespace. Set safety_class/sensitivity/allowed_fields
|
|
279
|
+
# on the resulting Capability objects to apply policy and the firewall.
|
|
280
|
+
capabilities = asyncio.run(driver.discover(namespace="billing"))
|
|
281
|
+
registry.register_many(capabilities)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
That's the whole tutorial. From here:
|
|
285
|
+
|
|
286
|
+
- [`docs/security.md`](security.md) — threat model, what HMAC tokens do
|
|
287
|
+
and do not protect against.
|
|
288
|
+
- [`docs/context_firewall.md`](context_firewall.md) — redaction,
|
|
289
|
+
summarization, and budget details.
|
|
290
|
+
- [`docs/capabilities.md`](capabilities.md) — designing capabilities
|
|
291
|
+
for large tool ecosystems.
|
|
292
|
+
- [`docs/integrations.md`](integrations.md) — full MCP and HTTP driver
|
|
293
|
+
integration patterns.
|
|
@@ -111,6 +111,7 @@ async def main() -> None:
|
|
|
111
111
|
expanded = kernel.expand(
|
|
112
112
|
frame.handle,
|
|
113
113
|
query={"offset": 0, "limit": 3, "fields": ["id", "amount", "status"]},
|
|
114
|
+
principal=analyst,
|
|
114
115
|
)
|
|
115
116
|
for row in expanded.table_preview:
|
|
116
117
|
print(f" {row}")
|
|
@@ -121,6 +122,7 @@ async def main() -> None:
|
|
|
121
122
|
overdue = kernel.expand(
|
|
122
123
|
frame.handle,
|
|
123
124
|
query={"filter": {"status": "overdue"}, "limit": 3, "fields": ["id", "amount"]},
|
|
125
|
+
principal=analyst,
|
|
124
126
|
)
|
|
125
127
|
print(f" Overdue rows returned: {len(overdue.table_preview)}")
|
|
126
128
|
for row in overdue.table_preview:
|