entropy-sdk 0.1.5__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.
- entropy_sdk-0.1.5/LICENSE +21 -0
- entropy_sdk-0.1.5/PKG-INFO +198 -0
- entropy_sdk-0.1.5/README.md +174 -0
- entropy_sdk-0.1.5/pyproject.toml +39 -0
- entropy_sdk-0.1.5/setup.cfg +4 -0
- entropy_sdk-0.1.5/src/entropy_sdk/__init__.py +25 -0
- entropy_sdk-0.1.5/src/entropy_sdk/adapters/__init__.py +5 -0
- entropy_sdk-0.1.5/src/entropy_sdk/adapters/base.py +31 -0
- entropy_sdk-0.1.5/src/entropy_sdk/adapters/langchain.py +52 -0
- entropy_sdk-0.1.5/src/entropy_sdk/adapters/pydanticai.py +43 -0
- entropy_sdk-0.1.5/src/entropy_sdk/adapters/raw.py +38 -0
- entropy_sdk-0.1.5/src/entropy_sdk/fallback.py +45 -0
- entropy_sdk-0.1.5/src/entropy_sdk/gate.py +82 -0
- entropy_sdk-0.1.5/src/entropy_sdk/gear.py +35 -0
- entropy_sdk-0.1.5/src/entropy_sdk/policy.py +55 -0
- entropy_sdk-0.1.5/src/entropy_sdk/py.typed +0 -0
- entropy_sdk-0.1.5/src/entropy_sdk/runtime.py +278 -0
- entropy_sdk-0.1.5/src/entropy_sdk/state.py +156 -0
- entropy_sdk-0.1.5/src/entropy_sdk.egg-info/PKG-INFO +198 -0
- entropy_sdk-0.1.5/src/entropy_sdk.egg-info/SOURCES.txt +27 -0
- entropy_sdk-0.1.5/src/entropy_sdk.egg-info/dependency_links.txt +1 -0
- entropy_sdk-0.1.5/src/entropy_sdk.egg-info/requires.txt +8 -0
- entropy_sdk-0.1.5/src/entropy_sdk.egg-info/top_level.txt +1 -0
- entropy_sdk-0.1.5/tests/test_core.py +125 -0
- entropy_sdk-0.1.5/tests/test_hardening.py +259 -0
- entropy_sdk-0.1.5/tests/test_v012.py +158 -0
- entropy_sdk-0.1.5/tests/test_v013.py +89 -0
- entropy_sdk-0.1.5/tests/test_v014.py +45 -0
- entropy_sdk-0.1.5/tests/test_v015.py +104 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wang Miaosheng / CYD-PRC
|
|
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.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: entropy-sdk
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Gear-based safety control layer for autonomous agents (EntropyRuntime core, embeddable SDK)
|
|
5
|
+
Author-email: Wang Miaosheng <wmsmiaosheng@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CYD-PRC/entropy-sdk
|
|
8
|
+
Project-URL: Paper, https://arxiv.org/abs/2607.00334
|
|
9
|
+
Keywords: ai-safety,agent,governance,runtime-verification,gear
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Provides-Extra: langchain
|
|
19
|
+
Requires-Dist: langchain-core<1.0,>=0.2.43; extra == "langchain"
|
|
20
|
+
Provides-Extra: pydanticai
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# entropy-sdk
|
|
26
|
+
|
|
27
|
+
**An embeddable, gear-based safety control layer for autonomous agents** — the
|
|
28
|
+
SDK distillation of the EntropyRuntime paper's core abstractions
|
|
29
|
+
([arXiv:2607.00334](https://arxiv.org/abs/2607.00334)).
|
|
30
|
+
|
|
31
|
+
> Make an AI's degree of autonomy observable, governable, and accountable.
|
|
32
|
+
|
|
33
|
+
[](https://github.com/CYD-PRC/entropy-sdk/actions/workflows/test.yml)
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
> Test readings are reported from **raw CI logs, not badge conclusions**:
|
|
38
|
+
> latest verified reading — **96 passed / 0 skipped** across Python 3.10–3.13
|
|
39
|
+
> (Actions run inspected line-by-line). A green badge alone is not evidence.
|
|
40
|
+
|
|
41
|
+
Unlike [CYD-PRC/entropyruntime](https://github.com/CYD-PRC/entropyruntime)
|
|
42
|
+
(the full production system — FastAPI + PostgreSQL + Redis + OPA), this
|
|
43
|
+
repository is the **zero-dependency, embeddable** minimal control layer:
|
|
44
|
+
`pip install` and five lines of code wire it into any agent loop.
|
|
45
|
+
|
|
46
|
+
## Core abstractions
|
|
47
|
+
|
|
48
|
+
| Abstraction | Paper | SDK |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Five-level gear ladder G0–G4 | Definition 1 (𝒜₀⊂…⊂𝒜₄) | `Gear` |
|
|
51
|
+
| Utility gate U(s,a) ≥ θ | Definitions 2–3, Theorem 2 | `UtilityGate` |
|
|
52
|
+
| Slow-up-fast-down state machine | §4 Gear state machine | `GearPolicy` |
|
|
53
|
+
| Event-driven fallback | Theorem 4 | `FallbackConfig` |
|
|
54
|
+
| Runtime state ρ=(g,σ,ϵ) | Definition 4 | `RuntimeState` |
|
|
55
|
+
| Audit chain | §5/§8 empirical requirements | `AuditLog` (append-only JSONL) |
|
|
56
|
+
|
|
57
|
+
## Design rules
|
|
58
|
+
|
|
59
|
+
1. **Pure stdlib core, zero dependencies.** Framework adapters (LangChain /
|
|
60
|
+
PydanticAI) ship as optional extras.
|
|
61
|
+
2. **fail-closed.** The utility function must be injected explicitly; there is
|
|
62
|
+
no fail-open switch — the utility gate is the sole dispatch channel
|
|
63
|
+
(Theorem 2, enforced in code).
|
|
64
|
+
3. **Audit chain built in.** Every gate decision, gear transition, and
|
|
65
|
+
suspension is appended to JSONL. `gate_acceptance_rate()` and
|
|
66
|
+
`gear_histogram()` produce the empirical data that the paper's Theorem 1
|
|
67
|
+
assumption and Theorem 3 prediction call for.
|
|
68
|
+
|
|
69
|
+
## Quickstart
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install entropy-sdk
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from entropy_sdk import EntropyRuntime, Gear, action, observe
|
|
77
|
+
|
|
78
|
+
# 1. Inject your domain utility function (the SDK ships no built-in U)
|
|
79
|
+
def my_utility(state, act):
|
|
80
|
+
return state.task_gain(act) + 2.0 * state.safety(act) - 0.5 * act.cost
|
|
81
|
+
|
|
82
|
+
# Note: action()'s **kwargs are call parameters of the wrapped function,
|
|
83
|
+
# not utility metadata. Feed metadata to the utility via post-creation
|
|
84
|
+
# assignment (act.utility_value = ... style).
|
|
85
|
+
# 2. Create the runtime (theta is the only safety-vs-output knob)
|
|
86
|
+
runtime = EntropyRuntime(utility=my_utility, theta=0.15,
|
|
87
|
+
audit_log="audit.jsonl")
|
|
88
|
+
|
|
89
|
+
# 3. Every agent action goes through the gate
|
|
90
|
+
result = runtime.step(
|
|
91
|
+
state=my_state,
|
|
92
|
+
action=action(delete_records, "users", required_gear=Gear.EXECUTE),
|
|
93
|
+
execute=lambda a: a(),
|
|
94
|
+
propose_alternative=my_fallback_planner, # optional: rejected-action fallback
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if result.suspended:
|
|
98
|
+
alert_human() # m consecutive rejections -> suspend at G0, await review
|
|
99
|
+
runtime.resume() # after human review, gears must be re-earned
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Gear semantics
|
|
103
|
+
|
|
104
|
+
| Gear | Name | Permitted actions |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| G0 | Observe | Read-only observation, safe holding |
|
|
107
|
+
| G1 | Suggest | Side-effect-free candidate plans |
|
|
108
|
+
| G2 | Plan | Bounded, reversible recovery actions |
|
|
109
|
+
| G3 | Execute | Independently chosen side-effecting actions |
|
|
110
|
+
| G4 | Integrate | System-level coordination (an emergent property of all-G3 fleets) |
|
|
111
|
+
|
|
112
|
+
**Slow up, fast down** (earned autonomy): escalation requires σ < σ_low **and**
|
|
113
|
+
h consecutive clean cycles — every gear must be re-earned (v0.1.2+);
|
|
114
|
+
de-escalation on σ overflow or error is immediate. Autonomy is earned, and it
|
|
115
|
+
can always be revoked.
|
|
116
|
+
|
|
117
|
+
A runnable version of this narrative lives in
|
|
118
|
+
[`examples/lifecycle_demo.py`](examples/lifecycle_demo.py)
|
|
119
|
+
(`python examples/lifecycle_demo.py`).
|
|
120
|
+
|
|
121
|
+
## Framework adapters
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
# LangChain: pip install entropy-sdk[langchain]
|
|
125
|
+
from entropy_sdk.adapters.langchain import gated_tool
|
|
126
|
+
safe_tool = gated_tool(runtime, my_tool, required_gear=Gear.EXECUTE)
|
|
127
|
+
|
|
128
|
+
# PydanticAI-compatible callable decorator: pip install entropy-sdk[pydanticai]
|
|
129
|
+
from entropy_sdk.adapters.pydanticai import gated
|
|
130
|
+
|
|
131
|
+
@gated(runtime, required_gear=Gear.EXECUTE)
|
|
132
|
+
def delete_records(table: str) -> str: ...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
On rejection the adapters **return an explanatory string instead of raising** —
|
|
136
|
+
rejection itself is feedback the agent can act on next turn.
|
|
137
|
+
|
|
138
|
+
(Note on naming: this is a **PydanticAI-compatible callable decorator**, not a
|
|
139
|
+
true PydanticAI adapter — it is a pure stdlib decorator with zero framework
|
|
140
|
+
dependencies (the `pydanticai` extra list is intentionally empty, FIX-8) and has
|
|
141
|
+
**not been validated against a real PydanticAI runtime**. Just
|
|
142
|
+
`from entropy_sdk.adapters.pydanticai import gated`.)
|
|
143
|
+
|
|
144
|
+
(LangChain boundary: `args_schema` passthrough only works for real
|
|
145
|
+
StructuredTool instances carrying a schema; bare tool objects (func only, no
|
|
146
|
+
args_schema) remain limited by the wrapper's `*args/**kwargs` signature.)
|
|
147
|
+
|
|
148
|
+
## Threat model boundaries (paper armor — read before deploying)
|
|
149
|
+
|
|
150
|
+
1. **The gate is the sole dispatch channel — only inside execution paths the
|
|
151
|
+
SDK controls.** A caller holding the raw callable can bypass the gate
|
|
152
|
+
(`tool.func(...)` skips it); the SDK governs calls that go through
|
|
153
|
+
`runtime.step`, not references in the caller's hands.
|
|
154
|
+
2. **`required_gear` is a caller-side attestation contract.** The SDK trusts
|
|
155
|
+
the label's truthfulness; whether labels may be trusted (who is allowed to
|
|
156
|
+
tag an action's gear) is the deployer's responsibility — the SDK
|
|
157
|
+
fail-closes on *invalid* values, but is not responsible for
|
|
158
|
+
under-tagged powerful actions.
|
|
159
|
+
3. **The gate governs invocation, not transactions.** Side effects that
|
|
160
|
+
already happened inside `execute` are not rolled back — the semantics are
|
|
161
|
+
invocation control, not atomicity. Implement compensation in your own
|
|
162
|
+
`execute` if you need transactional behavior.
|
|
163
|
+
4. **`resume()` does not clear σ** (design semantics, not a bug): human
|
|
164
|
+
review ≠ instant restoration of trust — σ persists after resuming, and
|
|
165
|
+
gears must be re-earned through clean cycles.
|
|
166
|
+
5. **`except Exception` does not cover `BaseException`**: if a
|
|
167
|
+
proposer/execute callback raises SystemExit/KeyboardInterrupt, the state
|
|
168
|
+
machine can still desynchronize — this is standard Python practice;
|
|
169
|
+
callers must not raise BaseException inside callbacks.
|
|
170
|
+
6. **Concurrency**: the runtime is lock-free. Under current CPython (GIL),
|
|
171
|
+
8000/8000 cycles measured with no lost updates; on free-threaded Python
|
|
172
|
+
(3.13t+), the read-modify-write of cycle/σ can lose updates — serialize
|
|
173
|
+
externally when using multiple threads.
|
|
174
|
+
7. **A deleted audit file reads as empty** (fail-open read path): in file
|
|
175
|
+
mode, `entries`/metrics return empty results for a missing chain file
|
|
176
|
+
rather than raising — asymmetric with the fail-closed write path. Deployers
|
|
177
|
+
should monitor the audit file's existence (a missing append-only chain file
|
|
178
|
+
is itself an event).
|
|
179
|
+
|
|
180
|
+
## Empirical data API
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
runtime.audit.gate_acceptance_rate() # gate acceptance rate (Theorem 1, p1≥p3)
|
|
184
|
+
runtime.audit.gear_histogram() # gear transition histogram (Theorem 3)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Every run produces data for the framework's empirical validation — a unique
|
|
188
|
+
value of the SDK relative to the full system.
|
|
189
|
+
|
|
190
|
+
## Changelog & security record
|
|
191
|
+
|
|
192
|
+
- [CHANGELOG.md](CHANGELOG.md) — versioned changes, including behavior-change
|
|
193
|
+
and compatibility notes.
|
|
194
|
+
- 中文文档:[README.zh-CN.md](README.zh-CN.md)
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT © Wang Miaosheng (ORCID: 0009-0003-2767-2421) — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# entropy-sdk
|
|
2
|
+
|
|
3
|
+
**An embeddable, gear-based safety control layer for autonomous agents** — the
|
|
4
|
+
SDK distillation of the EntropyRuntime paper's core abstractions
|
|
5
|
+
([arXiv:2607.00334](https://arxiv.org/abs/2607.00334)).
|
|
6
|
+
|
|
7
|
+
> Make an AI's degree of autonomy observable, governable, and accountable.
|
|
8
|
+
|
|
9
|
+
[](https://github.com/CYD-PRC/entropy-sdk/actions/workflows/test.yml)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
> Test readings are reported from **raw CI logs, not badge conclusions**:
|
|
14
|
+
> latest verified reading — **96 passed / 0 skipped** across Python 3.10–3.13
|
|
15
|
+
> (Actions run inspected line-by-line). A green badge alone is not evidence.
|
|
16
|
+
|
|
17
|
+
Unlike [CYD-PRC/entropyruntime](https://github.com/CYD-PRC/entropyruntime)
|
|
18
|
+
(the full production system — FastAPI + PostgreSQL + Redis + OPA), this
|
|
19
|
+
repository is the **zero-dependency, embeddable** minimal control layer:
|
|
20
|
+
`pip install` and five lines of code wire it into any agent loop.
|
|
21
|
+
|
|
22
|
+
## Core abstractions
|
|
23
|
+
|
|
24
|
+
| Abstraction | Paper | SDK |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Five-level gear ladder G0–G4 | Definition 1 (𝒜₀⊂…⊂𝒜₄) | `Gear` |
|
|
27
|
+
| Utility gate U(s,a) ≥ θ | Definitions 2–3, Theorem 2 | `UtilityGate` |
|
|
28
|
+
| Slow-up-fast-down state machine | §4 Gear state machine | `GearPolicy` |
|
|
29
|
+
| Event-driven fallback | Theorem 4 | `FallbackConfig` |
|
|
30
|
+
| Runtime state ρ=(g,σ,ϵ) | Definition 4 | `RuntimeState` |
|
|
31
|
+
| Audit chain | §5/§8 empirical requirements | `AuditLog` (append-only JSONL) |
|
|
32
|
+
|
|
33
|
+
## Design rules
|
|
34
|
+
|
|
35
|
+
1. **Pure stdlib core, zero dependencies.** Framework adapters (LangChain /
|
|
36
|
+
PydanticAI) ship as optional extras.
|
|
37
|
+
2. **fail-closed.** The utility function must be injected explicitly; there is
|
|
38
|
+
no fail-open switch — the utility gate is the sole dispatch channel
|
|
39
|
+
(Theorem 2, enforced in code).
|
|
40
|
+
3. **Audit chain built in.** Every gate decision, gear transition, and
|
|
41
|
+
suspension is appended to JSONL. `gate_acceptance_rate()` and
|
|
42
|
+
`gear_histogram()` produce the empirical data that the paper's Theorem 1
|
|
43
|
+
assumption and Theorem 3 prediction call for.
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install entropy-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from entropy_sdk import EntropyRuntime, Gear, action, observe
|
|
53
|
+
|
|
54
|
+
# 1. Inject your domain utility function (the SDK ships no built-in U)
|
|
55
|
+
def my_utility(state, act):
|
|
56
|
+
return state.task_gain(act) + 2.0 * state.safety(act) - 0.5 * act.cost
|
|
57
|
+
|
|
58
|
+
# Note: action()'s **kwargs are call parameters of the wrapped function,
|
|
59
|
+
# not utility metadata. Feed metadata to the utility via post-creation
|
|
60
|
+
# assignment (act.utility_value = ... style).
|
|
61
|
+
# 2. Create the runtime (theta is the only safety-vs-output knob)
|
|
62
|
+
runtime = EntropyRuntime(utility=my_utility, theta=0.15,
|
|
63
|
+
audit_log="audit.jsonl")
|
|
64
|
+
|
|
65
|
+
# 3. Every agent action goes through the gate
|
|
66
|
+
result = runtime.step(
|
|
67
|
+
state=my_state,
|
|
68
|
+
action=action(delete_records, "users", required_gear=Gear.EXECUTE),
|
|
69
|
+
execute=lambda a: a(),
|
|
70
|
+
propose_alternative=my_fallback_planner, # optional: rejected-action fallback
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if result.suspended:
|
|
74
|
+
alert_human() # m consecutive rejections -> suspend at G0, await review
|
|
75
|
+
runtime.resume() # after human review, gears must be re-earned
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Gear semantics
|
|
79
|
+
|
|
80
|
+
| Gear | Name | Permitted actions |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| G0 | Observe | Read-only observation, safe holding |
|
|
83
|
+
| G1 | Suggest | Side-effect-free candidate plans |
|
|
84
|
+
| G2 | Plan | Bounded, reversible recovery actions |
|
|
85
|
+
| G3 | Execute | Independently chosen side-effecting actions |
|
|
86
|
+
| G4 | Integrate | System-level coordination (an emergent property of all-G3 fleets) |
|
|
87
|
+
|
|
88
|
+
**Slow up, fast down** (earned autonomy): escalation requires σ < σ_low **and**
|
|
89
|
+
h consecutive clean cycles — every gear must be re-earned (v0.1.2+);
|
|
90
|
+
de-escalation on σ overflow or error is immediate. Autonomy is earned, and it
|
|
91
|
+
can always be revoked.
|
|
92
|
+
|
|
93
|
+
A runnable version of this narrative lives in
|
|
94
|
+
[`examples/lifecycle_demo.py`](examples/lifecycle_demo.py)
|
|
95
|
+
(`python examples/lifecycle_demo.py`).
|
|
96
|
+
|
|
97
|
+
## Framework adapters
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# LangChain: pip install entropy-sdk[langchain]
|
|
101
|
+
from entropy_sdk.adapters.langchain import gated_tool
|
|
102
|
+
safe_tool = gated_tool(runtime, my_tool, required_gear=Gear.EXECUTE)
|
|
103
|
+
|
|
104
|
+
# PydanticAI-compatible callable decorator: pip install entropy-sdk[pydanticai]
|
|
105
|
+
from entropy_sdk.adapters.pydanticai import gated
|
|
106
|
+
|
|
107
|
+
@gated(runtime, required_gear=Gear.EXECUTE)
|
|
108
|
+
def delete_records(table: str) -> str: ...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
On rejection the adapters **return an explanatory string instead of raising** —
|
|
112
|
+
rejection itself is feedback the agent can act on next turn.
|
|
113
|
+
|
|
114
|
+
(Note on naming: this is a **PydanticAI-compatible callable decorator**, not a
|
|
115
|
+
true PydanticAI adapter — it is a pure stdlib decorator with zero framework
|
|
116
|
+
dependencies (the `pydanticai` extra list is intentionally empty, FIX-8) and has
|
|
117
|
+
**not been validated against a real PydanticAI runtime**. Just
|
|
118
|
+
`from entropy_sdk.adapters.pydanticai import gated`.)
|
|
119
|
+
|
|
120
|
+
(LangChain boundary: `args_schema` passthrough only works for real
|
|
121
|
+
StructuredTool instances carrying a schema; bare tool objects (func only, no
|
|
122
|
+
args_schema) remain limited by the wrapper's `*args/**kwargs` signature.)
|
|
123
|
+
|
|
124
|
+
## Threat model boundaries (paper armor — read before deploying)
|
|
125
|
+
|
|
126
|
+
1. **The gate is the sole dispatch channel — only inside execution paths the
|
|
127
|
+
SDK controls.** A caller holding the raw callable can bypass the gate
|
|
128
|
+
(`tool.func(...)` skips it); the SDK governs calls that go through
|
|
129
|
+
`runtime.step`, not references in the caller's hands.
|
|
130
|
+
2. **`required_gear` is a caller-side attestation contract.** The SDK trusts
|
|
131
|
+
the label's truthfulness; whether labels may be trusted (who is allowed to
|
|
132
|
+
tag an action's gear) is the deployer's responsibility — the SDK
|
|
133
|
+
fail-closes on *invalid* values, but is not responsible for
|
|
134
|
+
under-tagged powerful actions.
|
|
135
|
+
3. **The gate governs invocation, not transactions.** Side effects that
|
|
136
|
+
already happened inside `execute` are not rolled back — the semantics are
|
|
137
|
+
invocation control, not atomicity. Implement compensation in your own
|
|
138
|
+
`execute` if you need transactional behavior.
|
|
139
|
+
4. **`resume()` does not clear σ** (design semantics, not a bug): human
|
|
140
|
+
review ≠ instant restoration of trust — σ persists after resuming, and
|
|
141
|
+
gears must be re-earned through clean cycles.
|
|
142
|
+
5. **`except Exception` does not cover `BaseException`**: if a
|
|
143
|
+
proposer/execute callback raises SystemExit/KeyboardInterrupt, the state
|
|
144
|
+
machine can still desynchronize — this is standard Python practice;
|
|
145
|
+
callers must not raise BaseException inside callbacks.
|
|
146
|
+
6. **Concurrency**: the runtime is lock-free. Under current CPython (GIL),
|
|
147
|
+
8000/8000 cycles measured with no lost updates; on free-threaded Python
|
|
148
|
+
(3.13t+), the read-modify-write of cycle/σ can lose updates — serialize
|
|
149
|
+
externally when using multiple threads.
|
|
150
|
+
7. **A deleted audit file reads as empty** (fail-open read path): in file
|
|
151
|
+
mode, `entries`/metrics return empty results for a missing chain file
|
|
152
|
+
rather than raising — asymmetric with the fail-closed write path. Deployers
|
|
153
|
+
should monitor the audit file's existence (a missing append-only chain file
|
|
154
|
+
is itself an event).
|
|
155
|
+
|
|
156
|
+
## Empirical data API
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
runtime.audit.gate_acceptance_rate() # gate acceptance rate (Theorem 1, p1≥p3)
|
|
160
|
+
runtime.audit.gear_histogram() # gear transition histogram (Theorem 3)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Every run produces data for the framework's empirical validation — a unique
|
|
164
|
+
value of the SDK relative to the full system.
|
|
165
|
+
|
|
166
|
+
## Changelog & security record
|
|
167
|
+
|
|
168
|
+
- [CHANGELOG.md](CHANGELOG.md) — versioned changes, including behavior-change
|
|
169
|
+
and compatibility notes.
|
|
170
|
+
- 中文文档:[README.zh-CN.md](README.zh-CN.md)
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT © Wang Miaosheng (ORCID: 0009-0003-2767-2421) — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "entropy-sdk"
|
|
7
|
+
version = "0.1.5"
|
|
8
|
+
description = "Gear-based safety control layer for autonomous agents (EntropyRuntime core, embeddable SDK)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Wang Miaosheng", email = "wmsmiaosheng@outlook.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai-safety", "agent", "governance", "runtime-verification", "gear"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
]
|
|
23
|
+
# 核心零依赖:纯 stdlib。框架适配器按需安装 extras。
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
langchain = ["langchain-core>=0.2.43,<1.0"] # 地板钉实测值:0.2.0 实测不兼容(pydantic v1 shim 校验炸),0.2.43 实测通过
|
|
28
|
+
pydanticai = [] # FIX-8:该适配器为纯 stdlib 装饰器,实际零框架依赖(名实对齐)
|
|
29
|
+
dev = ["pytest>=8"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/CYD-PRC/entropy-sdk"
|
|
33
|
+
Paper = "https://arxiv.org/abs/2607.00334"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
entropy-sdk — EntropyRuntime 控制层的可嵌入 SDK
|
|
3
|
+
|
|
4
|
+
论文同款核心抽象(arXiv:2607.00334):
|
|
5
|
+
Gear 五级档位 · Utility Gate 效用门 · 事件驱动 Fallback · append-only 审计链。
|
|
6
|
+
|
|
7
|
+
设计铁律:
|
|
8
|
+
1. 核心纯 stdlib,零依赖;
|
|
9
|
+
2. fail-closed —— 效用函数必须显式注入,门是唯一调度通道;
|
|
10
|
+
3. 每次判定自动落审计链,为经验验证生产数据。
|
|
11
|
+
"""
|
|
12
|
+
from .adapters.raw import RawAction, action, observe
|
|
13
|
+
from .fallback import FallbackConfig
|
|
14
|
+
from .gate import GateDecision, UtilityGate
|
|
15
|
+
from .gear import Gear
|
|
16
|
+
from .policy import GearPolicy
|
|
17
|
+
from .runtime import CycleResult, EntropyRuntime
|
|
18
|
+
from .state import AuditLog, RuntimeState
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.5"
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Gear", "UtilityGate", "GateDecision", "GearPolicy", "FallbackConfig",
|
|
23
|
+
"RuntimeState", "AuditLog", "EntropyRuntime", "CycleResult",
|
|
24
|
+
"RawAction", "action", "observe",
|
|
25
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""适配器基座:一个动作协议 + 一个提案协议,接任何 agent 框架。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Protocol, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from ..gear import Gear
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class GatedAction(Protocol):
|
|
11
|
+
"""被门控的动作必须声明自己需要的最低档位。
|
|
12
|
+
|
|
13
|
+
这是嵌套动作空间 A0⊂…⊂A4 的实现侧契约:
|
|
14
|
+
required_gear=G0 的动作(只读)在任何档位都允许;
|
|
15
|
+
required_gear=G3 的动作(有副作用)只在 Execute 以上放行。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
required_gear: int | Gear
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentAdapter(Protocol):
|
|
22
|
+
"""Agent 框架适配协议(对齐生产仓 interfaces/agent_adapter.py 的轻量版)。"""
|
|
23
|
+
|
|
24
|
+
def propose(self, state: Any, gear: Gear, history: list[dict]) -> GatedAction:
|
|
25
|
+
"""按当前档位生成候选动作。实现侧应遵守档位语义:
|
|
26
|
+
档位越低,生成的动作越保守。"""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def execute(self, action: GatedAction) -> Any:
|
|
30
|
+
"""真正执行。只会在门放行后被调用。"""
|
|
31
|
+
...
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""LangChain 适配:把 Tool 包成"门不过就不执行"的安全工具。
|
|
2
|
+
|
|
3
|
+
需要 ``pip install entropy-sdk[langchain]``。
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..gear import Gear
|
|
10
|
+
from ..runtime import EntropyRuntime, _safe_gear
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def gated_tool(runtime: EntropyRuntime, tool: Any, required_gear: int | Gear = Gear.EXECUTE,
|
|
14
|
+
state_fn=lambda *a, **k: None):
|
|
15
|
+
"""把一个 LangChain Tool 包装成受 EntropyRuntime 门控的工具。
|
|
16
|
+
|
|
17
|
+
门拒绝时返回拒绝说明字符串(不执行、不抛异常),
|
|
18
|
+
让 LLM 在下一轮自行调整——拒绝本身是反馈信号。
|
|
19
|
+
"""
|
|
20
|
+
# FIX5-2:与 runtime 同一严格度——构造期即拒非法 gear(3.0/"3"/True 等),
|
|
21
|
+
# 消灭「adapter 静默截断、runtime 拒绝」的两处判定不一致
|
|
22
|
+
gear = _safe_gear(required_gear)
|
|
23
|
+
if gear is None:
|
|
24
|
+
raise ValueError(f"invalid required_gear {required_gear!r} "
|
|
25
|
+
"(want Gear instance or plain int 0-4)")
|
|
26
|
+
|
|
27
|
+
def _run(*args: Any, **kwargs: Any) -> str:
|
|
28
|
+
from .raw import action # 延迟导入,避免硬依赖
|
|
29
|
+
|
|
30
|
+
# FIX-7:description 透传,效用函数可按工具身份定价
|
|
31
|
+
act = action(tool.func if hasattr(tool, "func") else tool,
|
|
32
|
+
*args, required_gear=gear,
|
|
33
|
+
description=getattr(tool, "description", "") or getattr(tool, "name", ""),
|
|
34
|
+
**kwargs)
|
|
35
|
+
result = runtime.step(state=state_fn(), action=act, execute=lambda a: a())
|
|
36
|
+
if result.executed:
|
|
37
|
+
return str(result.result)
|
|
38
|
+
if result.suspended:
|
|
39
|
+
return "[GATE] runtime suspended after repeated rejections; human review required."
|
|
40
|
+
reason = result.gate.reason if result.gate else "gear does not permit this action"
|
|
41
|
+
return f"[GATE] action rejected: {reason}. Propose a safer alternative."
|
|
42
|
+
|
|
43
|
+
from langchain_core.tools import StructuredTool # noqa: 仅在调用时需要
|
|
44
|
+
|
|
45
|
+
return StructuredTool.from_function(
|
|
46
|
+
func=_run,
|
|
47
|
+
name=f"gated_{getattr(tool, 'name', 'tool')}",
|
|
48
|
+
description=(getattr(tool, "description", "") or "")
|
|
49
|
+
+ f" [safety-gated, requires gear {gear.label}]",
|
|
50
|
+
# FIX-6:透传原工具的 args_schema——不带它,带参工具没有正常调用路径
|
|
51
|
+
args_schema=getattr(tool, "args_schema", None),
|
|
52
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""PydanticAI 适配:工具函数装饰器,门控逻辑与框架解耦。
|
|
2
|
+
|
|
3
|
+
需要 ``pip install entropy-sdk[pydanticai]``。
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
from ..gear import Gear
|
|
11
|
+
from ..runtime import EntropyRuntime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def gated(runtime: EntropyRuntime, required_gear: int | Gear = Gear.EXECUTE,
|
|
15
|
+
state_fn=lambda *a, **k: None):
|
|
16
|
+
"""装饰任意工具函数,使其经过 EntropyRuntime 效用门。
|
|
17
|
+
|
|
18
|
+
拒绝时返回说明字符串而非抛异常,让 agent 把拒绝当作反馈。
|
|
19
|
+
用法::
|
|
20
|
+
|
|
21
|
+
@gated(runtime, required_gear=Gear.EXECUTE)
|
|
22
|
+
def delete_records(table: str) -> str: ...
|
|
23
|
+
"""
|
|
24
|
+
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
25
|
+
@functools.wraps(fn)
|
|
26
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
27
|
+
from .raw import action
|
|
28
|
+
|
|
29
|
+
# FIX-7:description 透传(docstring 优先,函数名兜底),效用可按工具身份定价
|
|
30
|
+
act = action(fn, *args, required_gear=required_gear,
|
|
31
|
+
description=(fn.__doc__ or "").strip() or fn.__name__,
|
|
32
|
+
**kwargs)
|
|
33
|
+
result = runtime.step(state=state_fn(), action=act, execute=lambda a: a())
|
|
34
|
+
if result.executed:
|
|
35
|
+
return result.result
|
|
36
|
+
if result.suspended:
|
|
37
|
+
return "[GATE] runtime suspended; human review required."
|
|
38
|
+
reason = result.gate.reason if result.gate else "gear does not permit this action"
|
|
39
|
+
return f"[GATE] rejected: {reason}"
|
|
40
|
+
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
return decorator
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""零框架适配:把任意 Python callable 包装成被门控的动作。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from ..gear import Gear
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class RawAction:
|
|
12
|
+
"""最小动作包装。"""
|
|
13
|
+
|
|
14
|
+
fn: Callable[..., Any]
|
|
15
|
+
args: tuple = ()
|
|
16
|
+
kwargs: dict = field(default_factory=dict)
|
|
17
|
+
required_gear: int | Gear = Gear.EXECUTE
|
|
18
|
+
description: str = ""
|
|
19
|
+
|
|
20
|
+
def __call__(self) -> Any:
|
|
21
|
+
return self.fn(*self.args, **self.kwargs)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def action(
|
|
25
|
+
fn: Callable[..., Any],
|
|
26
|
+
*args: Any,
|
|
27
|
+
required_gear: int | Gear = Gear.EXECUTE,
|
|
28
|
+
description: str = "",
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> RawAction:
|
|
31
|
+
"""一行包装:``action(send_email, to, body, required_gear=Gear.EXECUTE)``。"""
|
|
32
|
+
return RawAction(fn=fn, args=args, kwargs=kwargs,
|
|
33
|
+
required_gear=required_gear, description=description)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def observe(fn: Callable[..., Any], *args: Any, **kwargs: Any) -> RawAction:
|
|
37
|
+
"""只读动作快捷包装:required_gear=G0,任何档位可执行。"""
|
|
38
|
+
return action(fn, *args, required_gear=Gear.OBSERVE, **kwargs)
|