onkernel 0.2.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.
- onkernel-0.2.0/.gitignore +25 -0
- onkernel-0.2.0/CHANGELOG.md +113 -0
- onkernel-0.2.0/LICENSE +21 -0
- onkernel-0.2.0/PKG-INFO +182 -0
- onkernel-0.2.0/README.md +145 -0
- onkernel-0.2.0/pyproject.toml +88 -0
- onkernel-0.2.0/src/kernel/__init__.py +85 -0
- onkernel-0.2.0/src/kernel/client.py +285 -0
- onkernel-0.2.0/src/kernel/crypto/__init__.py +12 -0
- onkernel-0.2.0/src/kernel/crypto/decrypt.py +39 -0
- onkernel-0.2.0/src/kernel/crypto/keys.py +30 -0
- onkernel-0.2.0/src/kernel/errors.py +304 -0
- onkernel-0.2.0/src/kernel/py.typed +0 -0
- onkernel-0.2.0/src/kernel/types.py +257 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ─── Python ──────────────────────────────────────────────────────────────
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
|
|
14
|
+
# ─── Secrets ─────────────────────────────────────────────────────────────
|
|
15
|
+
*.pem
|
|
16
|
+
!*-pub.pem
|
|
17
|
+
*.key
|
|
18
|
+
.env
|
|
19
|
+
.env.*
|
|
20
|
+
!.env.example
|
|
21
|
+
|
|
22
|
+
# ─── OS / editor ─────────────────────────────────────────────────────────
|
|
23
|
+
.DS_Store
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `onkernel` Python SDK are documented here. This
|
|
4
|
+
project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## 0.2.0 — 2026-06-17
|
|
7
|
+
|
|
8
|
+
First public PyPI release — the Kernel governance SDK with agent-to-agent
|
|
9
|
+
delegation. Designed to be obvious to use from an LLM-tool-dispatch layer
|
|
10
|
+
without a custom wrapper.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A2A delegation (`delegate()`)** — record agent-to-agent delegation chains
|
|
15
|
+
in the signed audit log. When one agent's ALLOW decision authorizes another
|
|
16
|
+
agent to act, pass the parent's `decision_id` so Kernel links the child
|
|
17
|
+
decision to its parent and unions their regulatory anchors:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
decision = orchestrator.execute("triage_refund", params=…) # root
|
|
21
|
+
kyc.delegate(decision.decision_id, "verify_identity", params=…) # child
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`delegate(parent_decision_id, …)` is sugar over the new
|
|
25
|
+
`execute(…, parent_decision_id=…)` keyword (sends the
|
|
26
|
+
`X-Kernel-Parent-Decision-Id` header). A cross-tenant or unknown parent is
|
|
27
|
+
refused identically to a missing one (no existence fingerprinting).
|
|
28
|
+
- **`ActionResponse.decision_id`** — the ALLOW decision's audit-event id, now
|
|
29
|
+
parsed off the response. Feed it to a downstream agent's `delegate()`. `None`
|
|
30
|
+
on non-ALLOW outcomes and on proxies predating execution verification.
|
|
31
|
+
- **`KernelOutcome` enum** on `ActionResponse.outcome`. Callers branch on
|
|
32
|
+
the outcome via `match/case` instead of catching an exception for the
|
|
33
|
+
"ask a human" path:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
resp = k.execute(action="refund", target="stripe", params={"amount_cents": 12000})
|
|
37
|
+
match resp.outcome:
|
|
38
|
+
case KernelOutcome.ALLOWED: do_thing(resp.result)
|
|
39
|
+
case KernelOutcome.REQUIRES_HUMAN: queue_for_review(resp.approval_id)
|
|
40
|
+
case KernelOutcome.BLOCKED: log_scanner_block(resp.violations)
|
|
41
|
+
case KernelOutcome.DENIED: log_policy_deny(resp.reason)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **`ScannerBlockedError`** — sibling of `PolicyDeniedError` for 403
|
|
45
|
+
responses with a populated `violations` list (PII / secrets / prompt
|
|
46
|
+
injection detector fired). Exposes `.violation_type` and
|
|
47
|
+
`.violation_details` so callers don't have to fish through
|
|
48
|
+
`e.violations[0]["type"]` themselves.
|
|
49
|
+
|
|
50
|
+
In v0.2 `ScannerBlockedError` inherits from `PolicyDeniedError` so
|
|
51
|
+
existing `except PolicyDeniedError:` blocks keep catching both. In v0.3
|
|
52
|
+
the two will become true siblings under `KernelDeniedError`; migrate by
|
|
53
|
+
catching them separately:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
try:
|
|
57
|
+
k.execute(...)
|
|
58
|
+
except ScannerBlockedError as e:
|
|
59
|
+
# PII / secret / injection — fix the input
|
|
60
|
+
...
|
|
61
|
+
except PolicyDeniedError as e:
|
|
62
|
+
# Policy rule denied — pick a different tool or escalate
|
|
63
|
+
...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **`KernelDeniedError`** — alias for the shared base of the 403-denial
|
|
67
|
+
family. Forward-compatible with the v0.3 split:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
except KernelDeniedError: # catches both scanner blocks and policy denies
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **`ActionResponse.result_message: str`** — always-non-empty
|
|
75
|
+
human-readable summary alongside `result: dict | None`. Removes the
|
|
76
|
+
LLM-anthropomorphizes-None failure mode ("the result was empty, should I
|
|
77
|
+
retry?"). Populated by the proxy when available; the SDK derives a
|
|
78
|
+
summary from `status` + `result` keys when the proxy is on an older
|
|
79
|
+
build.
|
|
80
|
+
|
|
81
|
+
Note: the Kernel proxy as of this release does NOT emit `result_message`
|
|
82
|
+
on successful action responses (only `message` on error paths). The SDK
|
|
83
|
+
derives a summary client-side and emits a `UserWarning` so operators can
|
|
84
|
+
tell stale proxies apart from current ones. Upgrade the Kernel proxy to
|
|
85
|
+
benefit from richer, server-side summaries.
|
|
86
|
+
|
|
87
|
+
### Deprecated
|
|
88
|
+
|
|
89
|
+
- **`ApprovalRequiredError`** — the 202-pending-approval path no longer
|
|
90
|
+
raises. The SDK returns `ActionResponse(outcome=REQUIRES_HUMAN,
|
|
91
|
+
approval_id=...)` and callers branch on `outcome`. The class is kept
|
|
92
|
+
importable for v0.2 and removed in v0.3; importing it emits a
|
|
93
|
+
`DeprecationWarning`.
|
|
94
|
+
|
|
95
|
+
- **`ActionResponse.message`** — renamed to `result_message`. Reading
|
|
96
|
+
`.message` emits a `DeprecationWarning`; removed in v0.3.
|
|
97
|
+
|
|
98
|
+
### Packaging
|
|
99
|
+
|
|
100
|
+
- First PyPI release as `onkernel` (namespace claimed; `kernel-sdk` was
|
|
101
|
+
already taken). `pip install onkernel` replaces the
|
|
102
|
+
`git+https://github.com/onkernel-ai/kernel-python` install string in
|
|
103
|
+
downstream `pyproject.toml`s.
|
|
104
|
+
- Added `LICENSE` (MIT), classifiers, `project.urls`, `[project.authors]`,
|
|
105
|
+
and `[project.optional-dependencies.dev]`.
|
|
106
|
+
- Added a `.github/workflows/test.yml` matrix (3.10–3.13) and a
|
|
107
|
+
`.github/workflows/publish.yml` that builds + publishes on tag push via
|
|
108
|
+
PyPI Trusted Publishing (no API token in secrets). Pre-release tags
|
|
109
|
+
(e.g. `v0.2.0-rc.1`) publish to TestPyPI first.
|
|
110
|
+
|
|
111
|
+
## 0.1.0
|
|
112
|
+
|
|
113
|
+
Initial baseline.
|
onkernel-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kernel (onkernel.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
onkernel-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onkernel
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for Kernel — deterministic governance for AI agents
|
|
5
|
+
Project-URL: Homepage, https://onkernel.ai
|
|
6
|
+
Project-URL: Documentation, https://github.com/onkernel-ai/kernel-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/onkernel-ai/kernel-python
|
|
8
|
+
Project-URL: Issues, https://github.com/onkernel-ai/kernel-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/onkernel-ai/kernel-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Kernel <engineering@onkernel.ai>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,ai,governance,guardrails,llm,policy
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Provides-Extra: crypto
|
|
28
|
+
Requires-Dist: cryptography>=44.0; extra == 'crypto'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: cryptography>=44.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# onkernel
|
|
39
|
+
|
|
40
|
+
Python SDK for [Kernel](https://onkernel.ai) — deterministic governance for AI agents.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install onkernel
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For encrypted response decryption:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install onkernel[crypto]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from kernel import Kernel, KernelOutcome
|
|
58
|
+
|
|
59
|
+
k = Kernel(api_key="ok_live_acme_bot1_abc123")
|
|
60
|
+
|
|
61
|
+
resp = k.execute(action="send_email", target="user@example.com", params={"body": "Hello"})
|
|
62
|
+
|
|
63
|
+
match resp.outcome:
|
|
64
|
+
case KernelOutcome.ALLOWED:
|
|
65
|
+
# resp.result and resp.result_message are populated
|
|
66
|
+
print(resp.result_message)
|
|
67
|
+
case KernelOutcome.REQUIRES_HUMAN:
|
|
68
|
+
# 202 — escalate to your approval queue
|
|
69
|
+
approval_id = resp.approval_id
|
|
70
|
+
case KernelOutcome.BLOCKED:
|
|
71
|
+
# a scanner fired; resp.violations is populated
|
|
72
|
+
...
|
|
73
|
+
case KernelOutcome.DENIED:
|
|
74
|
+
# a policy rule denied; resp.reason explains why
|
|
75
|
+
...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`ActionResponse` always carries a non-empty `result_message: str` — a
|
|
79
|
+
human-readable summary that's safe to surface in an LLM tool-call result.
|
|
80
|
+
|
|
81
|
+
### Handling errors
|
|
82
|
+
|
|
83
|
+
The denial path raises typed exceptions on 403:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from kernel import (
|
|
87
|
+
Kernel,
|
|
88
|
+
ScannerBlockedError, # scanner fired (PII, secrets, prompt injection, …)
|
|
89
|
+
PolicyDeniedError, # policy rule denied with no scanner involvement
|
|
90
|
+
KernelDeniedError, # base / alias — catch both
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
k.execute(action="export_pii_report", target="customers")
|
|
95
|
+
except ScannerBlockedError as e:
|
|
96
|
+
# e.violation_type, e.violation_details, e.violations
|
|
97
|
+
pass
|
|
98
|
+
except PolicyDeniedError as e:
|
|
99
|
+
# e.rule_id, e.policy_id, e.reason
|
|
100
|
+
pass
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3-step token flow
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
session = k.create_session(intent="onboard new customer")
|
|
107
|
+
token = k.request_token(session.id, action="create_account", target="stripe")
|
|
108
|
+
result = k.execute_token(token.token_id, agent_id="bot1")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Decrypting Responses
|
|
112
|
+
|
|
113
|
+
When a policy has `encryption_enabled: true`, responses arrive encrypted:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from kernel import Kernel
|
|
117
|
+
from kernel.crypto import load_private_key
|
|
118
|
+
|
|
119
|
+
k = Kernel(api_key="ok_live_acme_bot1_abc123")
|
|
120
|
+
private_key = load_private_key("path/to/private_key.pem")
|
|
121
|
+
|
|
122
|
+
resp = k.execute(action="fetch_records", target="db")
|
|
123
|
+
if resp.encrypted:
|
|
124
|
+
data = resp.decrypt(private_key)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Publishing (maintainers)
|
|
128
|
+
|
|
129
|
+
The SDK is published to PyPI as `onkernel`. Releases go out via tag push;
|
|
130
|
+
the `publish.yml` workflow handles build + Trusted-Publishing OIDC.
|
|
131
|
+
|
|
132
|
+
**Always release to TestPyPI first.** PyPI is fussy about metadata and
|
|
133
|
+
artifact integrity; a TestPyPI dry-run catches mistakes before they're
|
|
134
|
+
permanent (PyPI does not allow overwriting a released version).
|
|
135
|
+
|
|
136
|
+
### One-time setup
|
|
137
|
+
|
|
138
|
+
1. Create the project on PyPI: `pip install build twine && python -m build && twine upload --repository testpypi dist/*` (first manual upload claims the namespace).
|
|
139
|
+
2. On PyPI → project → Publishing, add a pending publisher:
|
|
140
|
+
- Owner: `onkernel-ai`
|
|
141
|
+
- Repository: `kernel-python`
|
|
142
|
+
- Workflow: `publish.yml`
|
|
143
|
+
- Environment: `pypi`
|
|
144
|
+
3. Repeat the publisher binding on test.pypi.org with environment `testpypi`.
|
|
145
|
+
4. In the GitHub repo Settings → Environments, create `pypi` and `testpypi`. Add required reviewers on `pypi` if you want a release gate.
|
|
146
|
+
|
|
147
|
+
### Cutting a release
|
|
148
|
+
|
|
149
|
+
1. Bump `pyproject.toml` `[project].version` and `src/kernel/__init__.py` `__version__` together.
|
|
150
|
+
2. Update `CHANGELOG.md`.
|
|
151
|
+
3. Pre-release dry-run:
|
|
152
|
+
```bash
|
|
153
|
+
git tag v0.X.Y-rc.1
|
|
154
|
+
git push --tags
|
|
155
|
+
```
|
|
156
|
+
The `publish.yml` workflow detects the `-rc.N` suffix and routes to TestPyPI. Validate:
|
|
157
|
+
```bash
|
|
158
|
+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ onkernel==0.X.Y-rc.1
|
|
159
|
+
python -c "from kernel import Kernel, KernelOutcome; print(KernelOutcome.ALLOWED)"
|
|
160
|
+
```
|
|
161
|
+
4. Real release:
|
|
162
|
+
```bash
|
|
163
|
+
git tag v0.X.Y
|
|
164
|
+
git push --tags
|
|
165
|
+
```
|
|
166
|
+
The same workflow routes the un-suffixed tag to real PyPI.
|
|
167
|
+
|
|
168
|
+
The workflow verifies that the tag matches `pyproject.toml [project].version`
|
|
169
|
+
before building — a mismatched tag fails fast instead of publishing the
|
|
170
|
+
wrong artifact.
|
|
171
|
+
|
|
172
|
+
### Fallback: manual API token
|
|
173
|
+
|
|
174
|
+
If Trusted Publishing isn't yet bound, set repo secret `PYPI_API_TOKEN`
|
|
175
|
+
and replace the OIDC publish step with:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
- run: twine upload -u __token__ -p "$PYPI_API_TOKEN" dist/*
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Tokens carry blast-radius risk (leak = arbitrary release). Migrate to OIDC
|
|
182
|
+
as soon as the binding is approved on the PyPI side.
|
onkernel-0.2.0/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# onkernel
|
|
2
|
+
|
|
3
|
+
Python SDK for [Kernel](https://onkernel.ai) — deterministic governance for AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install onkernel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For encrypted response decryption:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install onkernel[crypto]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from kernel import Kernel, KernelOutcome
|
|
21
|
+
|
|
22
|
+
k = Kernel(api_key="ok_live_acme_bot1_abc123")
|
|
23
|
+
|
|
24
|
+
resp = k.execute(action="send_email", target="user@example.com", params={"body": "Hello"})
|
|
25
|
+
|
|
26
|
+
match resp.outcome:
|
|
27
|
+
case KernelOutcome.ALLOWED:
|
|
28
|
+
# resp.result and resp.result_message are populated
|
|
29
|
+
print(resp.result_message)
|
|
30
|
+
case KernelOutcome.REQUIRES_HUMAN:
|
|
31
|
+
# 202 — escalate to your approval queue
|
|
32
|
+
approval_id = resp.approval_id
|
|
33
|
+
case KernelOutcome.BLOCKED:
|
|
34
|
+
# a scanner fired; resp.violations is populated
|
|
35
|
+
...
|
|
36
|
+
case KernelOutcome.DENIED:
|
|
37
|
+
# a policy rule denied; resp.reason explains why
|
|
38
|
+
...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`ActionResponse` always carries a non-empty `result_message: str` — a
|
|
42
|
+
human-readable summary that's safe to surface in an LLM tool-call result.
|
|
43
|
+
|
|
44
|
+
### Handling errors
|
|
45
|
+
|
|
46
|
+
The denial path raises typed exceptions on 403:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from kernel import (
|
|
50
|
+
Kernel,
|
|
51
|
+
ScannerBlockedError, # scanner fired (PII, secrets, prompt injection, …)
|
|
52
|
+
PolicyDeniedError, # policy rule denied with no scanner involvement
|
|
53
|
+
KernelDeniedError, # base / alias — catch both
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
k.execute(action="export_pii_report", target="customers")
|
|
58
|
+
except ScannerBlockedError as e:
|
|
59
|
+
# e.violation_type, e.violation_details, e.violations
|
|
60
|
+
pass
|
|
61
|
+
except PolicyDeniedError as e:
|
|
62
|
+
# e.rule_id, e.policy_id, e.reason
|
|
63
|
+
pass
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3-step token flow
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
session = k.create_session(intent="onboard new customer")
|
|
70
|
+
token = k.request_token(session.id, action="create_account", target="stripe")
|
|
71
|
+
result = k.execute_token(token.token_id, agent_id="bot1")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Decrypting Responses
|
|
75
|
+
|
|
76
|
+
When a policy has `encryption_enabled: true`, responses arrive encrypted:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from kernel import Kernel
|
|
80
|
+
from kernel.crypto import load_private_key
|
|
81
|
+
|
|
82
|
+
k = Kernel(api_key="ok_live_acme_bot1_abc123")
|
|
83
|
+
private_key = load_private_key("path/to/private_key.pem")
|
|
84
|
+
|
|
85
|
+
resp = k.execute(action="fetch_records", target="db")
|
|
86
|
+
if resp.encrypted:
|
|
87
|
+
data = resp.decrypt(private_key)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Publishing (maintainers)
|
|
91
|
+
|
|
92
|
+
The SDK is published to PyPI as `onkernel`. Releases go out via tag push;
|
|
93
|
+
the `publish.yml` workflow handles build + Trusted-Publishing OIDC.
|
|
94
|
+
|
|
95
|
+
**Always release to TestPyPI first.** PyPI is fussy about metadata and
|
|
96
|
+
artifact integrity; a TestPyPI dry-run catches mistakes before they're
|
|
97
|
+
permanent (PyPI does not allow overwriting a released version).
|
|
98
|
+
|
|
99
|
+
### One-time setup
|
|
100
|
+
|
|
101
|
+
1. Create the project on PyPI: `pip install build twine && python -m build && twine upload --repository testpypi dist/*` (first manual upload claims the namespace).
|
|
102
|
+
2. On PyPI → project → Publishing, add a pending publisher:
|
|
103
|
+
- Owner: `onkernel-ai`
|
|
104
|
+
- Repository: `kernel-python`
|
|
105
|
+
- Workflow: `publish.yml`
|
|
106
|
+
- Environment: `pypi`
|
|
107
|
+
3. Repeat the publisher binding on test.pypi.org with environment `testpypi`.
|
|
108
|
+
4. In the GitHub repo Settings → Environments, create `pypi` and `testpypi`. Add required reviewers on `pypi` if you want a release gate.
|
|
109
|
+
|
|
110
|
+
### Cutting a release
|
|
111
|
+
|
|
112
|
+
1. Bump `pyproject.toml` `[project].version` and `src/kernel/__init__.py` `__version__` together.
|
|
113
|
+
2. Update `CHANGELOG.md`.
|
|
114
|
+
3. Pre-release dry-run:
|
|
115
|
+
```bash
|
|
116
|
+
git tag v0.X.Y-rc.1
|
|
117
|
+
git push --tags
|
|
118
|
+
```
|
|
119
|
+
The `publish.yml` workflow detects the `-rc.N` suffix and routes to TestPyPI. Validate:
|
|
120
|
+
```bash
|
|
121
|
+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ onkernel==0.X.Y-rc.1
|
|
122
|
+
python -c "from kernel import Kernel, KernelOutcome; print(KernelOutcome.ALLOWED)"
|
|
123
|
+
```
|
|
124
|
+
4. Real release:
|
|
125
|
+
```bash
|
|
126
|
+
git tag v0.X.Y
|
|
127
|
+
git push --tags
|
|
128
|
+
```
|
|
129
|
+
The same workflow routes the un-suffixed tag to real PyPI.
|
|
130
|
+
|
|
131
|
+
The workflow verifies that the tag matches `pyproject.toml [project].version`
|
|
132
|
+
before building — a mismatched tag fails fast instead of publishing the
|
|
133
|
+
wrong artifact.
|
|
134
|
+
|
|
135
|
+
### Fallback: manual API token
|
|
136
|
+
|
|
137
|
+
If Trusted Publishing isn't yet bound, set repo secret `PYPI_API_TOKEN`
|
|
138
|
+
and replace the OIDC publish step with:
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
- run: twine upload -u __token__ -p "$PYPI_API_TOKEN" dist/*
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Tokens carry blast-radius risk (leak = arbitrary release). Migrate to OIDC
|
|
145
|
+
as soon as the binding is approved on the PyPI side.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "onkernel"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Python SDK for Kernel — deterministic governance for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Kernel", email = "engineering@onkernel.ai" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agents", "governance", "policy", "llm", "guardrails"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"httpx>=0.27",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
crypto = ["cryptography>=44.0"]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"pytest-asyncio>=0.23",
|
|
38
|
+
"ruff>=0.6",
|
|
39
|
+
"mypy>=1.10",
|
|
40
|
+
"cryptography>=44.0",
|
|
41
|
+
"respx>=0.21",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://onkernel.ai"
|
|
46
|
+
Documentation = "https://github.com/onkernel-ai/kernel-python#readme"
|
|
47
|
+
Repository = "https://github.com/onkernel-ai/kernel-python"
|
|
48
|
+
Issues = "https://github.com/onkernel-ai/kernel-python/issues"
|
|
49
|
+
Changelog = "https://github.com/onkernel-ai/kernel-python/blob/main/CHANGELOG.md"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/kernel"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
include = [
|
|
56
|
+
"src/kernel",
|
|
57
|
+
"README.md",
|
|
58
|
+
"CHANGELOG.md",
|
|
59
|
+
"LICENSE",
|
|
60
|
+
"pyproject.toml",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
target-version = "py310"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["E", "F", "I", "B", "UP", "SIM"]
|
|
69
|
+
ignore = ["E501"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.per-file-ignores]
|
|
72
|
+
"tests/*" = ["B018"]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
python_version = "3.10"
|
|
76
|
+
strict_optional = true
|
|
77
|
+
warn_unused_ignores = true
|
|
78
|
+
ignore_missing_imports = true
|
|
79
|
+
packages = ["kernel"]
|
|
80
|
+
|
|
81
|
+
[tool.pytest.ini_options]
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
filterwarnings = [
|
|
84
|
+
# Tests should fail if they trigger an unexpected DeprecationWarning so
|
|
85
|
+
# callers see clean output. Individual tests assert deprecations with
|
|
86
|
+
# `pytest.warns`.
|
|
87
|
+
"error::DeprecationWarning:kernel.*",
|
|
88
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Kernel SDK — deterministic governance for AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import warnings as _warnings
|
|
6
|
+
from typing import Any as _Any
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.0"
|
|
9
|
+
|
|
10
|
+
import contextlib as _contextlib
|
|
11
|
+
|
|
12
|
+
from kernel.client import Kernel
|
|
13
|
+
from kernel.errors import (
|
|
14
|
+
AuthError,
|
|
15
|
+
CircuitOpenError,
|
|
16
|
+
FeatureNotInTier,
|
|
17
|
+
KernelDeniedError,
|
|
18
|
+
KernelError,
|
|
19
|
+
PlanCeilingHit,
|
|
20
|
+
PlanLimitExceeded,
|
|
21
|
+
PlanSoftWarning,
|
|
22
|
+
PolicyDeniedError,
|
|
23
|
+
RateLimitError,
|
|
24
|
+
ScannerBlockedError,
|
|
25
|
+
WebhookError,
|
|
26
|
+
)
|
|
27
|
+
from kernel.types import (
|
|
28
|
+
ActionResponse,
|
|
29
|
+
ApprovalResult,
|
|
30
|
+
ExecuteResponse,
|
|
31
|
+
KernelOutcome,
|
|
32
|
+
Session,
|
|
33
|
+
TokenResponse,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
with _contextlib.suppress(ImportError):
|
|
37
|
+
from kernel.crypto import decrypt, decrypt_json # noqa: F401 — re-export
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def __getattr__(name: str) -> _Any:
|
|
41
|
+
"""Lazy deprecation for `kernel.ApprovalRequiredError` import.
|
|
42
|
+
|
|
43
|
+
The SDK no longer raises this exception (the 202 path now returns an
|
|
44
|
+
`ActionResponse` with `outcome == KernelOutcome.REQUIRES_HUMAN`). The
|
|
45
|
+
name is kept exported for one minor version so existing
|
|
46
|
+
`from kernel import ApprovalRequiredError` keeps importing. Reading
|
|
47
|
+
the symbol emits a `DeprecationWarning`; the class is removed in v0.3.
|
|
48
|
+
"""
|
|
49
|
+
if name == "ApprovalRequiredError":
|
|
50
|
+
_warnings.warn(
|
|
51
|
+
"ApprovalRequiredError is deprecated and will be removed in v0.3. "
|
|
52
|
+
"The SDK no longer raises on 202 responses; branch on "
|
|
53
|
+
"ActionResponse.outcome (KernelOutcome.REQUIRES_HUMAN) instead. "
|
|
54
|
+
"See CHANGELOG for migration.",
|
|
55
|
+
DeprecationWarning,
|
|
56
|
+
stacklevel=2,
|
|
57
|
+
)
|
|
58
|
+
from kernel.errors import ApprovalRequiredError as _AR
|
|
59
|
+
return _AR
|
|
60
|
+
raise AttributeError(f"module 'kernel' has no attribute {name!r}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"Kernel",
|
|
65
|
+
"KernelError",
|
|
66
|
+
"AuthError",
|
|
67
|
+
"KernelDeniedError",
|
|
68
|
+
"PolicyDeniedError",
|
|
69
|
+
"ScannerBlockedError",
|
|
70
|
+
"RateLimitError",
|
|
71
|
+
"CircuitOpenError",
|
|
72
|
+
"WebhookError",
|
|
73
|
+
"PlanLimitExceeded",
|
|
74
|
+
"PlanCeilingHit",
|
|
75
|
+
"FeatureNotInTier",
|
|
76
|
+
"PlanSoftWarning",
|
|
77
|
+
"ActionResponse",
|
|
78
|
+
"KernelOutcome",
|
|
79
|
+
"Session",
|
|
80
|
+
"TokenResponse",
|
|
81
|
+
"ExecuteResponse",
|
|
82
|
+
"ApprovalResult",
|
|
83
|
+
# Deprecated, retained one minor version:
|
|
84
|
+
"ApprovalRequiredError",
|
|
85
|
+
]
|