capability-reasoning-kernel 0.4.1__tar.gz → 0.4.2__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.
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/.env.example +3 -1
- capability_reasoning_kernel-0.4.2/.github/workflows/pages.yml +48 -0
- capability_reasoning_kernel-0.4.2/.pre-commit-config.yaml +33 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/CHANGELOG.md +49 -0
- capability_reasoning_kernel-0.4.2/CLAUDE.md +35 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/PKG-INFO +18 -4
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/README.md +17 -3
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/SECURITY.md +3 -3
- capability_reasoning_kernel-0.4.2/docs/ANALYSIS-2026-07.md +246 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/docs/DEVELOPMENT.md +4 -3
- capability_reasoning_kernel-0.4.2/docs/favicon.svg +6 -0
- capability_reasoning_kernel-0.4.2/docs/index.html +760 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/justfile +3 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/pyproject.toml +2 -1
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/__init__.py +2 -1
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/config.py +4 -2
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/reasoner_error.py +4 -2
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/kernel/gate.py +8 -4
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/anthropic.py +7 -2
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/base.py +13 -3
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/factory.py +15 -2
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/openai.py +10 -3
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/parse.py +12 -4
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/roles.py +7 -4
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/provenance.py +6 -3
- capability_reasoning_kernel-0.4.2/src/reasoning_kernel/tools/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/conftest.py +1 -1
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_gate_provenance_declassify.py +22 -0
- capability_reasoning_kernel-0.4.2/tests/test_gate_unrestricted_readers.py +84 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_parse_with_schema.py +25 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_provenance_propagation.py +19 -0
- capability_reasoning_kernel-0.4.2/tests/test_reasoner_factory.py +50 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_reasoner_robustness.py +115 -3
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/uv.lock +1 -1
- capability_reasoning_kernel-0.4.1/.pre-commit-config.yaml +0 -26
- capability_reasoning_kernel-0.4.1/tests/test_reasoner_factory.py +0 -27
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/.github/workflows/ci.yml +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/.github/workflows/release.yml +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/.gitignore +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/.python-version +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/LICENSE +0 -0
- /capability_reasoning_kernel-0.4.1/src/reasoning_kernel/context/__init__.py → /capability_reasoning_kernel-0.4.2/docs/.nojekyll +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/demo → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/context}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/context/assembler.py +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/kernel → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/demo}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/_report.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/email_exfil.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/live_run.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/merge.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/run_limits.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/demo/subkernel.py +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/memory → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/kernel}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/kernel/effects.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/kernel/interpreter.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/kernel/taint.py +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/reasoner → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/memory}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/memory/store.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/memory/trace.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/py.typed +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/schemas → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/reasoner}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/deepseek.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/reasoner/fake.py +0 -0
- {capability_reasoning_kernel-0.4.1/src/reasoning_kernel/tools → capability_reasoning_kernel-0.4.2/src/reasoning_kernel/schemas}/__init__.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/capability.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/ids.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/limits.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/plan.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/policy.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/registry.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/trace.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/schemas/values.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/tools/demo_mail.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/src/reasoning_kernel/tools/registry.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_capability.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_composition.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_declass_policy.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_demo_email_exfil.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_demo_mail_tools.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_gate_capability.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_interpreter_loop.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_invariant_a.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_merge.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_no_bypass_conformance.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_plan_schema.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_providers_live.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_public_api.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_registry.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_run_limits.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_store.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_subject_provenance.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_subkernel.py +0 -0
- {capability_reasoning_kernel-0.4.1 → capability_reasoning_kernel-0.4.2}/tests/test_trace_append_only.py +0 -0
|
@@ -7,7 +7,9 @@ DEEPSEEK_API_KEY=
|
|
|
7
7
|
|
|
8
8
|
# Optional overrides (defaults shown)
|
|
9
9
|
# RK_LLM_PROVIDER_DEFAULT=anthropic
|
|
10
|
-
# RK_LLM_MODEL_ANTHROPIC=claude-sonnet-
|
|
10
|
+
# RK_LLM_MODEL_ANTHROPIC=claude-sonnet-5 # or claude-opus-4-8 (more capable, different pricing)
|
|
11
11
|
# RK_LLM_MODEL_OPENAI=gpt-5.5 # or gpt-5.5-pro (more capable, different pricing)
|
|
12
12
|
# RK_LLM_MODEL_DEEPSEEK=deepseek-v4-flash # or deepseek-v4-pro (more capable, different pricing)
|
|
13
13
|
# RK_DEEPSEEK_BASE_URL=https://api.deepseek.com
|
|
14
|
+
# RK_LLM_TIMEOUT_SECONDS=120.0 # per-request SDK timeout passed to provider clients
|
|
15
|
+
# RK_LLM_MAX_TOKENS=4096 # default max_tokens for structured-output calls
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Pages
|
|
2
|
+
|
|
3
|
+
# Deploy the static landing page in `docs/` to GitHub Pages. No build step — the page is
|
|
4
|
+
# self-contained HTML (CSS + SVG inline). First-time setup requires the repository's
|
|
5
|
+
# Settings → Pages → Source to be set to "GitHub Actions".
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
paths:
|
|
10
|
+
- "docs/**"
|
|
11
|
+
- ".github/workflows/pages.yml"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pages: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
# Allow one concurrent deployment; don't cancel an in-progress publish.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: pages
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
deploy:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
steps:
|
|
31
|
+
# Actions pinned to immutable commit SHAs (supply-chain hardening); tag in comment.
|
|
32
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
|
|
36
|
+
- name: Configure Pages
|
|
37
|
+
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
|
38
|
+
with:
|
|
39
|
+
enablement: true # self-enable Pages (build_type=workflow) on first run — no manual Settings step
|
|
40
|
+
|
|
41
|
+
- name: Upload artifact
|
|
42
|
+
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
|
43
|
+
with:
|
|
44
|
+
path: docs
|
|
45
|
+
|
|
46
|
+
- name: Deploy to GitHub Pages
|
|
47
|
+
id: deployment
|
|
48
|
+
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
- id: check-toml
|
|
7
|
+
- id: check-added-large-files
|
|
8
|
+
args: ["--maxkb=1000"]
|
|
9
|
+
- id: end-of-file-fixer
|
|
10
|
+
- id: trailing-whitespace
|
|
11
|
+
|
|
12
|
+
# Ruff and pyright run as local hooks through uv so the hook, `just lint`/`just typecheck`,
|
|
13
|
+
# and CI all execute the exact versions pinned in uv.lock — no pinned-mirror drift.
|
|
14
|
+
- repo: local
|
|
15
|
+
hooks:
|
|
16
|
+
- id: ruff-check
|
|
17
|
+
name: ruff check --fix (uv.lock version, same as `just fix`)
|
|
18
|
+
language: system
|
|
19
|
+
entry: uv run ruff check --fix src/ tests/
|
|
20
|
+
pass_filenames: false
|
|
21
|
+
types_or: [python, pyi]
|
|
22
|
+
- id: ruff-format
|
|
23
|
+
name: ruff format (uv.lock version, same as `just fix`)
|
|
24
|
+
language: system
|
|
25
|
+
entry: uv run ruff format src/ tests/
|
|
26
|
+
pass_filenames: false
|
|
27
|
+
types_or: [python, pyi]
|
|
28
|
+
- id: pyright
|
|
29
|
+
name: pyright (same scope as `just typecheck` — strict dirs set in pyproject)
|
|
30
|
+
language: system
|
|
31
|
+
entry: uv run pyright
|
|
32
|
+
pass_filenames: false
|
|
33
|
+
types: [python]
|
|
@@ -4,6 +4,54 @@ All notable changes to this project are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.2] - 2026-07-10
|
|
8
|
+
|
|
9
|
+
A correctness release for the gate's provenance stage, plus fail-closed hardening of the provider
|
|
10
|
+
integrations. No API removals; two behavior changes in the gate/taint semantics (both strictly
|
|
11
|
+
safety-improving) and stricter model resolution.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Gate: a tainted value with unrestricted `readers` no longer skips declassification.**
|
|
16
|
+
`readers=None` is reserved for purely trusted data, but `quarantine_label` preserves the source's
|
|
17
|
+
readers — so a Q-LLM parse of a *trusted* const yielded a value that was tainted (Q_LLM) yet
|
|
18
|
+
carried `readers=None`, and the gate's stage 3 auto-permitted it into a WRITE without consulting
|
|
19
|
+
the declassification policy. The fast-path now requires an **explicit** readers set covering the
|
|
20
|
+
tool's required capabilities; unscoped tainted flows go through the declassifier. (Regression
|
|
21
|
+
tests at gate level and end-to-end: a recipient laundered through the Q-LLM is now blocked by the
|
|
22
|
+
policy instead of auto-committed.)
|
|
23
|
+
- **Taint: `DERIVED` is no longer counted as an untrusted source.** Merging values derived solely
|
|
24
|
+
from the trusted query no longer manufactures taint (which caused spurious denials in policies
|
|
25
|
+
keyed on `is_tainted`); taint derives only from the actual presence of `TOOL_READ` / `Q_LLM` in
|
|
26
|
+
the joined sources. `DERIVED` remains on the label as an audit marker. The two fixes land
|
|
27
|
+
together deliberately: each previously masked the other.
|
|
28
|
+
- **`RK_LLM_MAX_TOKENS` now has an effect.** `call_structured` / `parse_with_schema` defaulted to a
|
|
29
|
+
private module constant, silently ignoring the `settings.llm_max_tokens` override; they now read
|
|
30
|
+
settings (lazily) as the single source of truth.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Transport faults fail closed with an audit record.** Provider API errors (rate limit exhausted,
|
|
35
|
+
5xx, network, SDK timeout) are wrapped in a new `TransportError(ReasonerError)` by the Anthropic
|
|
36
|
+
and OpenAI/Deepseek providers, so an in-flight run ends with a terminal trace event
|
|
37
|
+
(`PlanRejected`/`RunErrored`) instead of an unrecorded traceback.
|
|
38
|
+
- **Model resolution is strict and construction-time.** `PLLM`/`QLLM` no longer default to
|
|
39
|
+
`model="fake"` (a real provider without an explicit model got a 404 deep inside a run); an
|
|
40
|
+
omitted model resolves via `default_model_for(provider.name)` at construction. `default_model_for`
|
|
41
|
+
now raises `ValueError` for unknown providers instead of silently falling back to the Anthropic
|
|
42
|
+
model, and maps `"fake"` to the FakeProvider's ignored sentinel. `call_structured` requires
|
|
43
|
+
`model=` explicitly.
|
|
44
|
+
- **Anthropic default model refreshed**: `claude-sonnet-4-6` → `claude-sonnet-5` (comment variant
|
|
45
|
+
stays `claude-opus-4-8`); `.env.example` and `docs/DEVELOPMENT.md` updated, including the
|
|
46
|
+
previously undocumented `RK_LLM_TIMEOUT_SECONDS` / `RK_LLM_MAX_TOKENS` overrides.
|
|
47
|
+
- **Branch coverage enabled** (`coverage: branch = true`): line coverage alone hid the gate's
|
|
48
|
+
untested readers fast-path; the new gate tests pin both directions of that branch.
|
|
49
|
+
- **Toolchain drift eliminated.** pre-commit now runs ruff and pyright as local hooks through
|
|
50
|
+
`uv run` (same versions as `uv.lock`, same scope as `just lint`/`just typecheck` and CI, instead
|
|
51
|
+
of a stale pinned ruff mirror and a partial pyright scope); a new aggregate `just check` runs
|
|
52
|
+
lint + typecheck + test exactly as CI does. `CLAUDE.md` and the README role map now include
|
|
53
|
+
`tools/` (the registry is part of the no-bypass argument).
|
|
54
|
+
|
|
7
55
|
## [0.4.1] - 2026-05-31
|
|
8
56
|
|
|
9
57
|
### Changed
|
|
@@ -95,6 +143,7 @@ Initial reference implementation of the Reasoning Kernel pattern (strong / CaMeL
|
|
|
95
143
|
invariants, no-effect-bypasses-the-Verifier by construction, the deterministic declassification seam,
|
|
96
144
|
and the worked email-exfiltration demo.
|
|
97
145
|
|
|
146
|
+
[0.4.2]: https://github.com/gianlucamazza/reasoning-kernel/releases/tag/v0.4.2
|
|
98
147
|
[0.4.1]: https://github.com/gianlucamazza/reasoning-kernel/releases/tag/v0.4.1
|
|
99
148
|
[0.4.0]: https://github.com/gianlucamazza/reasoning-kernel/releases/tag/v0.4.0
|
|
100
149
|
[0.3.0]: https://github.com/gianlucamazza/reasoning-kernel/releases/tag/v0.3.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CLAUDE.md — reasoning-kernel
|
|
2
|
+
|
|
3
|
+
Framework-agnostic Python **reference implementation** of the Reasoning Kernel pattern (strong,
|
|
4
|
+
CaMeL-like form; Debenedetti et al. 2025): the LLM is treated as untrusted compute, mediated by
|
|
5
|
+
context on input and verification on output. Published to PyPI as `capability-reasoning-kernel`.
|
|
6
|
+
Remote: `gianlucamazza/reasoning-kernel`.
|
|
7
|
+
|
|
8
|
+
## The two invariants (do not violate)
|
|
9
|
+
- **A** — the reasoner never sees raw reality: every model call gets a system-assembled, inspectable
|
|
10
|
+
context (`src/reasoning_kernel/context/`).
|
|
11
|
+
- **B** — the reasoner never commits reality: no model output becomes a durable effect except through
|
|
12
|
+
the single deterministic gate (`src/reasoning_kernel/kernel/gate.py`).
|
|
13
|
+
|
|
14
|
+
The pattern fixes a *topology*, not a policy guarantee — keep mediation/verification at those
|
|
15
|
+
boundaries by construction.
|
|
16
|
+
|
|
17
|
+
## Stack & layout
|
|
18
|
+
- Python, `uv` (uv.lock), `pyproject.toml`, `just`. Lint Ruff, types pyright.
|
|
19
|
+
- `src/reasoning_kernel/`: `context/` · `kernel/` (gate) · `reasoner/` · `memory/` · `schemas/` ·
|
|
20
|
+
`tools/` (registry — sole holder of tool callables, part of the no-bypass argument) · `demo/`
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
```bash
|
|
24
|
+
just check # lint + typecheck + test (everything CI runs)
|
|
25
|
+
just lint # ruff
|
|
26
|
+
just fix # ruff --fix
|
|
27
|
+
just typecheck # pyright
|
|
28
|
+
just test-live # tests against live model
|
|
29
|
+
just demo # run demo (also: demo-live, demo-subkernel, demo-limits, demo-merge, demo-reasoner-error)
|
|
30
|
+
uv sync # install/refresh env (--all-extras for providers + dev tools)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Conventions
|
|
34
|
+
- It's a reference impl + spec, not a turn-key product — favor clarity and conformance over features.
|
|
35
|
+
- Changes touching invariants A/B must preserve the topology; update `docs/` and the README spec.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capability-reasoning-kernel
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Reference implementation of the Reasoning Kernel pattern (strong / CaMeL-like form)
|
|
5
5
|
Project-URL: Homepage, https://github.com/gianlucamazza/reasoning-kernel
|
|
6
6
|
Project-URL: Repository, https://github.com/gianlucamazza/reasoning-kernel
|
|
@@ -32,6 +32,15 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
|
|
33
33
|
# Reasoning Kernel
|
|
34
34
|
|
|
35
|
+
[](https://pypi.org/project/capability-reasoning-kernel/)
|
|
36
|
+
[](https://pypi.org/project/capability-reasoning-kernel/)
|
|
37
|
+
[](https://github.com/gianlucamazza/reasoning-kernel/actions/workflows/ci.yml)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
[](https://github.com/astral-sh/ruff)
|
|
40
|
+
[](https://microsoft.github.io/pyright/)
|
|
41
|
+
|
|
42
|
+
**[Live site →](https://gianlucamazza.github.io/reasoning-kernel/)**
|
|
43
|
+
|
|
35
44
|
**The problem.** An LLM agent that reads untrusted data — an email, a web page, a tool result — can be
|
|
36
45
|
hijacked by instructions hidden in that data and then act on them: leak your contacts, send mail, call
|
|
37
46
|
tools on your behalf. This is a reference implementation of an architecture where such a hijack
|
|
@@ -82,6 +91,7 @@ The trusted, deterministic kernel is the **interpreter + capability/provenance g
|
|
|
82
91
|
| Reasoner(s) | `reasoner/` (multi-provider) | a provider or the interface |
|
|
83
92
|
| Conductor | `kernel/interpreter.py` | the execution loop |
|
|
84
93
|
| Verifier | `kernel/gate.py`, `effects.py` | verification policy |
|
|
94
|
+
| Tool catalog | `tools/registry.py` | sole holder of tool callables |
|
|
85
95
|
| Memory / Trace | `memory/` | durability / audit format |
|
|
86
96
|
|
|
87
97
|
Reasoner providers: Anthropic, OpenAI, Deepseek (OpenAI-compatible, reusing the `openai` SDK via a
|
|
@@ -184,15 +194,19 @@ result = kernel.run(ctx) # RunResult(trace, committed); committed is
|
|
|
184
194
|
|
|
185
195
|
- **Provenance is multi-dimensional**: a `ProvenanceLabel` carries *origin* (`sources`), *where it may
|
|
186
196
|
flow* (`readers`), and *whose data it is* (`subjects`). Third-party data is never auto-released into a
|
|
187
|
-
WRITE — even to the requesting user — and the Q-LLM cannot launder any of these dimensions.
|
|
197
|
+
WRITE — even to the requesting user — and the Q-LLM cannot launder any of these dimensions. A tainted
|
|
198
|
+
value whose flow was never scoped (`readers=None` is reserved for purely trusted data) is likewise
|
|
199
|
+
never auto-permitted into a WRITE: it is routed to the declassifier like any other tainted flow.
|
|
188
200
|
- **Invariant A is typed**: the trusted channel is a `TrustedQuery` (text + label); `const`/inline
|
|
189
201
|
literals DERIVE their label from it, so the trust assumption is explicit rather than by convention.
|
|
190
202
|
- **Termination**: `RunLimits` bounds steps / effects / q-parses (and an optional per-call timeout); a
|
|
191
203
|
run exceeding a bound aborts closed (`RunAborted`), committing nothing further. The timeout abort is
|
|
192
204
|
prompt — it does not block waiting on the hung call (`kernel/interpreter.py:_call_reasoner`).
|
|
193
205
|
- **Reasoner failure is fail-closed**: a provider that returns no usable output (empty / refused /
|
|
194
|
-
malformed) raises `ReasonerError` (`reasoner/base.py`)
|
|
195
|
-
|
|
206
|
+
malformed) raises `ReasonerError` (`reasoner/base.py`), and a provider call that fails in transport
|
|
207
|
+
(rate limit exhausted, 5xx, network fault) raises its `TransportError` subclass; either way the
|
|
208
|
+
Conductor records a terminal trace event and commits nothing, rather than crashing or acting on a
|
|
209
|
+
partial result. Treating the model as untrusted compute
|
|
196
210
|
means a flaky reasoner can never produce a half-applied effect.
|
|
197
211
|
- **Capability composition (§5.4)**: every reasoner is bound to a `CapabilitySet`; the kernel rejects a
|
|
198
212
|
reasoner whose grant exceeds the dispatcher's — a child can never widen authority. A `SubKernelStep`
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Reasoning Kernel
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/capability-reasoning-kernel/)
|
|
4
|
+
[](https://pypi.org/project/capability-reasoning-kernel/)
|
|
5
|
+
[](https://github.com/gianlucamazza/reasoning-kernel/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/astral-sh/ruff)
|
|
8
|
+
[](https://microsoft.github.io/pyright/)
|
|
9
|
+
|
|
10
|
+
**[Live site →](https://gianlucamazza.github.io/reasoning-kernel/)**
|
|
11
|
+
|
|
3
12
|
**The problem.** An LLM agent that reads untrusted data — an email, a web page, a tool result — can be
|
|
4
13
|
hijacked by instructions hidden in that data and then act on them: leak your contacts, send mail, call
|
|
5
14
|
tools on your behalf. This is a reference implementation of an architecture where such a hijack
|
|
@@ -50,6 +59,7 @@ The trusted, deterministic kernel is the **interpreter + capability/provenance g
|
|
|
50
59
|
| Reasoner(s) | `reasoner/` (multi-provider) | a provider or the interface |
|
|
51
60
|
| Conductor | `kernel/interpreter.py` | the execution loop |
|
|
52
61
|
| Verifier | `kernel/gate.py`, `effects.py` | verification policy |
|
|
62
|
+
| Tool catalog | `tools/registry.py` | sole holder of tool callables |
|
|
53
63
|
| Memory / Trace | `memory/` | durability / audit format |
|
|
54
64
|
|
|
55
65
|
Reasoner providers: Anthropic, OpenAI, Deepseek (OpenAI-compatible, reusing the `openai` SDK via a
|
|
@@ -152,15 +162,19 @@ result = kernel.run(ctx) # RunResult(trace, committed); committed is
|
|
|
152
162
|
|
|
153
163
|
- **Provenance is multi-dimensional**: a `ProvenanceLabel` carries *origin* (`sources`), *where it may
|
|
154
164
|
flow* (`readers`), and *whose data it is* (`subjects`). Third-party data is never auto-released into a
|
|
155
|
-
WRITE — even to the requesting user — and the Q-LLM cannot launder any of these dimensions.
|
|
165
|
+
WRITE — even to the requesting user — and the Q-LLM cannot launder any of these dimensions. A tainted
|
|
166
|
+
value whose flow was never scoped (`readers=None` is reserved for purely trusted data) is likewise
|
|
167
|
+
never auto-permitted into a WRITE: it is routed to the declassifier like any other tainted flow.
|
|
156
168
|
- **Invariant A is typed**: the trusted channel is a `TrustedQuery` (text + label); `const`/inline
|
|
157
169
|
literals DERIVE their label from it, so the trust assumption is explicit rather than by convention.
|
|
158
170
|
- **Termination**: `RunLimits` bounds steps / effects / q-parses (and an optional per-call timeout); a
|
|
159
171
|
run exceeding a bound aborts closed (`RunAborted`), committing nothing further. The timeout abort is
|
|
160
172
|
prompt — it does not block waiting on the hung call (`kernel/interpreter.py:_call_reasoner`).
|
|
161
173
|
- **Reasoner failure is fail-closed**: a provider that returns no usable output (empty / refused /
|
|
162
|
-
malformed) raises `ReasonerError` (`reasoner/base.py`)
|
|
163
|
-
|
|
174
|
+
malformed) raises `ReasonerError` (`reasoner/base.py`), and a provider call that fails in transport
|
|
175
|
+
(rate limit exhausted, 5xx, network fault) raises its `TransportError` subclass; either way the
|
|
176
|
+
Conductor records a terminal trace event and commits nothing, rather than crashing or acting on a
|
|
177
|
+
partial result. Treating the model as untrusted compute
|
|
164
178
|
means a flaky reasoner can never produce a half-applied effect.
|
|
165
179
|
- **Capability composition (§5.4)**: every reasoner is bound to a `CapabilitySet`; the kernel rejects a
|
|
166
180
|
reasoner whose grant exceeds the dispatcher's — a child can never widen authority. A `SubKernelStep`
|
|
@@ -34,8 +34,8 @@ permissive policy written on top of it.
|
|
|
34
34
|
|
|
35
35
|
| Version | Supported |
|
|
36
36
|
|---------|-----------|
|
|
37
|
-
| `0.
|
|
38
|
-
| `< 0.
|
|
37
|
+
| `0.4.x` | ✅ |
|
|
38
|
+
| `< 0.4` | ❌ |
|
|
39
39
|
|
|
40
40
|
## Reporting a vulnerability
|
|
41
41
|
|
|
@@ -47,4 +47,4 @@ Please report privately — do **not** open a public issue for a suspected vulne
|
|
|
47
47
|
Include a minimal reproduction (a plan + tool/policy setup that commits an effect that should have been
|
|
48
48
|
blocked), the expected vs. actual behavior, and the affected version/commit. Expect an initial
|
|
49
49
|
acknowledgement within a few days. As a single-maintainer reference project there is no formal SLA, but
|
|
50
|
-
mechanism-level issues are taken seriously and will be addressed in a `0.
|
|
50
|
+
mechanism-level issues are taken seriously and will be addressed in a `0.4.x` patch.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Analisi del progetto e piano di miglioramento — luglio 2026
|
|
2
|
+
|
|
3
|
+
Analisi completa della codebase alla versione **0.4.1** (branch `main`, commit `0189752`),
|
|
4
|
+
condotta su tre assi: core fidato (kernel/memory/context), livello di integrazione LLM
|
|
5
|
+
(reasoner/config/tools), e qualità/DevEx (test, CI, packaging, documentazione).
|
|
6
|
+
|
|
7
|
+
## Stato di avanzamento (aggiornato 2026-07-10)
|
|
8
|
+
|
|
9
|
+
**✅ Tranche 1 (0.4.2) — completata su questo branch.** Corretti e testati: A1 (gate:
|
|
10
|
+
`readers=None` tainted → declassificazione, con regressione end-to-end), M1 (`DERIVED` fuori
|
|
11
|
+
da `_UNTRUSTED`), fast-path del gate testato + branch coverage attiva, `RK_LLM_MAX_TOKENS`
|
|
12
|
+
cablato, `model` risolto strettamente alla costruzione dei ruoli (`default_model_for` rifiuta
|
|
13
|
+
provider ignoti), `TransportError` per i guasti di trasporto Anthropic/OpenAI/Deepseek
|
|
14
|
+
(esportato dall'API pubblica), test `AnthropicProvider`, default `claude-sonnet-5`,
|
|
15
|
+
CHANGELOG/README/DEVELOPMENT aggiornati. Suite: 111 test, coverage ~94% (branch).
|
|
16
|
+
|
|
17
|
+
**✅ Consolidamento drift (stesso branch):** pre-commit riallineato a justfile/CI (ruff e
|
|
18
|
+
pyright come hook local via `uv run`, stessa versione di uv.lock e stesso scope di
|
|
19
|
+
`just typecheck`), target aggregato `just check`, `tools/` aggiunto al layout di CLAUDE.md e
|
|
20
|
+
alla role map del README.
|
|
21
|
+
|
|
22
|
+
> Nota: il passaggio a `uv sync --all-extras --locked` in `.github/workflows/ci.yml` (entrambi
|
|
23
|
+
> i job) è pronto ma NON è su questo branch: il token dell'app GitHub non ha il permesso
|
|
24
|
+
> `workflows`, quindi va applicato manualmente insieme agli altri interventi CI aperti.
|
|
25
|
+
|
|
26
|
+
**⬜ Ancora aperti:** tranche 0.5.0 (RunLimits globali sui sub-kernel — M3/1.3,
|
|
27
|
+
`ToolExecutionError` + evento di trace, fallback strict-schema robusto — 1.1, trace writer
|
|
28
|
+
per-run — M4, export `LLMResult`/`LLMUsage` — 3.1), infra release (build unica promossa +
|
|
29
|
+
gate di test/versione — 2.1/2.2, dependabot — 2.3, pinning SHA — 2.4, matrice 3.14 — 2.7,
|
|
30
|
+
`permissions` in ci.yml — 2.5, `uv build` + `twine check` — 2.9), spec/docs (semantica limits
|
|
31
|
+
— 4.3, regola "argomenti che escono ⇒ WRITE" — M2, CONFORMANCE.md — 4.4, metadati pyproject
|
|
32
|
+
— §3), e i P2 restanti (hardening prompt, test minori, audit trace).
|
|
33
|
+
|
|
34
|
+
## Stato di salute (baseline verificata)
|
|
35
|
+
|
|
36
|
+
| Check | Esito |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `ruff check` + `ruff format --check` | pulito (67 file) |
|
|
39
|
+
| `pyright` (strict su schemas/kernel/memory) | 0 errori |
|
|
40
|
+
| `pytest` (suite non-live) | 98 passed |
|
|
41
|
+
| Coverage | 92.19% (soglia 85% applicata in CI) |
|
|
42
|
+
|
|
43
|
+
L'architettura di sicurezza regge: nessun bypass strutturale del gate (il registry è
|
|
44
|
+
l'unico detentore dei callable, il dispatcher chiama `Gate.check` incondizionatamente),
|
|
45
|
+
validazione del Plan solida (id unici, ref forward-only, niente cicli), clamp monotono dei
|
|
46
|
+
grant nei sub-kernel, join delle label monotono. I finding sotto riguardano punti specifici,
|
|
47
|
+
non la topologia del pattern.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## P0 — Correttezza del gate (invariante B)
|
|
52
|
+
|
|
53
|
+
### A1. Valore tainted con `readers=None` salta la declassificazione ⚠️
|
|
54
|
+
|
|
55
|
+
`kernel/gate.py:71-77` + `kernel/taint.py:58-68` + `schemas/provenance.py:43,54-58`
|
|
56
|
+
|
|
57
|
+
`provenance.py` documenta `readers=None` = "unrestricted, **reserved for purely trusted
|
|
58
|
+
data**", ma `quarantine_label()` preserva i `readers` della sorgente: un `q_parse` su un
|
|
59
|
+
const trusted produce un valore **tainted (Q_LLM) con `readers=None`**. Nello stage 3 del
|
|
60
|
+
gate, `allows_reader()` restituisce `True` quando `readers is None` → `permitted=True`,
|
|
61
|
+
la declassificazione viene saltata.
|
|
62
|
+
|
|
63
|
+
Scenario: piano con `const → q_parse(const) → send_email(to=ref(q_parse))`. L'output del
|
|
64
|
+
Q-LLM (compute probabilistico non fidato, per spec "verification never depends on trusting
|
|
65
|
+
a probabilistic component") diventa argomento di una WRITE senza alcun vincolo
|
|
66
|
+
deterministico oltre lo schema.
|
|
67
|
+
|
|
68
|
+
**Fix (minimale, fail-closed):** in `gate.check`, per gli argomenti tainted trattare
|
|
69
|
+
`readers is None` come *non* auto-permesso e instradare a declassificazione. In alternativa
|
|
70
|
+
(più invasivo) non produrre mai label tainted con `readers=None` in `quarantine_label` /
|
|
71
|
+
`result_label`.
|
|
72
|
+
|
|
73
|
+
### M1. `DERIVED` è considerato untrusted: il merge di soli valori trusted diventa tainted
|
|
74
|
+
|
|
75
|
+
`schemas/provenance.py:34` + `kernel/taint.py:30-31`
|
|
76
|
+
|
|
77
|
+
`_UNTRUSTED = {TOOL_READ, Q_LLM, DERIVED}` e `join_labels` aggiunge `DERIVED` a ogni join
|
|
78
|
+
con >1 input: `merge(const, const)` risulta tainted anche se tutti gli input derivano dalla
|
|
79
|
+
query trusted → denial spuri nelle policy che usano `is_tainted`.
|
|
80
|
+
|
|
81
|
+
**Fix:** togliere `DERIVED` da `_UNTRUSTED`; il taint deve derivare solo dalla presenza
|
|
82
|
+
effettiva di `TOOL_READ`/`Q_LLM`. `DERIVED` resta come marcatore di audit.
|
|
83
|
+
|
|
84
|
+
> **A1 e M1 vanno corretti insieme**: oggi si mascherano a vicenda (il merge over-taintato
|
|
85
|
+
> passa comunque grazie al buco readers=None; chiudere solo A1 bloccherebbe i merge di dati
|
|
86
|
+
> trusted).
|
|
87
|
+
|
|
88
|
+
### Test mancante sul fast-path del gate
|
|
89
|
+
|
|
90
|
+
`tests/` + `gate.py:71-78` — il ramo "readers permettono → niente declassificazione" nel
|
|
91
|
+
caso positivo non ha alcun test, e la line coverage al 100% lo nasconde perché
|
|
92
|
+
`branch = true` non è abilitato in `[tool.coverage.run]`. Aggiungere il test (arg tainted
|
|
93
|
+
con readers che coprono le required_caps + declassificatore `DenyAll` mai consultato) e
|
|
94
|
+
abilitare la branch coverage.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## P1 — Robustezza e semantica da definire
|
|
99
|
+
|
|
100
|
+
### RunLimits per-interprete, non globali (amplificazione via sub-kernel)
|
|
101
|
+
|
|
102
|
+
`kernel/interpreter.py:199-207` — ogni sub-kernel riceve gli stessi `limits` ma contatori
|
|
103
|
+
azzerati: con fan-out di sub-kernel il totale di effetti scala ~`E·S^D`. Il sub-planner è
|
|
104
|
+
per costruzione prompt-injectable, quindi un blob malevolo può moltiplicare i tentativi di
|
|
105
|
+
effetto entro il grant. Né README né docstring dichiarano questa semantica.
|
|
106
|
+
**Fix:** budget condiviso (contatori globali del run radice) passato ai sub-Interpreter;
|
|
107
|
+
in ogni caso documentare la semantica scelta in `limits.py` e nel README, con un test che
|
|
108
|
+
la pinna.
|
|
109
|
+
|
|
110
|
+
### Errori di trasporto dei provider → trace senza evento terminale
|
|
111
|
+
|
|
112
|
+
`kernel/interpreter.py:53` — `_PLAN_ERRORS` non copre `anthropic.APIError` /
|
|
113
|
+
`openai.APIError` (429 esauriti, 5xx, timeout SDK): il traceback propaga e la trace
|
|
114
|
+
(promessa come audit log completo) termina senza `RunErrored`/`RunAborted`.
|
|
115
|
+
**Fix:** wrappare le eccezioni API in un `TransportError(ReasonerError)` nei provider.
|
|
116
|
+
Stesso tema per i tool callable (`interpreter.py:126-127`): un `TypeError` nel callable
|
|
117
|
+
propaga senza evento di trace — introdurre `ToolExecutionError` con evento dedicato.
|
|
118
|
+
|
|
119
|
+
### Fallback JSON-mode basato su substring del messaggio d'errore
|
|
120
|
+
|
|
121
|
+
`reasoner/openai.py:19-21` — `_is_strict_schema_error` fa match su `"response_format"` nel
|
|
122
|
+
testo dell'eccezione: fragile in entrambe le direzioni (fallback che non scatta con
|
|
123
|
+
messaggi diversi; 400 non correlati che innescano un secondo round-trip). È il meccanismo
|
|
124
|
+
che regge `DeepseekProvider`.
|
|
125
|
+
**Fix:** ispezionare i campi strutturati di `openai.BadRequestError` (`exc.param` /
|
|
126
|
+
`exc.body`), o tentare il fallback una sola volta su qualunque `BadRequestError`
|
|
127
|
+
ri-sollevando l'errore originale se fallisce anche quello.
|
|
128
|
+
|
|
129
|
+
### `model="fake"` come default sui ruoli con provider reali
|
|
130
|
+
|
|
131
|
+
`reasoner/roles.py:55,80` + `parse.py:23` — `PLLM(provider)` senza `model=` spedisce
|
|
132
|
+
`model="fake"` all'API reale → 404 a runtime lontano dal punto d'errore.
|
|
133
|
+
**Fix:** `model` keyword-only obbligatorio, o risoluzione automatica via
|
|
134
|
+
`default_model_for(provider.name)`.
|
|
135
|
+
|
|
136
|
+
### `RK_LLM_MAX_TOKENS` non ha alcun effetto
|
|
137
|
+
|
|
138
|
+
`config.py:38` vs `reasoner/parse.py:14` — `llm_max_tokens` è definito in Settings ma
|
|
139
|
+
`parse.py` usa una propria costante `DEFAULT_MAX_TOKENS = 4096`: l'override in `.env` è
|
|
140
|
+
silenziosamente ignorato. **Fix:** far leggere `settings.llm_max_tokens` (import lazy) e
|
|
141
|
+
rimuovere la costante. Collegato: `PLLM.plan()` non espone `max_tokens` e nessun provider
|
|
142
|
+
controlla `finish_reason`/`stop_reason` == troncamento (errore criptico invece di
|
|
143
|
+
`ReasonerError("output truncated")`).
|
|
144
|
+
|
|
145
|
+
### Default modello Anthropic di una generazione indietro
|
|
146
|
+
|
|
147
|
+
`config.py:32` + `.env.example:10` — `claude-sonnet-4-6` funziona ancora ma è superato:
|
|
148
|
+
aggiornare a `claude-sonnet-5` (o `claude-opus-4-8`), allineando `.env.example`. Il beta
|
|
149
|
+
header `structured-outputs-2025-11-13` in `anthropic.py:15,36` è ormai superfluo (feature
|
|
150
|
+
GA) e può essere rimosso al prossimo bump dell'SDK.
|
|
151
|
+
|
|
152
|
+
### Release workflow: artefatto non promosso e nessun gate
|
|
153
|
+
|
|
154
|
+
`.github/workflows/release.yml` — i job `testpypi` e `pypi` fanno ciascuno `uv build`:
|
|
155
|
+
l'artefatto pubblicato su PyPI non è quello validato su TestPyPI. Inoltre il publish parte
|
|
156
|
+
sul tag senza rieseguire la suite né verificare tag ↔ `project.version`.
|
|
157
|
+
**Fix:** job `build` unico + `upload/download-artifact`; step di verifica versione;
|
|
158
|
+
job di test come `needs:` dei publish. (Trusted publishing e permessi sono già corretti.)
|
|
159
|
+
|
|
160
|
+
### Stage provenance solo sulle WRITE: argomenti dei READ come canale di uscita
|
|
161
|
+
|
|
162
|
+
`kernel/gate.py:62` — un tool READ con argomenti che lasciano il sistema (es.
|
|
163
|
+
`web_fetch(url)`, `search(query)`) trasmetterebbe dati tainted senza controllo di
|
|
164
|
+
provenance. Non sfruttabile oggi (i READ della demo non hanno argomenti), ma è un gap di
|
|
165
|
+
spec per chi conforma il proprio sistema.
|
|
166
|
+
**Fix minimo:** documentare in `capability.py` e nella spec che ogni tool i cui argomenti
|
|
167
|
+
attraversano il confine DEVE essere registrato WRITE. Più robusto: flag
|
|
168
|
+
`args_leave_boundary` su `ToolSpec`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## P2 — Qualità, API, DevEx
|
|
173
|
+
|
|
174
|
+
**API pubblica**
|
|
175
|
+
- `LLMProvider` è esportato ma `LLMResult`/`LLMUsage` (necessari per implementarlo) no —
|
|
176
|
+
aggiungerli a `__all__` (`__init__.py`).
|
|
177
|
+
- `supports_structured_output` dichiarato nel Protocol ma mai consultato: usarlo o rimuoverlo.
|
|
178
|
+
- `default_model_for` (`factory.py:39`) fa fallback silenzioso al modello Anthropic per
|
|
179
|
+
provider ignoti — meglio errore esplicito.
|
|
180
|
+
- Commento in `config.py:31` elenca `"fake"` tra i provider validi ma la factory lo rifiuta
|
|
181
|
+
(scelta intenzionale): allineare il commento.
|
|
182
|
+
|
|
183
|
+
**Interpreter/trace (audit)**
|
|
184
|
+
- `Interpreter`/`TraceWriter` non rientranti: un secondo `run()` mescola gli eventi del
|
|
185
|
+
primo (trace.py:13-23) — documentare one-shot o creare il writer per run.
|
|
186
|
+
- `TraceEvent` non frozen + `snapshot()` shallow: lo "snapshot immutabile" non è
|
|
187
|
+
strutturale.
|
|
188
|
+
- `digest()` basato su `repr()`: non deterministico per payload arbitrari — usare
|
|
189
|
+
serializzazione canonica.
|
|
190
|
+
- `RunId(f"{ctx.run_id}/{step.id}")` senza escaping: uno step id con `/` rende ambiguo il
|
|
191
|
+
trace — vietare `/` negli id del Plan.
|
|
192
|
+
- `dispatch()` etichetta `produced_by="__effect__<tool>"` invece dello step id.
|
|
193
|
+
- Ogni `_call_reasoner` crea un `ThreadPoolExecutor` (thread orfani cumulativi su timeout);
|
|
194
|
+
nessun timeout sui tool callable — riusare l'executor, valutare `tool_timeout_s`.
|
|
195
|
+
|
|
196
|
+
**Hardening prompt (mitigato dall'architettura, ma a buon mercato)**
|
|
197
|
+
- `context/assembler.py:61-66` e `interpreter.py:193`: il blob non fidato non è recintato —
|
|
198
|
+
delimitatori falsificabili (`# Extraction instruction:` contraffatto). Fix: tag sentinella
|
|
199
|
+
e istruzione ripetuta dopo il blob.
|
|
200
|
+
- Il sub-planner vede il catalogo tool completo, non filtrato per grant ridotto
|
|
201
|
+
(`effects.py:55-61`): filtrare per `required_caps ⊆ grant`.
|
|
202
|
+
|
|
203
|
+
**Test mancanti (oltre al fast-path P0)**
|
|
204
|
+
- `AnthropicProvider` a coverage 0% nonostante l'injection seam esista già: replicare i
|
|
205
|
+
fake client di `test_reasoner_robustness.py`.
|
|
206
|
+
- Timeout del Q-LLM mai esercitato (solo il planner): test con provider che si blocca su
|
|
207
|
+
`parse_blob` dopo un piano valido.
|
|
208
|
+
- Literal inline in `ToolCallStep.args` con query tainted → send bloccato (variante di
|
|
209
|
+
`test_const_label_derives_from_query`).
|
|
210
|
+
- Oracolo di `test_every_committed_effect_was_gated_first` confronta solo per nome tool:
|
|
211
|
+
contare le occorrenze.
|
|
212
|
+
- `RunLimits` accetta valori negativi: `Field(ge=0)` + test.
|
|
213
|
+
|
|
214
|
+
**CI / supply chain**
|
|
215
|
+
- Aggiungere `.github/dependabot.yml` (ecosystem `github-actions` + `uv`).
|
|
216
|
+
- Pinnare a SHA le action in `ci.yml` e soprattutto `release.yml` (pages.yml lo fa già).
|
|
217
|
+
- `permissions: contents: read` in `ci.yml`; `uv sync --locked`; matrice Python + 3.14
|
|
218
|
+
(e classifier); step `uv build` + `twine check` nel job quality; `branch = true` nella
|
|
219
|
+
coverage.
|
|
220
|
+
|
|
221
|
+
**DevEx / docs**
|
|
222
|
+
- pre-commit: l'hook pyright copre solo `schemas kernel memory` mentre `just typecheck`
|
|
223
|
+
copre tutto `src/` — usare `uv run pyright`; il pin ruff `v0.9.0` diverge dal venv —
|
|
224
|
+
hook local `uv run ruff`.
|
|
225
|
+
- justfile: aggiungere target aggregato `check: lint typecheck test`.
|
|
226
|
+
- CLAUDE.md e la tabella "Role → module map" del README omettono `tools/` (che è parte
|
|
227
|
+
dell'argomento no-bypass).
|
|
228
|
+
- `RK_LLM_MAX_TOKENS` e `RK_LLM_TIMEOUT_SECONDS` assenti da DEVELOPMENT.md/.env.example.
|
|
229
|
+
- Valutare `docs/CONFORMANCE.md`: checklist verificabile per il claim "conform your own
|
|
230
|
+
system to it" (oggi la spec vive tutta nel README).
|
|
231
|
+
- pyproject: URL `Changelog`/`Documentation`, migrazione licenza a PEP 639, classifier 3.14.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Roadmap proposta
|
|
236
|
+
|
|
237
|
+
1. **0.4.2 (patch, subito):** A1 + M1 con test dedicati (fix congiunto), branch coverage,
|
|
238
|
+
fix `RK_LLM_MAX_TOKENS`, `model` obbligatorio sui ruoli, `TransportError`, test
|
|
239
|
+
AnthropicProvider, default `claude-sonnet-5`.
|
|
240
|
+
2. **0.5.0 (minor):** semantica globale dei RunLimits (breaking sulla semantica, non
|
|
241
|
+
sull'API), `ToolExecutionError` + evento di trace, fallback strict-schema robusto,
|
|
242
|
+
trace writer per-run, esportazione `LLMResult`/`LLMUsage`.
|
|
243
|
+
3. **Infra (indipendente):** release workflow a build unica con gate, dependabot, pinning
|
|
244
|
+
SHA, matrice 3.14, allineamento pre-commit, `just check`.
|
|
245
|
+
4. **Spec/docs:** semantica limits, regola "argomenti che escono ⇒ WRITE", `tools/` nel
|
|
246
|
+
role map, CONFORMANCE.md.
|
|
@@ -16,6 +16,7 @@ Python 3.12+ is required. The default suite needs no API keys: it runs against t
|
|
|
16
16
|
|
|
17
17
|
| Command | What it does |
|
|
18
18
|
|--------------------|--------------------------------------------------------------------------|
|
|
19
|
+
| `just check` | `lint` + `typecheck` + `test` — everything CI runs, in one command |
|
|
19
20
|
| `just demo` | Worked demo (FakeProvider): legit send commits; injection inert; exfil blocked |
|
|
20
21
|
| `just demo-subkernel` | §5.4 composition demo: untrusted content delegated at a reduced grant |
|
|
21
22
|
| `just demo-limits` | Termination demo: `RunLimits` aborts the run closed before the second effect |
|
|
@@ -61,16 +62,16 @@ Keys are accepted under either their conventional bare name or an `RK_`-prefixed
|
|
|
61
62
|
|
|
62
63
|
| Provider | Env var | Default model (more capable variant) |
|
|
63
64
|
|-----------|------------------------------------------|---------------------------------------------|
|
|
64
|
-
| Anthropic | `ANTHROPIC_API_KEY` / `RK_ANTHROPIC_API_KEY` | `claude-sonnet-
|
|
65
|
+
| Anthropic | `ANTHROPIC_API_KEY` / `RK_ANTHROPIC_API_KEY` | `claude-sonnet-5` (`claude-opus-4-8`) |
|
|
65
66
|
| OpenAI | `OPENAI_API_KEY` / `RK_OPENAI_API_KEY` | `gpt-5.5` (`gpt-5.5-pro`) |
|
|
66
67
|
| Deepseek | `DEEPSEEK_API_KEY` / `RK_DEEPSEEK_API_KEY` | `deepseek-v4-flash` (`deepseek-v4-pro`) |
|
|
67
68
|
|
|
68
69
|
Other overrides (defaults in `config.py`): `RK_LLM_PROVIDER_DEFAULT`, `RK_LLM_MODEL_*`,
|
|
69
|
-
`RK_DEEPSEEK_BASE_URL`, `RK_LLM_TIMEOUT_SECONDS`. A live test or demo **skips** any provider whose
|
|
70
|
+
`RK_DEEPSEEK_BASE_URL`, `RK_LLM_TIMEOUT_SECONDS`, `RK_LLM_MAX_TOKENS`. A live test or demo **skips** any provider whose
|
|
70
71
|
key is absent, so partial configuration is fine.
|
|
71
72
|
|
|
72
73
|
Each provider has a sensible default and a more capable variant (the parenthesised id above). Model
|
|
73
|
-
ids are current as of
|
|
74
|
+
ids are current as of July 2026; the defaults are the cost-effective tier, the variants the
|
|
74
75
|
frontier tier. For Deepseek the legacy `deepseek-chat` / `deepseek-reasoner` names still resolve as
|
|
75
76
|
deprecated aliases of `deepseek-v4-flash` but should not be used.
|
|
76
77
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Reasoning Kernel">
|
|
2
|
+
<rect width="64" height="64" fill="#0c0c0b"/>
|
|
3
|
+
<rect x="3.5" y="3.5" width="57" height="57" fill="none" stroke="#b45f43" stroke-width="2"/>
|
|
4
|
+
<text x="11" y="42" font-family="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
5
|
+
font-size="26" font-weight="500" fill="#b45f43">>_</text>
|
|
6
|
+
</svg>
|