entropy-sdk 0.1.5__py3-none-any.whl

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.
@@ -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,5 @@
1
+ """Agent 框架适配器。raw 零依赖;langchain/pydanticai 按需 extras 安装。"""
2
+ from .base import AgentAdapter, GatedAction
3
+ from .raw import RawAction, action, observe
4
+
5
+ __all__ = ["AgentAdapter", "GatedAction", "RawAction", "action", "observe"]
@@ -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)
@@ -0,0 +1,45 @@
1
+ """
2
+ 事件驱动 Fallback(论文 §4,Theorem 4)
3
+
4
+ 拒绝后的恢复路径:
5
+ 1. 记录拒绝,σ 上升;
6
+ 2. 由 proposer 生成备选动作 a′,重新过门;
7
+ 3. k 次备选全败 → 降一档,下周期待定;
8
+ 4. m 次连续拒绝 → 降到 G0 并挂起,等待人工复核。
9
+
10
+ Theorem 4 保证:任何错误态至多 |G|−1=4 步降到 G0,
11
+ G0 的只读动作平凡地满足 U ≥ 0 —— 系统永远有恢复路径,不需要重启。
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import math
16
+
17
+ from dataclasses import dataclass
18
+ from typing import Any, Callable
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FallbackConfig:
23
+ max_alternatives: int = 3 # k:每档最多尝试的备选数
24
+ max_consecutive_rejections: int = 5 # m:连续拒绝上限,触发 G0 挂起
25
+
26
+ def __post_init__(self):
27
+ # FIX-3b + FIX2-2 + FIX2-3:构造期校验(fail-closed)
28
+ # max_alternatives:0 合法(关闭 fallback 的显式语义,v0.1.2 恢复;
29
+ # v0.1 的 >=1 校验误杀了该意图——CHANGELOG 标注破坏性恢复);上界 100
30
+ # 防 10**9 级每周期巨大循环。两参数均须有限整数。
31
+ for name in ("max_alternatives", "max_consecutive_rejections"):
32
+ v = getattr(self, name)
33
+ if isinstance(v, bool) or not (
34
+ isinstance(v, int)
35
+ or (isinstance(v, float) and math.isfinite(v) and v.is_integer())
36
+ ):
37
+ raise ValueError(f"{name} must be a finite integer, got {v!r}")
38
+ if not 0 <= self.max_alternatives <= 100:
39
+ raise ValueError("max_alternatives must be in [0, 100] (0 = fallback off)")
40
+ if self.max_consecutive_rejections < 1:
41
+ raise ValueError("max_consecutive_rejections must be >= 1")
42
+
43
+
44
+ # 备选生成器签名:(state, rejected_action, attempt_index) -> alternative action | None
45
+ AlternativeProposer = Callable[[Any, Any, int], Any | None]
entropy_sdk/gate.py ADDED
@@ -0,0 +1,82 @@
1
+ """
2
+ Utility Gate(论文 Definitions 2–3,Theorem 2)
3
+
4
+ 门是唯一调度通道:动作被执行当且仅当 Gate=1,即 U(s,a) >= theta。
5
+ 本 SDK 不内置任何效用函数——U 必须由使用者按领域注入。
6
+ 未注入效用函数时构造即报错(fail-closed,无 fail-open 开关)。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import math
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Callable, Protocol
14
+
15
+
16
+ class UtilityFunction(Protocol):
17
+ """效用函数协议:U: S × A → R。
18
+
19
+ 推荐形态(论文 §4):U(s,a) = α·Δtask + β·safety − γ·cost,
20
+ 多智能体场景可把传感器熵项 H_i 折进 cost(论文 §6.1)。
21
+ """
22
+
23
+ def __call__(self, state: Any, action: Any) -> float: ...
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class GateDecision:
28
+ """一次门判定的完整记录(审计用)。"""
29
+
30
+ admitted: bool
31
+ utility: float
32
+ theta: float
33
+ reason: str = ""
34
+ meta: dict = field(default_factory=dict)
35
+
36
+
37
+ class UtilityGate:
38
+ """二元效用门:Gate(s,a) = 1 iff U(s,a) >= θ。"""
39
+
40
+ def __init__(self, utility: UtilityFunction | Callable[[Any, Any], float], theta: float = 0.0):
41
+ if utility is None:
42
+ raise ValueError(
43
+ "UtilityGate requires an explicit utility function. "
44
+ "There is no fail-open mode: the gate is the sole dispatch channel."
45
+ )
46
+ # FIX2-2:theta 必须有限(拒 NaN/±inf)且非负
47
+ if not math.isfinite(theta) or theta < 0:
48
+ raise ValueError("theta must be finite and >= 0 (paper Definition 3)")
49
+ self._utility = utility
50
+ self.theta = float(theta)
51
+
52
+ def evaluate(self, state: Any, action: Any) -> GateDecision:
53
+ # FIX-2:效用函数异常视同 U=−∞ 拒绝(fail-closed),不穿透 step();
54
+ # meta 载明错误类型,由 runtime 落 gate_error 审计(与 execute 异常分属)。
55
+ try:
56
+ u = float(self._utility(state, action))
57
+ except Exception as exc:
58
+ err = f"{type(exc).__name__}: {exc}"
59
+ return GateDecision(
60
+ admitted=False,
61
+ utility=float("-inf"),
62
+ theta=self.theta,
63
+ reason=f"gate_error: utility raised {err} (treated as U=-inf)",
64
+ meta={"gate_error": err},
65
+ )
66
+ # FIX4-1:非有限效用门内校验——同一把尺两个方向都闭上:
67
+ # NaN 靠 IEEE 碰巧 fail-closed,但 +inf >= θ 恒真会放行(fail-open)。
68
+ # 非有限一律拒绝,reason 载明值(审计落盘时经 FIX2-6 带符号消毒标记)。
69
+ if not math.isfinite(u):
70
+ return GateDecision(
71
+ admitted=False,
72
+ utility=u,
73
+ theta=self.theta,
74
+ reason=f"nonfinite utility: {u!r} (fail-closed)",
75
+ )
76
+ admitted = u >= self.theta
77
+ return GateDecision(
78
+ admitted=admitted,
79
+ utility=u,
80
+ theta=self.theta,
81
+ reason="" if admitted else f"U={u:.4f} < theta={self.theta:.4f}",
82
+ )
entropy_sdk/gear.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ Gear 状态系统(论文 Definition 1)
3
+
4
+ 五级执行档位,动作空间单调嵌套:A0 ⊂ A1 ⊂ A2 ⊂ A3 ⊂ A4 = A。
5
+ 档位的语义是"动作空间的上限",不是速度——降级收缩的是 agent 能做什么,
6
+ 不是做得多慢。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from enum import IntEnum
11
+
12
+
13
+ class Gear(IntEnum):
14
+ OBSERVE = 0 # G0: 只读观察 / 安全保持,无副作用
15
+ SUGGEST = 1 # G1: 生成候选计划,无外部副作用
16
+ PLAN = 2 # G2: 有界、可逆或保全性恢复动作
17
+ EXECUTE = 3 # G3: 可独立选择有副作用的动作
18
+ INTEGRATE = 4 # G4: 系统级协调(多智能体下为涌现属性,见论文 §6.3)
19
+
20
+ @property
21
+ def label(self) -> str:
22
+ return _LABELS[self]
23
+
24
+ def permits(self, required: "Gear") -> bool:
25
+ """嵌套动作空间:当前档位 g 允许所有 required_gear <= g 的动作。"""
26
+ return Gear(required) <= self
27
+
28
+
29
+ _LABELS = {
30
+ Gear.OBSERVE: "Observe",
31
+ Gear.SUGGEST: "Suggest",
32
+ Gear.PLAN: "Plan",
33
+ Gear.EXECUTE: "Execute",
34
+ Gear.INTEGRATE: "Integrate",
35
+ }
entropy_sdk/policy.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ Gear 转移策略(论文 §4 Gear state machine)
3
+
4
+ 慢升快降(earned autonomy):
5
+ - 升档:σ < σ_low 且连续 h 个干净周期 → 升一档
6
+ - 降档:σ > σ_high 或 ϵ=1 → 立即降一档
7
+ - 否则:保持
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import math
12
+
13
+ from dataclasses import dataclass
14
+
15
+ from .gear import Gear
16
+ from .state import RuntimeState
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class GearPolicy:
21
+ sigma_low: float = 0.3 # 升档门槛
22
+ sigma_high: float = 1.0 # 降档门槛
23
+ patience: int = 3 # h:升档所需连续干净周期
24
+ sigma_decay: float = 0.1 # δ:动作被接受时 σ 的衰减
25
+ sigma_step: float = 0.1 # Δσ:fallback 失败时 σ 的增量
26
+
27
+ def __post_init__(self):
28
+ # FIX-3a + FIX2-2:非法参数构造即抛(fail-closed)
29
+ # ——finite 校验:patience=NaN 会静默锁死 G0(clean_streak>=NaN 恒 False),
30
+ # patience=1.5 会静默变 2;σ 族 NaN/±inf 同理拒收
31
+ for name in ("sigma_low", "sigma_high", "sigma_decay", "sigma_step"):
32
+ v = getattr(self, name)
33
+ if not (isinstance(v, (int, float)) and math.isfinite(v)):
34
+ raise ValueError(f"{name} must be finite, got {v!r}")
35
+ if isinstance(self.patience, bool) or not (
36
+ isinstance(self.patience, int)
37
+ or (isinstance(self.patience, float) and self.patience.is_integer())
38
+ ):
39
+ raise ValueError(f"patience must be an integer, got {self.patience!r}")
40
+ if self.patience < 1:
41
+ raise ValueError("patience must be >= 1")
42
+ if self.sigma_decay < 0:
43
+ raise ValueError("sigma_decay must be >= 0")
44
+ if self.sigma_step < 0:
45
+ raise ValueError("sigma_step must be >= 0")
46
+ if not self.sigma_low < self.sigma_high:
47
+ raise ValueError("sigma_low must be < sigma_high")
48
+
49
+ def next_gear(self, state: RuntimeState) -> Gear:
50
+ g = state.gear
51
+ if state.sigma > self.sigma_high or state.error:
52
+ return Gear(max(int(g) - 1, int(Gear.OBSERVE)))
53
+ if state.sigma < self.sigma_low and state.clean_streak >= self.patience:
54
+ return Gear(min(int(g) + 1, int(Gear.INTEGRATE)))
55
+ return g
entropy_sdk/py.typed ADDED
File without changes
entropy_sdk/runtime.py ADDED
@@ -0,0 +1,278 @@
1
+ """
2
+ EntropyRuntime 控制循环(论文 §4 Algorithm 1)
3
+
4
+ 每周期四阶段:观察与档位评估 → 动作生成 → 效用门 → 执行与反馈。
5
+ 门是唯一调度通道:被拒绝的动作绝不执行(Theorem 2)。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Any, Callable
11
+
12
+ from .fallback import AlternativeProposer, FallbackConfig
13
+ from .gate import GateDecision, UtilityFunction, UtilityGate
14
+ from .gear import Gear
15
+ from .policy import GearPolicy
16
+ from .state import AuditLog, RuntimeState
17
+
18
+
19
+ def _safe_gear(value: Any) -> Gear | None:
20
+ """把调用方声明的 required_gear 收敛成合法 Gear;非法值返回 None(FIX-4)。
21
+
22
+ required_gear 是调用方声明契约(attestation)——标签不可信时
23
+ fail-closed:视同「不允许」,走档位前置闸拒绝路径,不抛栈穿透。
24
+ """
25
+ if isinstance(value, Gear):
26
+ return value
27
+ if isinstance(value, bool):
28
+ # IntEnum 值查找里 True==1 会静默成 SUGGEST——显式拒绝(已实测验证的洞)
29
+ return None
30
+ if not isinstance(value, int):
31
+ # 拒绝 float/str/None/object(含 3.9、3.7、"3"、"EXECUTE")
32
+ return None
33
+ try:
34
+ return Gear(value)
35
+ except (TypeError, ValueError, OverflowError):
36
+ # OverflowError:±inf 的 int() 转换(DEF-1 并案)
37
+ return None
38
+
39
+
40
+ @dataclass
41
+ class CycleResult:
42
+ """单周期结果。"""
43
+
44
+ executed: bool
45
+ action: Any
46
+ gate: GateDecision | None
47
+ gear_before: Gear
48
+ gear_after: Gear
49
+ used_fallback: bool = False
50
+ suspended: bool = False
51
+ result: Any = None
52
+ error: str | None = None
53
+
54
+
55
+ class EntropyRuntime:
56
+ """可嵌入的档位安全控制层。
57
+
58
+ 用法(最小闭环)::
59
+
60
+ runtime = EntropyRuntime(utility=my_utility, theta=0.15)
61
+ result = runtime.step(
62
+ state=my_state,
63
+ action=agent_proposed_action, # 必须带 required_gear
64
+ execute=lambda a: actually_do(a),
65
+ )
66
+
67
+ 动作对象只需暴露 ``required_gear``(int 或 Gear);
68
+ 可用 ``adapters.raw.action()`` 一键包装任意 callable。
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ utility: UtilityFunction,
74
+ theta: float = 0.0,
75
+ policy: GearPolicy | None = None,
76
+ fallback: FallbackConfig | None = None,
77
+ audit_log: AuditLog | str | None = None,
78
+ initial_gear: Gear = Gear.OBSERVE,
79
+ ):
80
+ self.gate = UtilityGate(utility, theta=theta)
81
+ self.policy = policy or GearPolicy()
82
+ self.fallback_cfg = fallback or FallbackConfig()
83
+ self.audit = audit_log if isinstance(audit_log, AuditLog) else AuditLog(audit_log)
84
+ # FIX3-4:initial_gear 与 required_gear 同一严格度(strict attestation)
85
+ gear0 = _safe_gear(initial_gear)
86
+ if gear0 is None:
87
+ raise ValueError(f"invalid initial_gear {initial_gear!r} "
88
+ "(want Gear instance or plain int 0-4)")
89
+ self.state = RuntimeState(gear=gear0)
90
+ self.audit.record("init", gear=int(self.state.gear), theta=theta)
91
+
92
+ # ------------------------------------------------------------------ #
93
+
94
+ def step(
95
+ self,
96
+ state: Any,
97
+ action: Any,
98
+ execute: Callable[[Any], Any],
99
+ propose_alternative: AlternativeProposer | None = None,
100
+ ) -> CycleResult:
101
+ """执行一个控制周期(Algorithm 1 的一次迭代)。"""
102
+ if self.state.suspended:
103
+ self.audit.record("suspended_skip", cycle=self.state.cycle)
104
+ return CycleResult(
105
+ executed=False, action=None, gate=None,
106
+ gear_before=self.state.gear, gear_after=self.state.gear,
107
+ suspended=True, error="suspended: awaiting human review",
108
+ )
109
+
110
+ gear_before = self.state.gear
111
+ result = self._dispatch(state, action, execute, propose_alternative)
112
+
113
+ # 更新 σ / ϵ / clean_streak(Algorithm 1, lines 7/11/13)
114
+ if result.executed and not result.used_fallback:
115
+ self.state.sigma = max(0.0, self.state.sigma - self.policy.sigma_decay)
116
+ self.state.error = False
117
+ self.state.clean_streak += 1
118
+ self.state.consecutive_rejections = 0
119
+ elif result.executed and result.used_fallback:
120
+ self.state.error = False # σ 保持不变
121
+ self.state.clean_streak += 1
122
+ self.state.consecutive_rejections = 0
123
+ else:
124
+ self.state.sigma += self.policy.sigma_step
125
+ self.state.error = True
126
+ self.state.clean_streak = 0
127
+ self.state.consecutive_rejections += 1
128
+
129
+ # 换挡(π_G)
130
+ gear_after = self.policy.next_gear(self.state)
131
+ if gear_after != self.state.gear:
132
+ self.audit.record(
133
+ "gear_transition", cycle=self.state.cycle,
134
+ **{"from": int(self.state.gear), "to": int(gear_after)},
135
+ sigma=round(self.state.sigma, 4),
136
+ )
137
+ # FIX2-4 + FIX3-2:换挡即清零 clean_streak——每一档都要重新挣满 h 个
138
+ # 连续干净周期。升降档同律:FIX2-4 原判词「拒绝分支已先清零」不覆盖
139
+ # σ 降档路径(成功执行但 σ 超阈的降档会残留 streak,窄 σ 带下提前 1 周期升档)。
140
+ if gear_after != self.state.gear:
141
+ self.state.clean_streak = 0
142
+ self.state.gear = gear_after
143
+
144
+ # m 次连续拒绝 → G0 挂起(Theorem 4 的终点)
145
+ if self.state.consecutive_rejections >= self.fallback_cfg.max_consecutive_rejections:
146
+ # FIX-5a:挂起导致的硬降档也要在链上留 gear_transition(换挡不许静默)
147
+ if self.state.gear != Gear.OBSERVE:
148
+ self.audit.record(
149
+ "gear_transition", cycle=self.state.cycle,
150
+ **{"from": int(self.state.gear), "to": int(Gear.OBSERVE)},
151
+ sigma=round(self.state.sigma, 4), reason="suspend",
152
+ )
153
+ self.state.gear = Gear.OBSERVE
154
+ self.state.suspended = True
155
+ result.suspended = True
156
+ self.audit.record("suspend", cycle=self.state.cycle,
157
+ consecutive_rejections=self.state.consecutive_rejections)
158
+
159
+ result.gear_before = gear_before
160
+ result.gear_after = self.state.gear
161
+ self.state.cycle += 1
162
+ return result
163
+
164
+ def resume(self) -> None:
165
+ """人工复核后解除挂起(从 G0 重新开始挣档位)。"""
166
+ self.state.suspended = False
167
+ self.state.consecutive_rejections = 0
168
+ self.state.error = False
169
+ self.audit.record("resume", cycle=self.state.cycle)
170
+
171
+ # ------------------------------------------------------------------ #
172
+
173
+ def _dispatch(
174
+ self,
175
+ state: Any,
176
+ action: Any,
177
+ execute: Callable[[Any], Any],
178
+ propose_alternative: AlternativeProposer | None,
179
+ ) -> CycleResult:
180
+ required = _safe_gear(getattr(action, "required_gear", Gear.EXECUTE))
181
+
182
+ # FIX-4:非法 required_gear 视同档位不允许(fail-closed),不抛栈穿透
183
+ if required is None:
184
+ self.audit.record(
185
+ "gate_decision", cycle=self.state.cycle, admitted=False,
186
+ reason=f"invalid required_gear "
187
+ f"{getattr(action, 'required_gear', None)!r} (attestation rejected)",
188
+ )
189
+ alt = self._try_alternatives(state, action, execute, propose_alternative)
190
+ if alt is not None:
191
+ return alt
192
+ return CycleResult(False, action, None, self.state.gear, self.state.gear)
193
+
194
+ # 档位前置闸:超出当前动作空间的动作连门都不进
195
+ if not self.state.gear.permits(required):
196
+ self.audit.record(
197
+ "gate_decision", cycle=self.state.cycle, admitted=False,
198
+ reason=f"gear {self.state.gear.label} does not permit {required.label}",
199
+ )
200
+ alt = self._try_alternatives(state, action, execute, propose_alternative)
201
+ if alt is not None:
202
+ return alt
203
+ return CycleResult(False, action, None, self.state.gear, self.state.gear)
204
+
205
+ decision = self.gate.evaluate(state, action)
206
+ self.audit.record(
207
+ "gate_decision", cycle=self.state.cycle, admitted=decision.admitted,
208
+ utility=round(decision.utility, 4), theta=decision.theta, reason=decision.reason,
209
+ )
210
+ # FIX-2:效用异常视同 U=−∞ 拒绝(fail-closed),审计事件与 execute 异常分属
211
+ if decision.meta.get("gate_error"):
212
+ self.audit.record("gate_error", cycle=self.state.cycle,
213
+ error=decision.meta["gate_error"])
214
+ if decision.admitted:
215
+ return self._run(action, execute, decision, used_fallback=False)
216
+
217
+ alt = self._try_alternatives(state, action, execute, propose_alternative)
218
+ if alt is not None:
219
+ return alt
220
+ return CycleResult(False, action, decision, self.state.gear, self.state.gear)
221
+
222
+ def _try_alternatives(
223
+ self,
224
+ state: Any,
225
+ rejected: Any,
226
+ execute: Callable[[Any], Any],
227
+ propose_alternative: AlternativeProposer | None,
228
+ ) -> CycleResult | None:
229
+ if propose_alternative is None or self.fallback_cfg.max_alternatives == 0:
230
+ # FIX2-3:max_alternatives=0 是合法配置——关闭 fallback,
231
+ # proposer 零调用,直接进 σ/降档/挂起流程
232
+ return None
233
+ for i in range(self.fallback_cfg.max_alternatives):
234
+ # FIX-1:proposer 异常视同无备选——记审计后走正常拒绝分支,
235
+ # 不穿透 step()(对齐 Theorem 4:拒绝路径的 σ/挂起语义照常生效)
236
+ try:
237
+ alt = propose_alternative(state, rejected, i)
238
+ except Exception as exc:
239
+ self.audit.record(
240
+ "proposer_error", cycle=self.state.cycle, attempt_index=i,
241
+ error=type(exc).__name__,
242
+ )
243
+ break
244
+ if alt is None:
245
+ break
246
+ required = _safe_gear(getattr(alt, "required_gear", Gear.EXECUTE))
247
+ if required is None: # FIX-4:非法 gear 视同不允许
248
+ self.audit.record(
249
+ "gate_decision", cycle=self.state.cycle, admitted=False,
250
+ reason=f"fallback#{i}: invalid required_gear "
251
+ f"{getattr(alt, 'required_gear', None)!r} (attestation rejected)",
252
+ )
253
+ continue
254
+ if not self.state.gear.permits(required):
255
+ continue
256
+ decision = self.gate.evaluate(state, alt)
257
+ self.audit.record(
258
+ "gate_decision", cycle=self.state.cycle, admitted=decision.admitted,
259
+ utility=round(decision.utility, 4), theta=decision.theta,
260
+ reason=f"fallback#{i}: {decision.reason}",
261
+ )
262
+ if decision.meta.get("gate_error"):
263
+ self.audit.record("gate_error", cycle=self.state.cycle,
264
+ error=decision.meta["gate_error"])
265
+ if decision.admitted:
266
+ return self._run(alt, execute, decision, used_fallback=True)
267
+ return None
268
+
269
+ def _run(self, action, execute, decision, used_fallback) -> CycleResult:
270
+ try:
271
+ out = execute(action)
272
+ self.audit.record("execute", cycle=self.state.cycle, fallback=used_fallback)
273
+ return CycleResult(True, action, decision, self.state.gear, self.state.gear,
274
+ used_fallback=used_fallback, result=out)
275
+ except Exception as exc: # 执行异常按拒绝处理:σ 上升、ϵ=1
276
+ self.audit.record("execute_error", cycle=self.state.cycle, error=str(exc))
277
+ return CycleResult(False, action, decision, self.state.gear, self.state.gear,
278
+ used_fallback=used_fallback, error=str(exc))
entropy_sdk/state.py ADDED
@@ -0,0 +1,156 @@
1
+ """
2
+ 运行时状态 ρ=(g, σ, ϵ) 与 append-only 审计链(论文 Definition 4)
3
+
4
+ 审计链是 SDK 的一等公民:每次门判定、换挡、fallback、挂起都落一条 JSONL。
5
+ 这条链就是论文 Section 5/8 所需的实证数据来源(gate 接受率、gear 转移直方图)。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import math
11
+ import time
12
+ from dataclasses import asdict, dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from .gear import Gear
17
+
18
+
19
+ @dataclass
20
+ class RuntimeState:
21
+ """单 agent 运行时状态(环境状态 s 由使用者持有,此处只保留控制量)。"""
22
+
23
+ gear: Gear = Gear.OBSERVE
24
+ sigma: float = 0.0 # σ:累积不稳定度
25
+ error: bool = False # ϵ:错误标志
26
+ cycle: int = 0 # t:离散周期计数
27
+ clean_streak: int = 0 # 连续干净周期数(升档耐心计数)
28
+ consecutive_rejections: int = 0
29
+ suspended: bool = False # m 次连续拒绝后挂起,等待人工复核
30
+
31
+ def snapshot(self) -> dict:
32
+ d = asdict(self)
33
+ d["gear"] = int(self.gear)
34
+ d["gear_label"] = self.gear.label
35
+ return d
36
+
37
+
38
+ # FIX5-1:嵌套容器内非有限 float 的递归消毒深度上限(防深递归炸栈)
39
+ _SANITIZE_MAX_DEPTH = 32
40
+
41
+
42
+ def _sanitize_nonfinite(value: Any, _depth: int = 0, _seen: set | None = None) -> Any:
43
+ """递归消毒嵌套 dict/list/tuple 里的非有限 float → 字符串标记
44
+ ("nan"/"+inf"/"-inf",与顶层记号同族)。
45
+
46
+ 防循环引用:沿当前递归路径的 id set(离开分支即摘除,故共享但不成环的
47
+ 兄弟引用不误伤);防深递归:深度超 _SANITIZE_MAX_DEPTH 截断为标记。
48
+ 非容器、非浮点类型原样透传。
49
+ """
50
+ if isinstance(value, float):
51
+ if math.isfinite(value):
52
+ return value
53
+ return "nan" if math.isnan(value) else ("+inf" if value > 0 else "-inf")
54
+ if isinstance(value, (dict, list, tuple)):
55
+ if _seen is None:
56
+ _seen = set()
57
+ if _depth >= _SANITIZE_MAX_DEPTH or id(value) in _seen:
58
+ return "<truncated:depth-or-cycle>"
59
+ _seen.add(id(value))
60
+ try:
61
+ if isinstance(value, dict):
62
+ return {k: _sanitize_nonfinite(v, _depth + 1, _seen)
63
+ for k, v in value.items()}
64
+ if isinstance(value, list):
65
+ return [_sanitize_nonfinite(v, _depth + 1, _seen) for v in value]
66
+ return tuple(_sanitize_nonfinite(v, _depth + 1, _seen) for v in value)
67
+ finally:
68
+ _seen.discard(id(value))
69
+ return value
70
+
71
+
72
+ class AuditLog:
73
+ """append-only JSONL 审计链。只追加,不修改,不落盘失败则抛错(fail-closed)。"""
74
+
75
+ def __init__(self, path: str | Path | None = None):
76
+ self.path = Path(path) if path else None
77
+ self._buffer: list[dict] = []
78
+ self.corrupt_lines: int = 0 # FIX-5c:最近一次读盘跳过的坏行数(内存模式恒 0)
79
+ # FIX2-5:文件模式读缓存——按 (mtime_ns, size) 失效。
80
+ # 选型理由:iter_entries() 生成器救不了 metrics 的重复读
81
+ #(acceptance_rate + histogram 各过一遍),缓存一次失效模型两者通吃。
82
+ self._cache_key: tuple | None = None
83
+ self._cache_entries: list[dict] = []
84
+
85
+ def record(self, kind: str, **fields: Any) -> dict:
86
+ # FIX-5d:非有限浮点(NaN/Inf)消毒为 null + 同名字段加 _nonfinite 标记,
87
+ # 保证 JSONL 对严格解析器合法(门对 NaN 效用的 fail-closed 行为不变)
88
+ clean: dict[str, Any] = {}
89
+ for k, v in fields.items():
90
+ if isinstance(v, float) and not math.isfinite(v):
91
+ clean[k] = None
92
+ # FIX2-6:消毒标记带符号(+inf / -inf / nan),保住信息区分度
93
+ clean[f"{k}_nonfinite"] = (
94
+ "nan" if math.isnan(v) else ("+inf" if v > 0 else "-inf")
95
+ )
96
+ else:
97
+ # FIX5-1:嵌套容器递归消毒(顶层既有行为与标记格式不动)
98
+ clean[k] = _sanitize_nonfinite(v)
99
+ entry = {"ts": time.time(), "kind": kind, **clean}
100
+ if self.path:
101
+ self.path.parent.mkdir(parents=True, exist_ok=True)
102
+ with self.path.open("a", encoding="utf-8") as f:
103
+ f.write(json.dumps(entry, ensure_ascii=False, default=str) + "\n")
104
+ else:
105
+ self._buffer.append(entry)
106
+ return entry
107
+
108
+ @property
109
+ def entries(self) -> list[dict]:
110
+ # FIX-5b:文件模式与 metrics 同语义——读盘返回(README 主推路径不再恒空)
111
+ # FIX2-5:经缓存(mtime+size 失效),metrics 连读不重复扫盘
112
+ # FIX3-1:对外逐条浅拷贝——调用方涂改返回值不污染缓存与磁盘真值
113
+ #(条目值无嵌套结构,浅拷贝足够;内部 metrics 继续用缓存引用)
114
+ return [dict(e) for e in self._read_all()]
115
+
116
+ def _read_all(self) -> list[dict]:
117
+ if self.path and self.path.exists():
118
+ st = self.path.stat()
119
+ key = (st.st_mtime_ns, st.st_size)
120
+ if key != self._cache_key:
121
+ entries = []
122
+ corrupt = 0
123
+ with self.path.open(encoding="utf-8") as f:
124
+ for line in f:
125
+ line = line.strip()
126
+ if not line:
127
+ continue
128
+ try:
129
+ entries.append(json.loads(line))
130
+ except json.JSONDecodeError:
131
+ # FIX-5c:坏行跳过并计数,不崩
132
+ corrupt += 1
133
+ self._cache_key = key
134
+ self._cache_entries = entries
135
+ self.corrupt_lines = corrupt
136
+ return list(self._cache_entries)
137
+ self.corrupt_lines = 0
138
+ return list(self._buffer)
139
+
140
+ def gear_histogram(self) -> dict[int, int]:
141
+ """gear 转移直方图(Theorem 3 最终镇定的经验证据)。"""
142
+ hist: dict[int, int] = {}
143
+ for e in self._iter_all():
144
+ if e.get("kind") == "gear_transition":
145
+ hist[e["to"]] = hist.get(e["to"], 0) + 1
146
+ return hist
147
+
148
+ def gate_acceptance_rate(self) -> float | None:
149
+ """门接受率(Theorem 1 关键假设 p1 >= p3 的经验观测口径)。"""
150
+ decisions = [e for e in self._iter_all() if e.get("kind") == "gate_decision"]
151
+ if not decisions:
152
+ return None
153
+ return sum(1 for e in decisions if e["admitted"]) / len(decisions)
154
+
155
+ def _iter_all(self):
156
+ yield from self._read_all()
@@ -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
+ [![test](https://github.com/CYD-PRC/entropy-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/CYD-PRC/entropy-sdk/actions/workflows/test.yml)
34
+ ![license](https://img.shields.io/badge/license-MIT-blue)
35
+ ![python](https://img.shields.io/badge/python-%3E%3D3.10-blue)
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,18 @@
1
+ entropy_sdk/__init__.py,sha256=GVGNLD7hYPV8HiemshtT9Rf7Pr1nvNiJw6DqI6rITpI,909
2
+ entropy_sdk/fallback.py,sha256=b0qq7OHj6o5EZHOJ2EJRZzFUyYQFC21RZneKG8H0S2A,1937
3
+ entropy_sdk/gate.py,sha256=j6G979OiYmcpn940IfvN_5ymxVETjAfDLvk_0lZaypg,3121
4
+ entropy_sdk/gear.py,sha256=XBBoYP4g5cev9jvxdYtSfEUMG0kuuVcCiR_AHCJ1Q3Q,1116
5
+ entropy_sdk/policy.py,sha256=Bn3BknnhXmv5aoCVb8s1VvN9wrx2ZCKQ8sSW7UUKJ0U,2242
6
+ entropy_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ entropy_sdk/runtime.py,sha256=_lnXUkIM1ME46diElxjyeNmd2oiXV8SVesQvODNIHzY,12182
8
+ entropy_sdk/state.py,sha256=tX_tmg4kCcSyIH2Da2wiRiLJBesSgZelQkkPBXPVnvs,6760
9
+ entropy_sdk/adapters/__init__.py,sha256=0GJwytV8g_S73_H-BJUyxdu1bl9mi5k5SrwDnQP6NNQ,256
10
+ entropy_sdk/adapters/base.py,sha256=bRLemADErSHEf-rN9M7L1rXJDfZsTUvvr-erMzsRNDA,1067
11
+ entropy_sdk/adapters/langchain.py,sha256=DLQdp8oBlYOe_J_HBb5V9JjOYvsh8x1T3UMaXFQ35as,2383
12
+ entropy_sdk/adapters/pydanticai.py,sha256=o3Li0oeZhj8nud5wqEIYhP91i4pXzGssEa3bWM0T7YY,1622
13
+ entropy_sdk/adapters/raw.py,sha256=oFDi7zoUqCJx4oA9Ym95kROO77JhUlyJr9dlWYcg81o,1138
14
+ entropy_sdk-0.1.5.dist-info/licenses/LICENSE,sha256=vWN2USlCU8ER_YfGPw5SdLnANPTMrifsp6BepZI9p_A,1081
15
+ entropy_sdk-0.1.5.dist-info/METADATA,sha256=J8qFtvBEqXI7uBNpzlbd2RMSiiX6BdH767r0i8LYXqw,9002
16
+ entropy_sdk-0.1.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
17
+ entropy_sdk-0.1.5.dist-info/top_level.txt,sha256=T23JbLtxHkqL56xH8SatGgs2ppiZBMz8_dahdJHyFko,12
18
+ entropy_sdk-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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 @@
1
+ entropy_sdk