deepstrike 0.2.18__tar.gz → 0.2.20__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.
Files changed (171) hide show
  1. {deepstrike-0.2.18 → deepstrike-0.2.20}/Cargo.lock +5 -5
  2. {deepstrike-0.2.18 → deepstrike-0.2.20}/Cargo.toml +2 -2
  3. {deepstrike-0.2.18 → deepstrike-0.2.20}/PKG-INFO +1 -1
  4. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/manager.rs +76 -0
  5. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/skill_catalog.rs +26 -0
  6. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/kernel.rs +42 -0
  7. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +14 -0
  8. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +141 -0
  9. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/__init__.py +10 -0
  10. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/__init__.py +23 -0
  11. deepstrike-0.2.20/deepstrike/runtime/eval.py +110 -0
  12. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/kernel_step.py +4 -0
  13. deepstrike-0.2.20/deepstrike/runtime/replay_fixture.py +52 -0
  14. deepstrike-0.2.20/deepstrike/runtime/replay_provider.py +183 -0
  15. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/runner.py +127 -13
  16. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/skills/registry.py +9 -0
  17. {deepstrike-0.2.18 → deepstrike-0.2.20}/pyproject.toml +1 -1
  18. {deepstrike-0.2.18 → deepstrike-0.2.20}/README.md +0 -0
  19. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/Cargo.toml +0 -0
  20. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/compression.rs +0 -0
  21. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/config.rs +0 -0
  22. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
  23. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/mod.rs +0 -0
  24. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/partitions.rs +0 -0
  25. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/pressure.rs +0 -0
  26. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/renderer.rs +0 -0
  27. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/renewal.rs +0 -0
  28. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/sections.rs +0 -0
  29. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
  30. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
  31. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/task_state.rs +0 -0
  32. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/text.rs +0 -0
  33. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
  34. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/audit.rs +0 -0
  35. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
  36. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/mod.rs +0 -0
  37. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/permission.rs +0 -0
  38. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
  39. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/quota.rs +0 -0
  40. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
  41. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
  42. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
  43. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/governance/veto.rs +0 -0
  44. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/harness/eval.rs +0 -0
  45. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/harness/mod.rs +0 -0
  46. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/lib.rs +0 -0
  47. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/curator.rs +0 -0
  48. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/durable.rs +0 -0
  49. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
  50. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
  51. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/mod.rs +0 -0
  52. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
  53. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
  54. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/session.rs +0 -0
  55. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
  56. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
  57. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/memory/working.rs +0 -0
  58. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/mm/handle.rs +0 -0
  59. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/mm/memory.rs +0 -0
  60. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/mm/mod.rs +0 -0
  61. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
  62. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
  63. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
  64. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
  65. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
  66. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/proc/mod.rs +0 -0
  67. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
  68. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
  69. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
  70. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
  71. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/session.rs +0 -0
  72. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
  73. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
  74. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
  75. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
  76. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
  77. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
  78. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +0 -0
  79. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +0 -0
  80. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
  81. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
  82. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
  83. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +0 -0
  84. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
  85. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/signals/attention.rs +0 -0
  86. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/signals/mod.rs +0 -0
  87. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/signals/queue.rs +0 -0
  88. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/signals/router.rs +0 -0
  89. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
  90. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/agent.rs +0 -0
  91. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/capability.rs +0 -0
  92. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/contract.rs +0 -0
  93. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/error.rs +0 -0
  94. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/message.rs +0 -0
  95. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/milestone.rs +0 -0
  96. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/mod.rs +0 -0
  97. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/model.rs +0 -0
  98. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/policy.rs +0 -0
  99. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/result.rs +0 -0
  100. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/signal.rs +0 -0
  101. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/skill.rs +0 -0
  102. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-core/src/types/task.rs +0 -0
  103. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-py/Cargo.toml +0 -0
  104. {deepstrike-0.2.18 → deepstrike-0.2.20}/crates/deepstrike-py/src/lib.rs +0 -0
  105. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/__init__.py +0 -0
  106. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/contract.py +0 -0
  107. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/handoff.py +0 -0
  108. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/harness.py +0 -0
  109. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/modes.py +0 -0
  110. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/collaboration/pool.py +0 -0
  111. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/governance.py +0 -0
  112. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/harness/__init__.py +0 -0
  113. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/harness/harness.py +0 -0
  114. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/kernel/__init__.py +0 -0
  115. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/knowledge/__init__.py +0 -0
  116. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/knowledge/source.py +0 -0
  117. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/memory/__init__.py +0 -0
  118. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/memory/agent.py +0 -0
  119. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/memory/protocols.py +0 -0
  120. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/memory/working.py +0 -0
  121. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/__init__.py +0 -0
  122. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/anthropic.py +0 -0
  123. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/base.py +0 -0
  124. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/deepseek.py +0 -0
  125. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/gemini.py +0 -0
  126. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/glm.py +0 -0
  127. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/kimi.py +0 -0
  128. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/minimax.py +0 -0
  129. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/ollama.py +0 -0
  130. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/openai.py +0 -0
  131. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/openai_responses.py +0 -0
  132. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/qwen.py +0 -0
  133. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/replay.py +0 -0
  134. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/replay_validator.py +0 -0
  135. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/providers/stream.py +0 -0
  136. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/archive.py +0 -0
  137. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/credential_vault.py +0 -0
  138. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/execution_plane.py +0 -0
  139. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/filtered_plane.py +0 -0
  140. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/kernel_event_log.py +0 -0
  141. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/large_result_spool.py +0 -0
  142. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
  143. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/os_profile.py +0 -0
  144. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/os_snapshot.py +0 -0
  145. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/output_schema.py +0 -0
  146. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/process_sandbox_plane.py +0 -0
  147. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/provider_replay.py +0 -0
  148. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/reducers.py +0 -0
  149. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/remote_vpc_plane.py +0 -0
  150. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/replay_sanitize.py +0 -0
  151. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/session_log.py +0 -0
  152. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/session_repair.py +0 -0
  153. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
  154. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/workflow_control_flow.py +0 -0
  155. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/workflow_store.py +0 -0
  156. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/runtime/worktree_plane.py +0 -0
  157. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/safety/__init__.py +0 -0
  158. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/safety/permissions.py +0 -0
  159. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/signals/__init__.py +0 -0
  160. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/signals/gateway.py +0 -0
  161. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/signals/scheduled.py +0 -0
  162. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/signals/types.py +0 -0
  163. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/skills/__init__.py +0 -0
  164. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/skills/watcher.py +0 -0
  165. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/tools/__init__.py +0 -0
  166. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/tools/builtin/__init__.py +0 -0
  167. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/tools/builtin/read_file.py +0 -0
  168. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/tools/execution.py +0 -0
  169. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/tools/registry.py +0 -0
  170. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/types/__init__.py +0 -0
  171. {deepstrike-0.2.18 → deepstrike-0.2.20}/deepstrike/types/agent.py +0 -0
