weaver-kernel 0.8.0__tar.gz → 0.9.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.8.0 → weaver_kernel-0.9.0}/.github/workflows/ci.yml +1 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/AGENTS.md +1 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/CHANGELOG.md +129 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/CONTRIBUTING.md +6 -5
- weaver_kernel-0.9.0/Makefile +25 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/PKG-INFO +14 -5
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/README.md +13 -4
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/agent-context/architecture.md +1 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/agent-context/invariants.md +2 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/agent-context/review-checklist.md +1 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/agent-context/workflows.md +12 -6
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/architecture.md +1 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/capabilities.md +55 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/context_firewall.md +64 -3
- weaver_kernel-0.9.0/docs/federation.md +218 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/integrations.md +35 -0
- weaver_kernel-0.9.0/examples/readme_quickstart.py +79 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/pyproject.toml +5 -1
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/__init__.py +69 -5
- weaver_kernel-0.9.0/src/agent_kernel/drivers/base.py +92 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/errors.py +46 -0
- weaver_kernel-0.9.0/src/agent_kernel/federation.py +272 -0
- weaver_kernel-0.9.0/src/agent_kernel/federation_discovery.py +306 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/transform.py +63 -1
- weaver_kernel-0.9.0/src/agent_kernel/kernel/__init__.py +446 -0
- weaver_kernel-0.9.0/src/agent_kernel/kernel/_dry_run.py +99 -0
- weaver_kernel-0.9.0/src/agent_kernel/kernel/_federation.py +132 -0
- weaver_kernel-0.9.0/src/agent_kernel/kernel/_invoke.py +329 -0
- weaver_kernel-0.9.0/src/agent_kernel/kernel/_stream.py +215 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/models.py +213 -0
- weaver_kernel-0.9.0/src/agent_kernel/otel.py +255 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/policy.py +5 -62
- weaver_kernel-0.9.0/src/agent_kernel/policy_dsl.py +297 -0
- weaver_kernel-0.9.0/src/agent_kernel/policy_dsl_explain.py +214 -0
- weaver_kernel-0.9.0/src/agent_kernel/policy_dsl_parser.py +277 -0
- weaver_kernel-0.9.0/src/agent_kernel/rate_limit.py +78 -0
- weaver_kernel-0.9.0/src/agent_kernel/registry.py +296 -0
- weaver_kernel-0.9.0/src/agent_kernel/search_index.py +160 -0
- weaver_kernel-0.9.0/tests/test_federation.py +501 -0
- weaver_kernel-0.9.0/tests/test_federation_discovery.py +390 -0
- weaver_kernel-0.9.0/tests/test_firewall_stream.py +198 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_kernel.py +110 -0
- weaver_kernel-0.9.0/tests/test_otel.py +192 -0
- weaver_kernel-0.9.0/tests/test_public_api.py +40 -0
- weaver_kernel-0.9.0/tests/test_readme_quickstart.py +53 -0
- weaver_kernel-0.9.0/tests/test_registry.py +415 -0
- weaver_kernel-0.8.0/Makefile +0 -21
- weaver_kernel-0.8.0/src/agent_kernel/drivers/base.py +0 -42
- weaver_kernel-0.8.0/src/agent_kernel/kernel.py +0 -632
- weaver_kernel-0.8.0/src/agent_kernel/policy_dsl.py +0 -661
- weaver_kernel-0.8.0/src/agent_kernel/registry.py +0 -124
- weaver_kernel-0.8.0/tests/test_registry.py +0 -112
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/.claude/CLAUDE.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/.github/copilot-instructions.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/.github/workflows/publish.yml +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/.gitignore +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/LICENSE +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/RELEASE.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/agent-context/lessons-learned.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/security.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/docs/tutorial.md +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/basic_cli.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/billing_demo.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/http_driver_demo.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/policies/default.toml +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/policies/default.yaml +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/examples/tutorial.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/adapters/__init__.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/adapters/_base.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/adapters/anthropic.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/adapters/openai.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/drivers/__init__.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/drivers/http.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/drivers/mcp.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/drivers/mcp_support.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/drivers/memory.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/enums.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/__init__.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/budget_manager.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/budgets.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/redaction.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/summarize.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/firewall/token_counting.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/handles.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/policy_reasons.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/py.typed +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/router.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/tokens.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/src/agent_kernel/trace.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/conftest.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_adapters.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_drivers.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_firewall.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_firewall_boundary.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_handles.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_logging.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_mcp_driver.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_models.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_policy.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_redaction.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_router.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_tokens.py +0 -0
- {weaver_kernel-0.8.0 → weaver_kernel-0.9.0}/tests/test_trace.py +0 -0
|
@@ -46,7 +46,7 @@ Use these terms consistently. Never substitute synonyms:
|
|
|
46
46
|
|
|
47
47
|
## Quality bar
|
|
48
48
|
|
|
49
|
-
- `make ci` must pass before every push. It runs: `fmt → lint → type → test → example`.
|
|
49
|
+
- `make ci` must pass before every push. It runs: `fmt-check → lint → type → test → example`. Use `make fmt` to auto-fix formatting before re-running `make ci`.
|
|
50
50
|
- All public interfaces need type hints and docstrings.
|
|
51
51
|
- Never raise bare `ValueError` or `KeyError` to callers. Use custom exceptions from `errors.py`. Catching stdlib exceptions internally to remap them is fine.
|
|
52
52
|
- Error messages are part of the contract — tests must assert both exception type and message.
|
|
@@ -7,6 +7,135 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] - 2026-05-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Capability namespaces and hierarchical discovery in `CapabilityRegistry`:
|
|
14
|
+
dot-notation `capability_id`s now expose `list_namespaces()` /
|
|
15
|
+
`list_namespace(prefix)` operations; `register_namespace(prefix, loader=...)`
|
|
16
|
+
enables deferred registration for large tool ecosystems (the loader runs
|
|
17
|
+
at most once on first access). `search()` gained an `offset` kwarg for
|
|
18
|
+
pagination, strips a small stop-word set, and now scores with a
|
|
19
|
+
BM25-flavoured ranker that weights `capability_id`/`tags` matches above
|
|
20
|
+
`description`. Flat (un-namespaced) capability IDs continue to work
|
|
21
|
+
unchanged. (#45)
|
|
22
|
+
- Capability marketplace, part 1 — manifest format & local registry: new
|
|
23
|
+
`CapabilityDescriptor` and `CapabilityManifest` dataclasses (both
|
|
24
|
+
JSON-round-trippable via `to_dict`/`from_dict`), new
|
|
25
|
+
`agent_kernel.federation` module with `build_manifest()`,
|
|
26
|
+
`import_manifest()`, and `merge_sensitivity()`, and new `Kernel.advertise()`
|
|
27
|
+
/ `Kernel.import_remote()` methods. `Kernel` gained a `kernel_id`
|
|
28
|
+
argument used as the manifest publisher identity. Three trust policies
|
|
29
|
+
are honoured at import time (`most_restrictive` (default), `local_only`,
|
|
30
|
+
`remote_deferred`); imported capabilities are routed through a
|
|
31
|
+
caller-supplied driver and flow through the full local policy → token →
|
|
32
|
+
firewall pipeline. HMAC tokens remain kernel-scoped — a token issued by
|
|
33
|
+
one kernel cannot be verified by another with a different secret. New
|
|
34
|
+
errors `NamespaceNotFound`, `FederationError`, `ManifestError`,
|
|
35
|
+
`TrustPolicyError`. (#52)
|
|
36
|
+
- New docs: [`docs/federation.md`](docs/federation.md) for the marketplace
|
|
37
|
+
protocol and a namespace section in
|
|
38
|
+
[`docs/capabilities.md`](docs/capabilities.md).
|
|
39
|
+
- Capability marketplace, part 2 — federated discovery: new
|
|
40
|
+
`agent_kernel.federation_discovery` module with `discover_peers()`,
|
|
41
|
+
`sign_manifest()`, `verify_manifest()`, `serve_manifest_payload()`, and
|
|
42
|
+
`DiscoveryRateLimiter`. `Kernel.discover_peers()` fetches one or more
|
|
43
|
+
manifests over HTTP from peer URLs or a registry URL. Signed envelopes
|
|
44
|
+
(HMAC-SHA256) detect tampering and let importers refuse unsigned
|
|
45
|
+
manifests when a verification secret is in play (and vice versa). New
|
|
46
|
+
errors `ManifestSignatureError` and `DiscoveryError`. (#51, closes #49)
|
|
47
|
+
- OpenTelemetry integration: new `agent_kernel.otel` module with
|
|
48
|
+
`instrument_kernel(kernel)` that wraps `Kernel.invoke` and
|
|
49
|
+
`Kernel.grant_capability` with OTel spans + metrics (invocation count,
|
|
50
|
+
latency histogram, denial counter). No-op when the optional `[otel]`
|
|
51
|
+
extra is not installed (`OTEL_AVAILABLE` reports the runtime status).
|
|
52
|
+
Idempotent — repeat calls on the same kernel are no-ops. (#38)
|
|
53
|
+
- Streaming firewall: new `Firewall.apply_stream()` async-iterator method
|
|
54
|
+
that processes driver chunks one-at-a-time, applying PII redaction
|
|
55
|
+
per-chunk. New `StreamingDriver` Protocol in `drivers/base.py` extends
|
|
56
|
+
`Driver` with an optional `execute_stream()`. New `Kernel.invoke_stream()`
|
|
57
|
+
yields `Frame` chunks; the last chunk carries `is_final=True`. Drivers
|
|
58
|
+
without `execute_stream` automatically fall back to a single-chunk stream
|
|
59
|
+
via `execute()`. `Frame` gained an `is_final: bool` field. (#47)
|
|
60
|
+
- `examples/readme_quickstart.py` — a runnable mirror of the README quickstart,
|
|
61
|
+
wired into `make example` and the CI "Examples" step. Together with
|
|
62
|
+
`tests/test_readme_quickstart.py` (which extracts and runs the inline README
|
|
63
|
+
snippet itself), CI fails if the documented quickstart stops producing the
|
|
64
|
+
expected output. (#83)
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
- Tech debt: `policy_dsl.py` decomposed (was 661 lines). Parsing and
|
|
68
|
+
schema dataclasses now live in `policy_dsl_parser.py`
|
|
69
|
+
(`PolicyMatch`, `PolicyRule`, `parse_engine_data`, `parse_rule`,
|
|
70
|
+
YAML/TOML loaders), and the denial-explanation traversal in
|
|
71
|
+
`policy_dsl_explain.py`. The public import surface
|
|
72
|
+
(`DeclarativePolicyEngine`, `PolicyMatch`, `PolicyRule`) is unchanged.
|
|
73
|
+
`RateLimiter` and rate-limit constants extracted from `policy.py` into
|
|
74
|
+
a new `rate_limit.py` module; `policy.py` continues to re-export them
|
|
75
|
+
under their original names. (#68)
|
|
76
|
+
- Tech debt: `kernel.py` split into the `agent_kernel.kernel` sub-package
|
|
77
|
+
to honour AGENTS.md's ≤ 300-line module bar. The `Kernel` class lives
|
|
78
|
+
in `kernel/__init__.py`; heavy methods (invoke pipeline, dry-run,
|
|
79
|
+
federation, streaming) delegate to sibling modules. Existing
|
|
80
|
+
`from agent_kernel import Kernel` / `from agent_kernel.kernel import Kernel`
|
|
81
|
+
imports are unchanged. (#68)
|
|
82
|
+
|
|
83
|
+
### Documentation
|
|
84
|
+
- Fixed handle-expansion examples that omitted the now-required `principal`
|
|
85
|
+
argument and therefore raised `HandleConstraintViolation` when copy-pasted:
|
|
86
|
+
the README quickstart (#83) and `docs/context_firewall.md` +
|
|
87
|
+
`docs/architecture.md` (#84) now pass `principal=` and link to
|
|
88
|
+
`docs/security.md#handle-expansion-boundary`. The README quickstart also
|
|
89
|
+
drops two unused imports (`ExecutionContext`, `HMACTokenProvider`).
|
|
90
|
+
- `docs/capabilities.md` "Sensitivity tags" now lists `SensitivityTag.MEMORY`
|
|
91
|
+
and links to `docs/security.md#memory-actions`. (#89)
|
|
92
|
+
- Corrected the base-dependency list in `docs/agent-context/invariants.md` and
|
|
93
|
+
`docs/agent-context/architecture.md` from "`httpx` only" to
|
|
94
|
+
"`httpx` + `pydantic`", pointing to `AGENTS.md` as the canonical dependency
|
|
95
|
+
policy. (#90)
|
|
96
|
+
- The `agent_kernel` module docstring's `Errors::` block now lists every
|
|
97
|
+
exported error class — added `TokenRevoked`, `AdapterParseError`,
|
|
98
|
+
`CapabilityAlreadyRegistered`, `HandleConstraintViolation`,
|
|
99
|
+
`ManifestSignatureError`, and `DiscoveryError`. (#91)
|
|
100
|
+
|
|
101
|
+
### Tests
|
|
102
|
+
- Added explicit dry-run regression tests for `HTTPDriver` and `MCPDriver`,
|
|
103
|
+
pinning the kernel's driver-agnostic dry-run short-circuit. (#68)
|
|
104
|
+
- `tests/test_public_api.py` — asserts every error class exported via `__all__`
|
|
105
|
+
appears in the `agent_kernel` module docstring, preventing public-API
|
|
106
|
+
docstring drift. (#91)
|
|
107
|
+
- `tests/test_readme_quickstart.py` — extracts the README quickstart code block
|
|
108
|
+
and executes it, asserting the documented output so the inline snippet cannot
|
|
109
|
+
silently drift from the working API. (#83)
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
- `merge_sensitivity()` (and `most_restrictive` imports) now ranks the `MEMORY`
|
|
113
|
+
sensitivity tag instead of silently treating it as `NONE`. (#52)
|
|
114
|
+
- `import_manifest()` is now atomic: a manifest whose capability ID is already
|
|
115
|
+
registered locally — or that lists the same ID more than once — raises
|
|
116
|
+
`ManifestError` and registers nothing, instead of leaving a partial,
|
|
117
|
+
unrouted import behind. (#52)
|
|
118
|
+
- `CapabilityRegistry.search()` now triggers every pending deferred namespace
|
|
119
|
+
loader before ranking, so matches are no longer missed when the query shares
|
|
120
|
+
no token with a namespace prefix. (#45)
|
|
121
|
+
- A deferred namespace loader that fails (by raising or returning an
|
|
122
|
+
out-of-namespace capability) no longer permanently disables the namespace —
|
|
123
|
+
the load is retried on a later access. (#45)
|
|
124
|
+
- `Makefile` now invokes every tool via `python -m <tool>` (matching the
|
|
125
|
+
existing `test` target) so `make ci` uses the active interpreter's
|
|
126
|
+
site-packages instead of whatever `ruff` / `mypy` resolves first on
|
|
127
|
+
`PATH`. Fixes spurious `import-not-found` / `no-redef` errors on machines
|
|
128
|
+
where `mypy` or `ruff` is provided by an isolated installer such as
|
|
129
|
+
`uv tool` or `pipx`. (#86)
|
|
130
|
+
- `make ci` now runs the non-mutating `fmt-check` target (`ruff format
|
|
131
|
+
--check`) instead of the file-mutating `fmt` target. Local `make ci`
|
|
132
|
+
now fails on unformatted code exactly like `.github/workflows/ci.yml`
|
|
133
|
+
does, instead of silently auto-fixing the working tree and letting an
|
|
134
|
+
unfixed commit be pushed. `make fmt` remains available for manual
|
|
135
|
+
auto-formatting. `AGENTS.md`, `docs/agent-context/workflows.md`,
|
|
136
|
+
`docs/agent-context/review-checklist.md`, `CONTRIBUTING.md`, and the
|
|
137
|
+
`README.md` development section are updated to describe the new chain. (#88)
|
|
138
|
+
|
|
10
139
|
## [0.8.0] - 2026-05-22
|
|
11
140
|
|
|
12
141
|
### Added
|
|
@@ -15,11 +15,12 @@ pip install -e ".[dev]"
|
|
|
15
15
|
## Running checks
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
make fmt
|
|
19
|
-
make
|
|
20
|
-
make
|
|
21
|
-
make
|
|
22
|
-
make
|
|
18
|
+
make fmt # auto-format with ruff (not run by `make ci`)
|
|
19
|
+
make fmt-check # verify formatting with `ruff format --check` (no mutation)
|
|
20
|
+
make lint # lint with ruff
|
|
21
|
+
make type # type-check with mypy
|
|
22
|
+
make test # run pytest with coverage
|
|
23
|
+
make ci # fmt-check + lint + type + test + example
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Pull request guidelines
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.PHONY: fmt fmt-check lint type test example ci
|
|
2
|
+
|
|
3
|
+
fmt:
|
|
4
|
+
python -m ruff format src/ tests/ examples/
|
|
5
|
+
|
|
6
|
+
fmt-check:
|
|
7
|
+
python -m ruff format --check src/ tests/ examples/
|
|
8
|
+
|
|
9
|
+
lint:
|
|
10
|
+
python -m ruff check src/ tests/ examples/
|
|
11
|
+
|
|
12
|
+
type:
|
|
13
|
+
python -m mypy src/
|
|
14
|
+
|
|
15
|
+
test:
|
|
16
|
+
python -m pytest -q --cov=agent_kernel
|
|
17
|
+
|
|
18
|
+
example:
|
|
19
|
+
python examples/basic_cli.py
|
|
20
|
+
python examples/billing_demo.py
|
|
21
|
+
python examples/http_driver_demo.py
|
|
22
|
+
python examples/tutorial.py
|
|
23
|
+
python examples/readme_quickstart.py
|
|
24
|
+
|
|
25
|
+
ci: fmt-check lint type test example
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weaver-kernel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -296,10 +296,9 @@ import asyncio, os
|
|
|
296
296
|
os.environ["AGENT_KERNEL_SECRET"] = "my-secret"
|
|
297
297
|
|
|
298
298
|
from agent_kernel import (
|
|
299
|
-
Capability, CapabilityRegistry,
|
|
299
|
+
Capability, CapabilityRegistry,
|
|
300
300
|
InMemoryDriver, Kernel, Principal, SafetyClass, StaticRouter,
|
|
301
301
|
)
|
|
302
|
-
from agent_kernel.drivers.base import ExecutionContext
|
|
303
302
|
from agent_kernel.models import CapabilityRequest
|
|
304
303
|
|
|
305
304
|
# 1. Register a capability
|
|
@@ -332,7 +331,11 @@ async def main():
|
|
|
332
331
|
print(frame.facts) # ['Total rows: 1', 'Top keys: id, title', ...]
|
|
333
332
|
print(frame.handle) # Handle(handle_id='...', ...)
|
|
334
333
|
|
|
335
|
-
|
|
334
|
+
# `principal` is required: the handle is bound to the granting principal,
|
|
335
|
+
# so an omitted principal raises HandleConstraintViolation.
|
|
336
|
+
expanded = kernel.expand(
|
|
337
|
+
frame.handle, query={"limit": 1, "fields": ["title"]}, principal=principal
|
|
338
|
+
)
|
|
336
339
|
print(expanded.table_preview) # [{'title': 'Buy milk'}]
|
|
337
340
|
|
|
338
341
|
trace = kernel.explain(frame.action_id)
|
|
@@ -341,6 +344,12 @@ async def main():
|
|
|
341
344
|
asyncio.run(main())
|
|
342
345
|
```
|
|
343
346
|
|
|
347
|
+
> This snippet is extracted and executed by CI (`tests/test_readme_quickstart.py`), and
|
|
348
|
+
> a standalone runnable mirror lives at
|
|
349
|
+
> [`examples/readme_quickstart.py`](examples/readme_quickstart.py) (run by `make example`).
|
|
350
|
+
> CI fails if either stops producing the documented output, so this quickstart cannot
|
|
351
|
+
> silently drift from the working API.
|
|
352
|
+
|
|
344
353
|
## Where it fits
|
|
345
354
|
|
|
346
355
|
```
|
|
@@ -433,7 +442,7 @@ See [docs/agent-context/invariants.md](docs/agent-context/invariants.md) for the
|
|
|
433
442
|
git clone https://github.com/dgenio/agent-kernel
|
|
434
443
|
cd agent-kernel
|
|
435
444
|
pip install -e ".[dev]"
|
|
436
|
-
make ci # fmt + lint + type + test + examples
|
|
445
|
+
make ci # fmt-check + lint + type + test + examples
|
|
437
446
|
```
|
|
438
447
|
|
|
439
448
|
## License
|
|
@@ -50,10 +50,9 @@ import asyncio, os
|
|
|
50
50
|
os.environ["AGENT_KERNEL_SECRET"] = "my-secret"
|
|
51
51
|
|
|
52
52
|
from agent_kernel import (
|
|
53
|
-
Capability, CapabilityRegistry,
|
|
53
|
+
Capability, CapabilityRegistry,
|
|
54
54
|
InMemoryDriver, Kernel, Principal, SafetyClass, StaticRouter,
|
|
55
55
|
)
|
|
56
|
-
from agent_kernel.drivers.base import ExecutionContext
|
|
57
56
|
from agent_kernel.models import CapabilityRequest
|
|
58
57
|
|
|
59
58
|
# 1. Register a capability
|
|
@@ -86,7 +85,11 @@ async def main():
|
|
|
86
85
|
print(frame.facts) # ['Total rows: 1', 'Top keys: id, title', ...]
|
|
87
86
|
print(frame.handle) # Handle(handle_id='...', ...)
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
# `principal` is required: the handle is bound to the granting principal,
|
|
89
|
+
# so an omitted principal raises HandleConstraintViolation.
|
|
90
|
+
expanded = kernel.expand(
|
|
91
|
+
frame.handle, query={"limit": 1, "fields": ["title"]}, principal=principal
|
|
92
|
+
)
|
|
90
93
|
print(expanded.table_preview) # [{'title': 'Buy milk'}]
|
|
91
94
|
|
|
92
95
|
trace = kernel.explain(frame.action_id)
|
|
@@ -95,6 +98,12 @@ async def main():
|
|
|
95
98
|
asyncio.run(main())
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
> This snippet is extracted and executed by CI (`tests/test_readme_quickstart.py`), and
|
|
102
|
+
> a standalone runnable mirror lives at
|
|
103
|
+
> [`examples/readme_quickstart.py`](examples/readme_quickstart.py) (run by `make example`).
|
|
104
|
+
> CI fails if either stops producing the documented output, so this quickstart cannot
|
|
105
|
+
> silently drift from the working API.
|
|
106
|
+
|
|
98
107
|
## Where it fits
|
|
99
108
|
|
|
100
109
|
```
|
|
@@ -187,7 +196,7 @@ See [docs/agent-context/invariants.md](docs/agent-context/invariants.md) for the
|
|
|
187
196
|
git clone https://github.com/dgenio/agent-kernel
|
|
188
197
|
cd agent-kernel
|
|
189
198
|
pip install -e ".[dev]"
|
|
190
|
-
make ci # fmt + lint + type + test + examples
|
|
199
|
+
make ci # fmt-check + lint + type + test + examples
|
|
191
200
|
```
|
|
192
201
|
|
|
193
202
|
## License
|
|
@@ -39,7 +39,7 @@ The architecture optimizes for:
|
|
|
39
39
|
| Tokens signed, not encrypted | Simplicity; avoids key management complexity | Payloads are readable — never store secrets in them |
|
|
40
40
|
| Keyword-based capability search | Deterministic; no external service dependency | Less flexible than semantic search; relies on good tagging |
|
|
41
41
|
| Firewall is mandatory | Prevents accidental context blowup and data leakage | All output is bounded; debugging requires admin `raw` mode |
|
|
42
|
-
|
|
|
42
|
+
| Minimal deps (`httpx` + `pydantic`) | Small attack surface for a security kernel | Adding a dependency requires justification (see `AGENTS.md`) |
|
|
43
43
|
|
|
44
44
|
## Things not to simplify
|
|
45
45
|
|
|
@@ -40,7 +40,8 @@ These constraints are non-negotiable. Violating any one silently degrades securi
|
|
|
40
40
|
derived keys must stay out of logs, error messages, and traces.
|
|
41
41
|
|
|
42
42
|
6. **Never add dependencies without justification.** The dependency list is intentionally
|
|
43
|
-
minimal (`httpx`
|
|
43
|
+
minimal (`httpx` + `pydantic` — see [`AGENTS.md`](../../AGENTS.md) for the canonical
|
|
44
|
+
dependency policy). Every new dependency expands the attack surface.
|
|
44
45
|
|
|
45
46
|
7. **Never register duplicate capability IDs.** The registry raises
|
|
46
47
|
`CapabilityAlreadyRegistered`. Duplicates cause ambiguous routing and policy
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
Run before every PR submission.
|
|
11
11
|
|
|
12
12
|
### CI gate
|
|
13
|
-
- [ ] `make ci` passes (fmt → lint → type → test → example).
|
|
13
|
+
- [ ] `make ci` passes (fmt-check → lint → type → test → example).
|
|
14
14
|
|
|
15
15
|
### Correctness
|
|
16
16
|
- [ ] Every changed docstring matches the actual implementation.
|
|
@@ -7,18 +7,24 @@
|
|
|
7
7
|
|
|
8
8
|
| Command | Purpose | When to run |
|
|
9
9
|
|---------|---------|-------------|
|
|
10
|
-
| `make ci` | Full pre-push gate: fmt → lint → type → test → example | Before every push |
|
|
11
|
-
| `make fmt` | Auto-format with ruff | During development |
|
|
10
|
+
| `make ci` | Full pre-push gate: fmt-check → lint → type → test → example | Before every push |
|
|
11
|
+
| `make fmt` | Auto-format with ruff (mutates files) | During development |
|
|
12
|
+
| `make fmt-check` | Verify formatting with `ruff format --check` (no mutation) | Used by `make ci`; matches what CI runs |
|
|
12
13
|
| `make lint` | Lint check with ruff | Isolated lint verification |
|
|
13
14
|
| `make type` | mypy type check | After changing type annotations |
|
|
14
15
|
| `make test` | pytest with coverage | After changing code |
|
|
15
16
|
| `make example` | Run all example scripts | After changing examples or core APIs |
|
|
16
17
|
|
|
17
18
|
`make ci` is the **single authoritative pre-push command**. It runs all five targets
|
|
18
|
-
in sequence
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
in sequence and mirrors the checks in the `test` job of `.github/workflows/ci.yml`: the
|
|
20
|
+
format step is the non-mutating `fmt-check` (equivalent to CI's `ruff format --check`),
|
|
21
|
+
and lint/type/test/example run the same tools CI does. (CI's separate `conformance_stub`
|
|
22
|
+
job is a no-op placeholder and is not part of the local gate.) The Makefile
|
|
23
|
+
additionally invokes every tool via `python -m <tool>` — a local hardening over CI
|
|
24
|
+
that uses the active interpreter's site-packages, preventing spurious failures when
|
|
25
|
+
`ruff` or `mypy` are provided by isolated installers such as `uv tool` or `pipx`. If
|
|
26
|
+
`make ci` passes locally, the same checks will pass in CI. Use `make fmt` (the
|
|
27
|
+
mutating target) when you want to auto-fix formatting before re-running `make ci`.
|
|
22
28
|
|
|
23
29
|
## PR conventions
|
|
24
30
|
|
|
@@ -31,7 +31,7 @@ The central orchestrator. Wires all components together and exposes:
|
|
|
31
31
|
- `request_capabilities(goal)` — discover relevant capabilities
|
|
32
32
|
- `grant_capability(request, principal, justification)` — policy check + token issuance
|
|
33
33
|
- `invoke(token, principal, args, response_mode, dry_run=False)` — execute + firewall + trace, or short-circuit before driver dispatch when `dry_run=True`
|
|
34
|
-
- `expand(handle, query)` — paginate/filter stored results
|
|
34
|
+
- `expand(handle, *, query, principal=None)` — paginate/filter stored results; `principal` is required for principal-bound handles (see [`docs/security.md`](security.md#handle-expansion-boundary))
|
|
35
35
|
- `explain(action_id)` — retrieve audit trace
|
|
36
36
|
- `explain_denial(request, principal, justification)` — return a structured `DenialExplanation` instead of raising `PolicyDenied`
|
|
37
37
|
|
|
@@ -3,9 +3,60 @@
|
|
|
3
3
|
## Naming conventions
|
|
4
4
|
|
|
5
5
|
- Use `domain.verb_noun` format: `billing.list_invoices`, `users.get_profile`.
|
|
6
|
+
- Prefer fully namespaced IDs (`billing.invoices.list`) over flat ones —
|
|
7
|
+
the registry will infer namespace operations from the dot-segments and
|
|
8
|
+
large ecosystems benefit from being able to list/search per namespace.
|
|
6
9
|
- Be specific: prefer `billing.cancel_invoice` over `billing.update`.
|
|
7
10
|
- Avoid generic names like `billing.execute` or `api.call`.
|
|
8
11
|
|
|
12
|
+
## Namespaces and discovery
|
|
13
|
+
|
|
14
|
+
`CapabilityRegistry` recognises dot-notation namespaces automatically. No
|
|
15
|
+
extra registration step is required — `register(Capability(capability_id=
|
|
16
|
+
"billing.invoices.list", ...))` is enough to populate the `billing` and
|
|
17
|
+
`billing.invoices` namespaces.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
registry.list_namespaces()
|
|
21
|
+
# ['billing', 'crm']
|
|
22
|
+
|
|
23
|
+
registry.list_namespace("billing")
|
|
24
|
+
# [Capability('billing.invoices.list'), Capability('billing.payments.refund'), …]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For large tool ecosystems where eagerly registering hundreds of
|
|
28
|
+
capabilities is wasteful, declare a deferred loader. The loader runs at
|
|
29
|
+
most once, the first time the namespace is searched, listed, or any
|
|
30
|
+
capability under it is fetched via `get()`:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
def load_billing() -> list[Capability]:
|
|
34
|
+
return [
|
|
35
|
+
Capability(capability_id="billing.invoices.list", …),
|
|
36
|
+
Capability(capability_id="billing.invoices.create", …),
|
|
37
|
+
Capability(capability_id="billing.payments.refund", …),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
registry.register_namespace(
|
|
41
|
+
"billing",
|
|
42
|
+
description="Billing and invoicing tools",
|
|
43
|
+
loader=load_billing,
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Search ranks matches with a BM25-flavoured scorer that weights
|
|
48
|
+
`capability_id` and `tags` higher than `description`, strips a small
|
|
49
|
+
stop-word set (`a`, `the`, `please`, …), and offers `offset` for
|
|
50
|
+
pagination:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
results = registry.search("list invoices", max_results=10, offset=0)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Search is deterministic — equal-scoring capabilities are returned in
|
|
57
|
+
`capability_id` order — and trips any deferred namespace loader whose
|
|
58
|
+
prefix shares a token with the query.
|
|
59
|
+
|
|
9
60
|
## Granularity
|
|
10
61
|
|
|
11
62
|
Each capability should map to a single, auditable action with clear side-effects.
|
|
@@ -32,6 +83,10 @@ Each capability should map to a single, auditable action with clear side-effects
|
|
|
32
83
|
Use `SensitivityTag.PII` when results may contain: name, email, phone, SSN, address.
|
|
33
84
|
Use `SensitivityTag.PCI` when results may contain: card numbers, CVV, bank details.
|
|
34
85
|
Use `SensitivityTag.SECRETS` when results may contain: API keys, passwords, tokens.
|
|
86
|
+
Use `SensitivityTag.MEMORY` when results are durable agent memory (project notes,
|
|
87
|
+
session handoff, learned context). Pair it with the `memory.read` / `memory.write` /
|
|
88
|
+
`memory.forget` capability IDs to activate the policy rules and audit-trace redaction
|
|
89
|
+
described in [`docs/security.md#memory-actions`](security.md#memory-actions).
|
|
35
90
|
|
|
36
91
|
Always pair sensitivity tags with `allowed_fields` to restrict which fields are returned
|
|
37
92
|
to non-privileged callers.
|
|
@@ -32,18 +32,23 @@ Budgets(
|
|
|
32
32
|
|
|
33
33
|
A `Handle` is an opaque reference to the full dataset stored server-side.
|
|
34
34
|
|
|
35
|
+
A handle is bound to the principal it was granted to, so `expand()` requires that
|
|
36
|
+
same `principal` — an omitted or mismatched principal raises
|
|
37
|
+
`HandleConstraintViolation` (handle IDs are not bearer credentials). See
|
|
38
|
+
[`docs/security.md#handle-expansion-boundary`](security.md#handle-expansion-boundary).
|
|
39
|
+
|
|
35
40
|
```python
|
|
36
41
|
# Stored automatically on every invoke()
|
|
37
42
|
handle = frame.handle
|
|
38
43
|
|
|
39
44
|
# Expand with pagination
|
|
40
|
-
expanded = kernel.expand(handle, query={"offset": 10, "limit": 5})
|
|
45
|
+
expanded = kernel.expand(handle, query={"offset": 10, "limit": 5}, principal=principal)
|
|
41
46
|
|
|
42
47
|
# Field selection
|
|
43
|
-
expanded = kernel.expand(handle, query={"fields": ["id", "name"]})
|
|
48
|
+
expanded = kernel.expand(handle, query={"fields": ["id", "name"]}, principal=principal)
|
|
44
49
|
|
|
45
50
|
# Basic filtering
|
|
46
|
-
expanded = kernel.expand(handle, query={"filter": {"status": "unpaid"}})
|
|
51
|
+
expanded = kernel.expand(handle, query={"filter": {"status": "unpaid"}}, principal=principal)
|
|
47
52
|
```
|
|
48
53
|
|
|
49
54
|
## Redaction
|
|
@@ -118,3 +123,59 @@ manager = BudgetManager(total_budget=128_000, token_counter=tiktoken_counter)
|
|
|
118
123
|
|
|
119
124
|
The default counter (`default_token_counter`) is a character-based
|
|
120
125
|
`len(json.dumps(value)) // 4` approximation with no extra dependencies.
|
|
126
|
+
|
|
127
|
+
## Streaming
|
|
128
|
+
|
|
129
|
+
For large results that arrive incrementally (e.g. SSE-style HTTP responses,
|
|
130
|
+
chunked database cursors, line-by-line tool output), `Firewall.apply_stream()`
|
|
131
|
+
lets you process chunks one at a time. PII redaction and per-chunk budget
|
|
132
|
+
caps apply on every yielded Frame — secrets cannot leak just because they
|
|
133
|
+
arrived in chunk N rather than the final aggregate.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from agent_kernel.drivers.base import ExecutionContext, StreamingDriver
|
|
137
|
+
|
|
138
|
+
class MyStreamingDriver:
|
|
139
|
+
driver_id = "stream"
|
|
140
|
+
|
|
141
|
+
async def execute(self, ctx: ExecutionContext):
|
|
142
|
+
# one-shot fallback, called when StreamingDriver isn't used.
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
async def execute_stream(self, ctx: ExecutionContext):
|
|
146
|
+
async for row in some_async_cursor(ctx):
|
|
147
|
+
yield {"row": row}
|
|
148
|
+
yield {"__is_final__": True} # explicit sentinel (optional)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# isinstance(driver, StreamingDriver) is runtime-checkable.
|
|
152
|
+
assert isinstance(MyStreamingDriver(), StreamingDriver)
|
|
153
|
+
|
|
154
|
+
async for frame in kernel.invoke_stream(token, principal=p, args={}):
|
|
155
|
+
handle_chunk(frame)
|
|
156
|
+
if frame.is_final:
|
|
157
|
+
break
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When the resolved driver does **not** implement `StreamingDriver`,
|
|
161
|
+
`Kernel.invoke_stream` falls back to a single `Driver.execute()` call and
|
|
162
|
+
yields exactly one `Frame` with `is_final=True`. Each invocation produces
|
|
163
|
+
one `ActionTrace` covering the whole stream.
|
|
164
|
+
|
|
165
|
+
## Observability
|
|
166
|
+
|
|
167
|
+
`agent_kernel.instrument_kernel(kernel)` installs OpenTelemetry spans and
|
|
168
|
+
metric emission on `Kernel.invoke` and `Kernel.grant_capability`:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from agent_kernel import Kernel, instrument_kernel, OTEL_AVAILABLE
|
|
172
|
+
|
|
173
|
+
kernel = Kernel(registry=...)
|
|
174
|
+
if OTEL_AVAILABLE:
|
|
175
|
+
instrument_kernel(kernel) # no-op when [otel] extra not installed
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Spans: `agent_kernel.invoke`, `agent_kernel.grant`. Metrics:
|
|
179
|
+
`agent_kernel.invocations` (counter), `agent_kernel.invocation_duration`
|
|
180
|
+
(histogram, ms), `agent_kernel.policy_denials` (counter). The call is
|
|
181
|
+
idempotent — repeat invocations on the same kernel are no-ops.
|