@@ -194,7 +194,7 @@ dependencies = [
194
194
 
195
195
  [[package]]
196
196
  name = "deepstrike-core"
197
- version = "0.2.18"
197
+ version = "0.2.20"
198
198
  dependencies = [
199
199
  "compact_str",
200
200
  "pretty_assertions",
@@ -206,7 +206,7 @@ dependencies = [
206
206
 
207
207
  [[package]]
208
208
  name = "deepstrike-node"
209
- version = "0.2.18"
209
+ version = "0.2.20"
210
210
  dependencies = [
211
211
  "compact_str",
212
212
  "deepstrike-core",
@@ -218,7 +218,7 @@ dependencies = [
218
218
 
219
219
  [[package]]
220
220
  name = "deepstrike-py"
221
- version = "0.2.18"
221
+ version = "0.2.20"
222
222
  dependencies = [
223
223
  "compact_str",
224
224
  "deepstrike-core",
@@ -229,7 +229,7 @@ dependencies = [
229
229
 
230
230
  [[package]]
231
231
  name = "deepstrike-sdk"
232
- version = "0.2.18"
232
+ version = "0.2.20"
233
233
  dependencies = [
234
234
  "async-stream",
235
235
  "async-trait",
@@ -263,7 +263,7 @@ dependencies = [
263
263
 
264
264
  [[package]]
265
265
  name = "deepstrike-wasm"
266
- version = "0.2.18"
266
+ version = "0.2.20"
267
267
  dependencies = [
268
268
  "compact_str",
269
269
  "deepstrike-core",
@@ -3,13 +3,13 @@ resolver = "2"
3
3
  members = ["crates/deepstrike-core", "crates/deepstrike-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.18"
6
+ version = "0.2.20"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/kongusen/deepstrike"
10
10
 
11
11
  [workspace.dependencies]
12
- deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.18" }
12
+ deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.20" }
13
13
  notify = "6"
14
14
  serde = { version = "1", features = ["derive"] }
15
15
  serde_json = "1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepstrike
3
- Version: 0.2.18
3
+ Version: 0.2.20
4
4
  Requires-Dist: httpx>=0.27
5
5
  Requires-Dist: pyyaml>=6.0
6
6
  Requires-Dist: anyio>=4.0
@@ -31,6 +31,15 @@ pub struct ContextManager {
31
31
  pub sprint: u32,
32
32
  pub last_handoff: Option<HandoffArtifact>,
33
33
  pub skills: SkillCatalog,
34
+ /// P1-B tool gating: the set of skills the model has loaded this session (by name). Their
35
+ /// declared `allowed_tools` are unioned to narrow the exposed toolset in `emit_call_llm`.
36
+ /// A set (not a single value) because the model may load several skills and still needs each
37
+ /// one's tools (D1). v1 accumulates (no eviction). Snapshotted for wake/resume.
38
+ pub active_skills: std::collections::BTreeSet<CompactString>,
39
+ /// P1-B/D stable-core: tool ids that stay exposed even when a skill narrows the toolset (the
40
+ /// "everyone uses these" set — read/search/bash etc.). Configured once by the SDK; empty by
41
+ /// default (铁律: no config ⇒ skills narrow to exactly their declared tools + meta-tools).
42
+ pub stable_core_tools: std::collections::HashSet<CompactString>,
34
43
  pub capabilities: CapabilityManifest,
35
44
  pub sections: ContextSectionRegistry,
36
45
  pub memory_enabled: bool,
@@ -82,6 +91,8 @@ impl ContextManager {
82
91
  partitions, max_tokens, config, engine,
83
92
  sprint: 0, last_handoff: None,
84
93
  skills: SkillCatalog::new(),
94
+ active_skills: std::collections::BTreeSet::new(),
95
+ stable_core_tools: std::collections::HashSet::new(),
85
96
  capabilities: CapabilityManifest::new(),
86
97
  sections: ContextSectionRegistry::default_agent_sections(),
87
98
  memory_enabled: false, knowledge_enabled: false, plan_tool_enabled: false,
@@ -487,6 +498,38 @@ impl ContextManager {
487
498
  self.skills.set_available(skills);
488
499
  }
489
500
 
501
+ /// P1-B/D: set the stable-core tool ids (always exposed under skill gating). Replaces any prior.
502
+ pub fn set_stable_core_tools(&mut self, ids: impl IntoIterator<Item = CompactString>) {
503
+ self.stable_core_tools = ids.into_iter().collect();
504
+ }
505
+
506
+ /// P1-B: record that the model has loaded a skill (its content is now in context). Returns
507
+ /// `true` if this changed the active set — an epoch boundary the SDK can use to re-anchor the
508
+ /// prompt cache (D). No-op (returns false) when the skill was already active.
509
+ pub fn activate_skill(&mut self, name: impl Into<CompactString>) -> bool {
510
+ self.active_skills.insert(name.into())
511
+ }
512
+
513
+ /// P1-B: the tool-id allow-set to narrow the exposed toolset to, given the active skills.
514
+ /// Returns `None` ⇒ **do not narrow** (no skill active, or some active skill declares no
515
+ /// `allowed_tools` ⇒ unbounded, errs-open per D3). `Some(set)` ⇒ narrow to `set` (the union of
516
+ /// every active skill's declared tools). Meta-tools and stable-core are layered on in
517
+ /// `emit_call_llm`, not here.
518
+ pub fn active_skill_tool_filter(&self) -> Option<std::collections::HashSet<CompactString>> {
519
+ if self.active_skills.is_empty() {
520
+ return None;
521
+ }
522
+ let mut union = std::collections::HashSet::new();
523
+ for name in &self.active_skills {
524
+ let declared = self.skills.allowed_tools(name);
525
+ if declared.is_empty() {
526
+ return None; // an unrestricted active skill ⇒ no narrowing (D3)
527
+ }
528
+ union.extend(declared.iter().cloned());
529
+ }
530
+ Some(union)
531
+ }
532
+
490
533
  pub fn skill_tool_schema(&self) -> Option<ToolSchema> {
491
534
  self.skills.build_tool_schema()
492
535
  }
@@ -783,6 +826,39 @@ mod tests {
783
826
  assert!(mgr.sections.get("capabilities.inventory").is_some());
784
827
  }
785
828
 
829
+ #[test]
830
+ fn b1_active_skill_state_and_tool_filter() {
831
+ let mut mgr = ContextManager::new(1_000);
832
+ let mut debug = SkillMetadata::new("debug", "Debug helper");
833
+ debug.allowed_tools = vec![CompactString::new("read"), CompactString::new("grep")];
834
+ let mut review = SkillMetadata::new("review", "Reviewer");
835
+ review.allowed_tools = vec![CompactString::new("git_diff")];
836
+ let plain = SkillMetadata::new("plain", "No tools declared"); // empty allowed_tools
837
+ mgr.set_available_skills(vec![debug, review, plain]);
838
+
839
+ // No active skill ⇒ no narrowing.
840
+ assert!(mgr.active_skill_tool_filter().is_none());
841
+
842
+ // Activating returns the epoch-boundary changed flag.
843
+ assert!(mgr.activate_skill("debug"));
844
+ assert!(!mgr.activate_skill("debug")); // already active ⇒ no change
845
+
846
+ // One restricted skill ⇒ narrow to its tools.
847
+ let f = mgr.active_skill_tool_filter().unwrap();
848
+ assert_eq!(f.len(), 2);
849
+ assert!(f.contains(&CompactString::new("read")) && f.contains(&CompactString::new("grep")));
850
+
851
+ // Second restricted skill ⇒ union (D1).
852
+ mgr.activate_skill("review");
853
+ let f = mgr.active_skill_tool_filter().unwrap();
854
+ assert_eq!(f.len(), 3);
855
+ assert!(f.contains(&CompactString::new("git_diff")));
856
+
857
+ // An active skill with NO declared tools ⇒ unbounded ⇒ do not narrow (D3, errs-open).
858
+ mgr.activate_skill("plain");
859
+ assert!(mgr.active_skill_tool_filter().is_none());
860
+ }
861
+
786
862
  #[test]
787
863
  fn snapshot_hint_changes_when_capabilities_change() {
788
864
  let mut mgr = ContextManager::new(1_000);
@@ -47,6 +47,16 @@ impl SkillCatalog {
47
47
  self.available.len()
48
48
  }
49
49
 
50
+ /// P1-B tool gating: the tool ids the named skill declares it needs. Empty when the skill is
51
+ /// unknown or declares none (⇒ that skill does not narrow the toolset). The kernel unions these
52
+ /// across the active-skill set in `emit_call_llm`.
53
+ pub fn allowed_tools(&self, name: &str) -> &[CompactString] {
54
+ self.available
55
+ .get(name)
56
+ .map(|s| s.allowed_tools.as_slice())
57
+ .unwrap_or(&[])
58
+ }
59
+
50
60
  pub fn is_empty(&self) -> bool {
51
61
  self.available.is_empty()
52
62
  }
@@ -152,4 +162,20 @@ mod tests {
152
162
  assert_eq!(catalog.available_count(), 1);
153
163
  assert!(!catalog.is_empty());
154
164
  }
165
+
166
+ #[test]
167
+ fn allowed_tools_round_trip_through_catalog() {
168
+ // P1-B B0: a skill's declared `allowed_tools` survives registration and is looked up by name.
169
+ let mut skill = SkillMetadata::new("debug", "Debug helper");
170
+ skill.allowed_tools = vec![CompactString::new("read"), CompactString::new("grep")];
171
+ let mut catalog = SkillCatalog::new();
172
+ catalog.set_available(vec![skill]);
173
+
174
+ let tools = catalog.allowed_tools("debug");
175
+ assert_eq!(tools.len(), 2);
176
+ assert!(tools.iter().any(|t| t == "read"));
177
+ assert!(tools.iter().any(|t| t == "grep"));
178
+ // Unknown skill / skill with no declaration ⇒ empty (no narrowing).
179
+ assert!(catalog.allowed_tools("missing").is_empty());
180
+ }
155
181
  }
@@ -115,6 +115,17 @@ pub enum KernelInputEvent {
115
115
  SetAvailableSkills {
116
116
  skills: Vec<SkillMetadata>,
117
117
  },
118
+ /// P1-B tool gating: the model loaded a skill (`name`). The SDK emits this when it resolves a
119
+ /// `skill` tool call. The kernel records it in the active-skill set and resolves the skill's
120
+ /// `allowed_tools` from the catalog to narrow the toolset on subsequent turns.
121
+ SkillActivated {
122
+ name: String,
123
+ },
124
+ /// P1-B/D: configure the stable-core tool ids (always exposed under skill gating). Set once by
125
+ /// the SDK; empty/absent ⇒ skills narrow to exactly their declared tools + meta-tools.
126
+ SetStableCoreTools {
127
+ tool_ids: Vec<String>,
128
+ },
118
129
  SetMemoryEnabled {
119
130
  enabled: bool,
120
131
  },
@@ -662,6 +673,16 @@ impl KernelRuntime {
662
673
  self.sm.ctx.set_available_skills(skills);
663
674
  return KernelStep::empty(self.sm.take_observations());
664
675
  }
676
+ KernelInputEvent::SkillActivated { name } => {
677
+ // B1: record the activation (B2 reads it in emit_call_llm to narrow tools).
678
+ // The returned `changed` flag is the epoch boundary for D's cache re-anchor.
679
+ self.sm.ctx.activate_skill(name);
680
+ return KernelStep::empty(self.sm.take_observations());
681
+ }
682
+ KernelInputEvent::SetStableCoreTools { tool_ids } => {
683
+ self.sm.ctx.set_stable_core_tools(tool_ids.into_iter().map(Into::into));
684
+ return KernelStep::empty(self.sm.take_observations());
685
+ }
665
686
  KernelInputEvent::SetMemoryEnabled { enabled } => {
666
687
  self.sm.ctx.set_memory_enabled(enabled);
667
688
  return KernelStep::empty(self.sm.take_observations());
@@ -1081,6 +1102,27 @@ mod tests {
1081
1102
  assert_eq!(runtime.state_machine().tools.len(), 1);
1082
1103
  }
1083
1104
 
1105
+ #[test]
1106
+ fn skill_activated_input_records_active_skill() {
1107
+ // P1-B B1: the SkillActivated event (serde `skill_activated`) records the active skill and,
1108
+ // via the catalog's declared tools, yields a narrowing filter — without itself acting.
1109
+ let mut runtime = KernelRuntime::new(LoopPolicy::default());
1110
+ let mut debug = SkillMetadata::new("debug", "Debug helper");
1111
+ debug.allowed_tools = vec!["read".into(), "grep".into()];
1112
+ runtime.step(KernelInput::new(KernelInputEvent::SetAvailableSkills {
1113
+ skills: vec![debug],
1114
+ }));
1115
+
1116
+ let step = runtime.step(KernelInput::new(KernelInputEvent::SkillActivated {
1117
+ name: "debug".to_string(),
1118
+ }));
1119
+
1120
+ assert!(step.actions.is_empty(), "activation is config, not an action");
1121
+ assert!(runtime.state_machine().ctx.active_skills.contains("debug"));
1122
+ let filter = runtime.state_machine().ctx.active_skill_tool_filter().unwrap();
1123
+ assert_eq!(filter.len(), 2);
1124
+ }
1125
+
1084
1126
  #[test]
1085
1127
  fn update_task_input_mutates_task_state() {
1086
1128
  let mut runtime = KernelRuntime::new(LoopPolicy::default());
@@ -810,6 +810,20 @@ impl LoopStateMachine {
810
810
  });
811
811
  }
812
812
 
813
+ // P1-B epoch skill gating (applied *after* the run-level filter ③, so A is the outer bound
814
+ // and B narrows within it — D6). When skills are active and declare tools, expose only
815
+ // `meta-tools ∪ stable-core ∪ ⋃(active skills' allowed_tools)`. `None` ⇒ no active/declared
816
+ // skill ⇒ no narrowing (D3, errs-open). Meta-tools are always exempt (D5) so the model can
817
+ // still load more skills. Byte-stable within an epoch: the set only changes on activation.
818
+ if let Some(allowed) = self.ctx.active_skill_tool_filter() {
819
+ let stable = &self.ctx.stable_core_tools;
820
+ tools.retain(|tool| {
821
+ matches!(tool.name.as_str(), "skill" | "memory" | "knowledge" | "update_plan")
822
+ || stable.contains(&tool.name)
823
+ || allowed.contains(&tool.name)
824
+ });
825
+ }
826
+
813
827
  LoopAction::CallLLM { context, tools }
814
828
  }
815
829
 
@@ -306,6 +306,84 @@
306
306
  }
307
307
  }
308
308
 
309
+ /// P1-B B2: once a skill with declared `allowed_tools` is active, `emit_call_llm` exposes only
310
+ /// `meta-tools ∪ stable-core ∪ allowed_tools`. Meta-tools stay (D5); undeclared/inactive = full.
311
+ #[test]
312
+ fn active_skill_gates_exposed_tools_with_stable_core() {
313
+ let mut sm = sm();
314
+ sm.tools = vec![
315
+ make_tool_schema("read"),
316
+ make_tool_schema("write"),
317
+ make_tool_schema("bash"),
318
+ make_tool_schema("grep"),
319
+ ];
320
+ // A skill that declares only {read, grep}; stable-core keeps {bash}; `write` is gated out.
321
+ let mut debug = SkillMetadata::new("debug", "Debug helper");
322
+ debug.allowed_tools = vec![compact_str::CompactString::new("read"), compact_str::CompactString::new("grep")];
323
+ sm.ctx.set_available_skills(vec![debug]);
324
+ sm.ctx.set_stable_core_tools([compact_str::CompactString::new("bash")]);
325
+
326
+ // Before activation: all tools exposed (no narrowing).
327
+ match sm.start(RuntimeTask::new("go")) {
328
+ LoopAction::CallLLM { tools, .. } => {
329
+ let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
330
+ assert!(["read", "write", "bash", "grep"].iter().all(|n| names.contains(n)));
331
+ // skill meta-tool present (skills registered) and must survive gating later.
332
+ assert!(names.contains(&SKILL_TOOL_NAME));
333
+ }
334
+ _ => panic!("expected CallLLM"),
335
+ }
336
+
337
+ // Activate the skill → next emit narrows.
338
+ sm.ctx.activate_skill("debug");
339
+ match sm.emit_call_llm() {
340
+ LoopAction::CallLLM { tools, .. } => {
341
+ let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
342
+ assert!(names.contains(&"read") && names.contains(&"grep"), "declared: {names:?}");
343
+ assert!(names.contains(&"bash"), "stable-core kept: {names:?}");
344
+ assert!(names.contains(&SKILL_TOOL_NAME), "meta-tool exempt: {names:?}");
345
+ assert!(!names.contains(&"write"), "undeclared gated out: {names:?}");
346
+ }
347
+ _ => panic!("expected CallLLM"),
348
+ }
349
+ }
350
+
351
+ /// P1-B B4: the gated toolset is byte-stable *within* an epoch (no active-set change ⇒ identical
352
+ /// tools every turn — the cache prefix never churns mid-epoch) and changes only when the active
353
+ /// set changes (the single epoch boundary where D re-anchors the cache).
354
+ #[test]
355
+ fn gating_is_byte_stable_within_an_epoch() {
356
+ fn names(action: LoopAction) -> Vec<String> {
357
+ match action {
358
+ LoopAction::CallLLM { tools, .. } => {
359
+ let mut n: Vec<String> = tools.iter().map(|t| t.name.to_string()).collect();
360
+ n.sort();
361
+ n
362
+ }
363
+ _ => panic!("expected CallLLM"),
364
+ }
365
+ }
366
+ let mut sm = sm();
367
+ sm.tools = vec![make_tool_schema("read"), make_tool_schema("write"), make_tool_schema("grep")];
368
+ let mut debug = SkillMetadata::new("debug", "d");
369
+ debug.allowed_tools = vec![compact_str::CompactString::new("read")];
370
+ let mut review = SkillMetadata::new("review", "r");
371
+ review.allowed_tools = vec![compact_str::CompactString::new("grep")];
372
+ sm.ctx.set_available_skills(vec![debug, review]);
373
+
374
+ sm.start(RuntimeTask::new("go"));
375
+ sm.ctx.activate_skill("debug");
376
+ let n1 = names(sm.emit_call_llm());
377
+ let n2 = names(sm.emit_call_llm()); // no activation change → identical
378
+ assert_eq!(n1, n2, "toolset must be byte-stable within an epoch");
379
+ assert!(n1.contains(&"read".to_string()) && !n1.contains(&"write".to_string()));
380
+
381
+ sm.ctx.activate_skill("review"); // epoch boundary
382
+ let n3 = names(sm.emit_call_llm());
383
+ assert_ne!(n1, n3, "activating another skill changes the toolset");
384
+ assert!(n3.contains(&"grep".to_string()), "union now includes review's tools: {n3:?}");
385
+ }
386
+
309
387
  #[test]
310
388
  fn skill_tool_injected_in_call_llm_when_skills_registered() {
311
389
  let mut sm = sm();
@@ -332,6 +410,69 @@
332
410
  }
333
411
  }
334
412
 
413
+ /// P0-A (tool gating): a top-level run carrying an `AgentRunSpec` with a
414
+ /// capability filter must only expose the allow-listed tools each turn — the
415
+ /// static per-run profile. The filter is an allow-list (empty = allow all),
416
+ /// applied in `emit_call_llm`, and is byte-stable across the run, so it never
417
+ /// busts the cache prefix. Gates the same path sub-agents already use.
418
+ #[test]
419
+ fn top_level_run_capability_filter_gates_exposed_tools() {
420
+ use crate::types::agent::{
421
+ AgentCapabilityFilter, AgentIdentity, AgentRole, AgentRunSpec,
422
+ };
423
+ let mut sm = sm();
424
+ sm.tools = vec![
425
+ make_tool_schema("read"),
426
+ make_tool_schema("write"),
427
+ make_tool_schema("bash"),
428
+ make_tool_schema("search"),
429
+ ];
430
+ // Static profile: only `read` + `search` are exposed for this run.
431
+ let mut spec = AgentRunSpec::new(
432
+ AgentIdentity::new("root", "root-session"),
433
+ AgentRole::Custom,
434
+ "do the task",
435
+ );
436
+ spec.capability_filter = AgentCapabilityFilter {
437
+ allowed_kinds: Vec::new(),
438
+ allowed_ids: vec![
439
+ compact_str::CompactString::new("read"),
440
+ compact_str::CompactString::new("search"),
441
+ ],
442
+ };
443
+ sm.run_spec = Some(spec);
444
+
445
+ match sm.start(RuntimeTask::new("do the task")) {
446
+ LoopAction::CallLLM { tools, .. } => {
447
+ let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
448
+ assert!(names.contains(&"read"), "read should be exposed: {names:?}");
449
+ assert!(names.contains(&"search"), "search should be exposed: {names:?}");
450
+ assert!(!names.contains(&"write"), "write must be gated out: {names:?}");
451
+ assert!(!names.contains(&"bash"), "bash must be gated out: {names:?}");
452
+ }
453
+ _ => panic!("expected CallLLM"),
454
+ }
455
+ }
456
+
457
+ /// Counterpart: with no run spec (the default top-level run), every base tool
458
+ /// is exposed — i.e. gating is strictly opt-in (铁律: no config = old behavior).
459
+ #[test]
460
+ fn top_level_run_without_spec_exposes_all_tools() {
461
+ let mut sm = sm();
462
+ sm.tools = vec![
463
+ make_tool_schema("read"),
464
+ make_tool_schema("write"),
465
+ make_tool_schema("bash"),
466
+ ];
467
+ match sm.start(RuntimeTask::new("do the task")) {
468
+ LoopAction::CallLLM { tools, .. } => {
469
+ let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
470
+ assert!(names.contains(&"read") && names.contains(&"write") && names.contains(&"bash"));
471
+ }
472
+ _ => panic!("expected CallLLM"),
473
+ }
474
+ }
475
+
335
476
  #[test]
336
477
  fn compression_emits_observation() {
337
478
  let mut sm = LoopStateMachine::new(LoopPolicy {
@@ -43,6 +43,16 @@ from deepstrike.runtime import (
43
43
  default_sub_agent_orchestrator,
44
44
  DEFAULT_SANDBOX_POLICY,
45
45
  validate_declarative_policy,
46
+ ReplayProvider,
47
+ ReplayProviderOpts,
48
+ extract_recorded_messages,
49
+ Criterion,
50
+ Verdict,
51
+ VerdictDetail,
52
+ judge,
53
+ build_eval_messages,
54
+ parse_verdict,
55
+ verdict_output_schema,
46
56
  )
47
57
  from deepstrike.governance import Governance, GovernanceVerdict
48
58
  from deepstrike.providers import (
@@ -7,6 +7,7 @@ from deepstrike.runtime.runner import (
7
7
  RuntimeOptions,
8
8
  RuntimeRunner,
9
9
  SubAgentHarnessConfig,
10
+ TurnMetrics,
10
11
  collect_text,
11
12
  )
12
13
  from deepstrike.runtime.session_log import (
@@ -60,10 +61,22 @@ from deepstrike.runtime.os_profile import (
60
61
  os_profile,
61
62
  validate_declarative_policy,
62
63
  )
64
+ from deepstrike.runtime.replay_provider import ReplayProvider, ReplayProviderOpts
65
+ from deepstrike.runtime.replay_fixture import extract_recorded_messages
66
+ from deepstrike.runtime.eval import (
67
+ Criterion,
68
+ Verdict,
69
+ VerdictDetail,
70
+ judge,
71
+ build_eval_messages,
72
+ parse_verdict,
73
+ verdict_output_schema,
74
+ )
63
75
 
64
76
  __all__ = [
65
77
  "RuntimeRunner",
66
78
  "RuntimeOptions",
79
+ "TurnMetrics",
67
80
  "ResourceQuota",
68
81
  "MemoryPolicy",
69
82
  "MemoryWriteRateLimit",
@@ -116,4 +129,14 @@ __all__ = [
116
129
  "assert_native_profile",
117
130
  "os_profile",
118
131
  "validate_declarative_policy",
132
+ "ReplayProvider",
133
+ "ReplayProviderOpts",
134
+ "extract_recorded_messages",
135
+ "Criterion",
136
+ "Verdict",
137
+ "VerdictDetail",
138
+ "judge",
139
+ "build_eval_messages",
140
+ "parse_verdict",
141
+ "verdict_output_schema",
119
142
  ]
@@ -0,0 +1,110 @@
1
+ """`judge()` — one-shot quality scoring against a goal + criteria.
2
+
3
+ Python port of node/src/runtime/eval.ts. Wraps the kernel's `gen_eval` free functions
4
+ (`build_eval_messages` / `parse_verdict` / `verdict_output_schema`) into a small typed
5
+ surface so callers can score one (goal, criteria, result) pair without standing up
6
+ HarnessLoop.
7
+
8
+ Single LLM call: build the eval prompt → stream → parse verdict.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ from deepstrike._kernel import ( # type: ignore
17
+ Message,
18
+ build_eval_messages as _kernel_build_eval_messages,
19
+ parse_verdict as _kernel_parse_verdict,
20
+ verdict_output_schema as _kernel_verdict_output_schema,
21
+ )
22
+ from deepstrike.providers.base import LLMProvider, RenderedContext # type: ignore
23
+
24
+
25
+ @dataclass
26
+ class Criterion:
27
+ text: str
28
+ required: bool = True
29
+ weight: float | None = None
30
+
31
+
32
+ @dataclass
33
+ class VerdictDetail:
34
+ criterion: str
35
+ passed: bool
36
+ score: float
37
+ feedback: str
38
+
39
+
40
+ @dataclass
41
+ class Verdict:
42
+ passed: bool
43
+ overall_score: float
44
+ feedback: str
45
+ details: list[VerdictDetail] = field(default_factory=list)
46
+
47
+
48
+ def build_eval_messages(goal: str, criteria: list[Criterion], result: str) -> list[Message]:
49
+ """Render the kernel's eval prompt for (goal, criteria, result)."""
50
+ native_criteria = [
51
+ {"text": c.text, "required": c.required, **({"weight": c.weight} if c.weight is not None else {})}
52
+ for c in criteria
53
+ ]
54
+ return _kernel_build_eval_messages(goal, native_criteria, result, 1, False)
55
+
56
+
57
+ def parse_verdict(text: str) -> Verdict:
58
+ """Parse a Verdict from raw judge-LLM text."""
59
+ raw = _kernel_parse_verdict(text)
60
+ details_raw = raw.get("details") if isinstance(raw, dict) else getattr(raw, "details", None)
61
+ details: list[VerdictDetail] = []
62
+ for d in details_raw or []:
63
+ if isinstance(d, dict):
64
+ details.append(VerdictDetail(
65
+ criterion=d.get("criterion", ""),
66
+ passed=bool(d.get("passed", False)),
67
+ score=float(d.get("score", 0.0)),
68
+ feedback=d.get("feedback", ""),
69
+ ))
70
+ if isinstance(raw, dict):
71
+ return Verdict(
72
+ passed=bool(raw.get("passed", False)),
73
+ overall_score=float(raw.get("overallScore", raw.get("overall_score", 0.0))),
74
+ feedback=raw.get("feedback", ""),
75
+ details=details,
76
+ )
77
+ return Verdict(
78
+ passed=bool(getattr(raw, "passed", False)),
79
+ overall_score=float(getattr(raw, "overall_score", 0.0)),
80
+ feedback=getattr(raw, "feedback", ""),
81
+ details=details,
82
+ )
83
+
84
+
85
+ def verdict_output_schema() -> dict[str, Any]:
86
+ """The JSON Schema the kernel expects judge output to conform to."""
87
+ return json.loads(_kernel_verdict_output_schema(False))
88
+
89
+
90
+ async def judge(
91
+ provider: LLMProvider,
92
+ goal: str,
93
+ criteria: list[Criterion],
94
+ result: str,
95
+ ) -> Verdict:
96
+ """Run one judge pass: render eval prompt, stream the provider, parse the verdict."""
97
+ msgs = build_eval_messages(goal, criteria, result)
98
+ sys_text = "\n\n".join(m.get("content", "") if isinstance(m, dict) else getattr(m, "content", "")
99
+ for m in msgs
100
+ if (m.get("role") if isinstance(m, dict) else getattr(m, "role", None)) == "system")
101
+ turns = [m for m in msgs if (m.get("role") if isinstance(m, dict) else getattr(m, "role", None)) != "system"]
102
+ ctx: RenderedContext = {"systemText": sys_text, "turns": turns} # type: ignore[assignment]
103
+
104
+ text = ""
105
+ async for evt in provider.stream(ctx, [], None):
106
+ if (evt.get("type") if isinstance(evt, dict) else getattr(evt, "type", None)) == "text_delta":
107
+ text += (evt.get("delta") if isinstance(evt, dict) else getattr(evt, "delta", "")) or ""
108
+ if not text:
109
+ raise RuntimeError("judge: provider produced no text")
110
+ return parse_verdict(text)
@@ -76,6 +76,10 @@ def skill_metadata_to_kernel(skill: Any) -> dict[str, Any]:
76
76
  out["when_to_use"] = when_to_use
77
77
  if effort is not None:
78
78
  out["effort"] = effort
79
+ # P1-B: forward declared tool ids (additive; omitted when empty so existing skills' wire is unchanged).
80
+ allowed_tools = getattr(skill, "allowed_tools", None)
81
+ if allowed_tools:
82
+ out["allowed_tools"] = list(allowed_tools)
79
83
  return out
80
84
 
81
85