deepstrike 0.2.28__tar.gz → 0.2.30__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 (175) hide show
  1. {deepstrike-0.2.28 → deepstrike-0.2.30}/Cargo.lock +5 -5
  2. {deepstrike-0.2.28 → deepstrike-0.2.30}/Cargo.toml +2 -2
  3. {deepstrike-0.2.28 → deepstrike-0.2.30}/PKG-INFO +39 -12
  4. {deepstrike-0.2.28 → deepstrike-0.2.30}/README.md +38 -11
  5. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/kernel.rs +281 -41
  6. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +17 -0
  7. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/__init__.py +5 -0
  8. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/__init__.py +5 -3
  9. deepstrike-0.2.30/deepstrike/providers/factories.py +80 -0
  10. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/__init__.py +3 -0
  11. deepstrike-0.2.30/deepstrike/runtime/facade.py +89 -0
  12. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/runner.py +12 -8
  13. {deepstrike-0.2.28 → deepstrike-0.2.30}/pyproject.toml +1 -1
  14. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/Cargo.toml +0 -0
  15. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/compression.rs +0 -0
  16. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/config.rs +0 -0
  17. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
  18. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/manager.rs +0 -0
  19. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/mod.rs +0 -0
  20. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/partitions.rs +0 -0
  21. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/pressure.rs +0 -0
  22. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/renderer.rs +0 -0
  23. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/renewal.rs +0 -0
  24. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/sections.rs +0 -0
  25. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
  26. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
  27. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
  28. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/task_state.rs +0 -0
  29. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/text.rs +0 -0
  30. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
  31. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/audit.rs +0 -0
  32. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
  33. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/mod.rs +0 -0
  34. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/permission.rs +0 -0
  35. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
  36. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/quota.rs +0 -0
  37. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
  38. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
  39. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
  40. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/governance/veto.rs +0 -0
  41. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/harness/eval.rs +0 -0
  42. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/harness/mod.rs +0 -0
  43. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/lib.rs +0 -0
  44. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/curator.rs +0 -0
  45. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/durable.rs +0 -0
  46. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
  47. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
  48. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/mod.rs +0 -0
  49. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
  50. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
  51. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/session.rs +0 -0
  52. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
  53. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
  54. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/memory/working.rs +0 -0
  55. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/mm/handle.rs +0 -0
  56. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/mm/memory.rs +0 -0
  57. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/mm/mod.rs +0 -0
  58. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
  59. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
  60. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
  61. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
  62. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
  63. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/proc/mod.rs +0 -0
  64. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
  65. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
  66. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
  67. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
  68. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/session.rs +0 -0
  69. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
  70. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
  71. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
  72. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
  73. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
  74. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
  75. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +0 -0
  76. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +0 -0
  77. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
  78. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +0 -0
  79. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
  80. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
  81. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +0 -0
  82. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
  83. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/signals/attention.rs +0 -0
  84. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/signals/mod.rs +0 -0
  85. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/signals/queue.rs +0 -0
  86. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/signals/router.rs +0 -0
  87. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
  88. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/agent.rs +0 -0
  89. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/capability.rs +0 -0
  90. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/contract.rs +0 -0
  91. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/error.rs +0 -0
  92. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/message.rs +0 -0
  93. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/milestone.rs +0 -0
  94. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/mod.rs +0 -0
  95. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/model.rs +0 -0
  96. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/policy.rs +0 -0
  97. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/result.rs +0 -0
  98. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/signal.rs +0 -0
  99. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/skill.rs +0 -0
  100. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-core/src/types/task.rs +0 -0
  101. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-py/Cargo.toml +0 -0
  102. {deepstrike-0.2.28 → deepstrike-0.2.30}/crates/deepstrike-py/src/lib.rs +0 -0
  103. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/__init__.py +0 -0
  104. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/contract.py +0 -0
  105. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/handoff.py +0 -0
  106. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/harness.py +0 -0
  107. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/modes.py +0 -0
  108. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/collaboration/pool.py +0 -0
  109. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/governance.py +0 -0
  110. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/harness/__init__.py +0 -0
  111. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/harness/harness.py +0 -0
  112. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/kernel/__init__.py +0 -0
  113. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/knowledge/__init__.py +0 -0
  114. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/knowledge/source.py +0 -0
  115. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/memory/__init__.py +0 -0
  116. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/memory/agent.py +0 -0
  117. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/memory/in_memory_store.py +0 -0
  118. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/memory/protocols.py +0 -0
  119. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/memory/working.py +0 -0
  120. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/anthropic.py +0 -0
  121. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/base.py +0 -0
  122. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/deepseek.py +0 -0
  123. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/gemini.py +0 -0
  124. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/glm.py +0 -0
  125. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/kimi.py +0 -0
  126. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/minimax.py +0 -0
  127. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/ollama.py +0 -0
  128. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/openai.py +0 -0
  129. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/openai_responses.py +0 -0
  130. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/qwen.py +0 -0
  131. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/replay.py +0 -0
  132. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/replay_validator.py +0 -0
  133. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/providers/stream.py +0 -0
  134. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/archive.py +0 -0
  135. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/credential_vault.py +0 -0
  136. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/eval.py +0 -0
  137. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/execution_plane.py +0 -0
  138. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/filtered_plane.py +0 -0
  139. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/kernel_event_log.py +0 -0
  140. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/kernel_step.py +0 -0
  141. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/large_result_spool.py +0 -0
  142. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
  143. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/os_profile.py +0 -0
  144. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/os_snapshot.py +0 -0
  145. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/output_schema.py +0 -0
  146. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/process_sandbox_plane.py +0 -0
  147. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/provider_replay.py +0 -0
  148. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/reducers.py +0 -0
  149. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/remote_vpc_plane.py +0 -0
  150. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/replay_fixture.py +0 -0
  151. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/replay_provider.py +0 -0
  152. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/replay_sanitize.py +0 -0
  153. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/session_log.py +0 -0
  154. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/session_repair.py +0 -0
  155. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
  156. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/workflow_control_flow.py +0 -0
  157. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/workflow_store.py +0 -0
  158. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/runtime/worktree_plane.py +0 -0
  159. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/safety/__init__.py +0 -0
  160. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/safety/permissions.py +0 -0
  161. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/signals/__init__.py +0 -0
  162. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/signals/gateway.py +0 -0
  163. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/signals/scheduled.py +0 -0
  164. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/signals/types.py +0 -0
  165. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/skills/__init__.py +0 -0
  166. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/skills/registry.py +0 -0
  167. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/skills/watcher.py +0 -0
  168. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/__init__.py +0 -0
  169. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/builtin/__init__.py +0 -0
  170. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/builtin/read_file.py +0 -0
  171. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/errors.py +0 -0
  172. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/execution.py +0 -0
  173. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/tools/registry.py +0 -0
  174. {deepstrike-0.2.28 → deepstrike-0.2.30}/deepstrike/types/__init__.py +0 -0
  175. {deepstrike-0.2.28 → deepstrike-0.2.30}/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.28"
197
+ version = "0.2.30"
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.28"
209
+ version = "0.2.30"
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.28"
221
+ version = "0.2.30"
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.28"
232
+ version = "0.2.30"
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.28"
266
+ version = "0.2.30"
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.28"
6
+ version = "0.2.30"
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.28" }
12
+ deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.30" }
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.28
3
+ Version: 0.2.30
4
4
  Requires-Dist: httpx>=0.27
5
5
  Requires-Dist: pyyaml>=6.0
6
6
  Requires-Dist: anyio>=4.0
@@ -68,6 +68,25 @@ asyncio.run(collect_text(runner.run(
68
68
  # => "45"
69
69
  ```
70
70
 
71
+ ### Recipes — the canonical entry points
72
+
73
+ Most apps need one of two shapes. Start with the facades; drop to `RuntimeRunner` for streaming, tools, signals, memory, or governance.
74
+
75
+ ```python
76
+ from deepstrike import run_agent, run_fanout
77
+
78
+ # 1) Single agent — one prompt, one model, the text back.
79
+ answer = await run_agent(provider=provider, goal="What is 17 + 28?", tools=[add])
80
+
81
+ # 2) Parallel fan-out → synthesize over the kernel-gated DAG (safe from a stateless handler).
82
+ out = await run_fanout(
83
+ provider=provider,
84
+ tasks=["Summarize the auth module", "Summarize the data layer"],
85
+ synthesize="Combine the findings into one summary.",
86
+ )
87
+ synthesis = out["synthesis"]
88
+ ```
89
+
71
90
  Same-session continuity is explicit via `session_id`:
72
91
 
73
92
  ```python
@@ -205,17 +224,25 @@ runner = RuntimeRunner(RuntimeOptions(
205
224
  ))
206
225
  ```
207
226
 
208
- | Class | Backend | Notes |
209
- |-------|---------|-------|
210
- | `OpenAIProvider` | OpenAI API | SSE tool-call accumulation |
211
- | `AnthropicProvider` | Anthropic API | Native SSE, `ThinkingDelta` support |
212
- | `QwenProvider` | DashScope | `enable_thinking` via extensions |
213
- | `DeepSeekProvider` | DeepSeek API | Reasoner models strip tools automatically |
214
- | `MiniMaxProvider` | MiniMax API | M1 reasoning via `expose_reasoning` |
215
- | `OllamaProvider` | Local Ollama | `http://localhost:11434` default |
216
- | `KimiProvider` | Moonshot API | |
217
-
218
- All providers accept `RetryConfig` for exponential backoff and share a `CircuitBreaker`.
227
+ The top-level package exports the base providers `OpenAIProvider`, `OpenAIResponsesProvider`, and
228
+ `AnthropicProvider`. **Every other backend is a factory function** in `deepstrike.providers`, with a
229
+ `protocol` argument where a backend speaks both the OpenAI- and Anthropic-compatible wire:
230
+
231
+ ```python
232
+ from deepstrike.providers import deepseek, kimi, minimax
233
+
234
+ ds = deepseek(api_key="...") # OpenAI-compatible wire (default)
235
+ dsA = deepseek(api_key="...", protocol="anthropic") # Anthropic-compatible wire
236
+ mm = minimax(api_key="...") # MiniMax defaults to the Anthropic wire
237
+ ```
238
+
239
+ | Entry | Import from | Backend |
240
+ |-------|-------------|---------|
241
+ | `OpenAIProvider` / `OpenAIResponsesProvider` | `deepstrike` | OpenAI (and OpenAI-compatible) |
242
+ | `AnthropicProvider` | `deepstrike` | Anthropic Messages API |
243
+ | `deepseek` · `kimi` · `qwen` · `glm` · `minimax` · `gemini` · `ollama` | `deepstrike.providers` | the respective vendor (factory functions) |
244
+
245
+ All providers accept `retry_config` for exponential backoff and share a `CircuitBreaker`.
219
246
 
220
247
  `extensions` are forwarded to the provider while SDK-owned structural fields remain protected.
221
248
 
@@ -55,6 +55,25 @@ asyncio.run(collect_text(runner.run(
55
55
  # => "45"
56
56
  ```
57
57
 
58
+ ### Recipes — the canonical entry points
59
+
60
+ Most apps need one of two shapes. Start with the facades; drop to `RuntimeRunner` for streaming, tools, signals, memory, or governance.
61
+
62
+ ```python
63
+ from deepstrike import run_agent, run_fanout
64
+
65
+ # 1) Single agent — one prompt, one model, the text back.
66
+ answer = await run_agent(provider=provider, goal="What is 17 + 28?", tools=[add])
67
+
68
+ # 2) Parallel fan-out → synthesize over the kernel-gated DAG (safe from a stateless handler).
69
+ out = await run_fanout(
70
+ provider=provider,
71
+ tasks=["Summarize the auth module", "Summarize the data layer"],
72
+ synthesize="Combine the findings into one summary.",
73
+ )
74
+ synthesis = out["synthesis"]
75
+ ```
76
+
58
77
  Same-session continuity is explicit via `session_id`:
59
78
 
60
79
  ```python
@@ -192,17 +211,25 @@ runner = RuntimeRunner(RuntimeOptions(
192
211
  ))
193
212
  ```
194
213
 
195
- | Class | Backend | Notes |
196
- |-------|---------|-------|
197
- | `OpenAIProvider` | OpenAI API | SSE tool-call accumulation |
198
- | `AnthropicProvider` | Anthropic API | Native SSE, `ThinkingDelta` support |
199
- | `QwenProvider` | DashScope | `enable_thinking` via extensions |
200
- | `DeepSeekProvider` | DeepSeek API | Reasoner models strip tools automatically |
201
- | `MiniMaxProvider` | MiniMax API | M1 reasoning via `expose_reasoning` |
202
- | `OllamaProvider` | Local Ollama | `http://localhost:11434` default |
203
- | `KimiProvider` | Moonshot API | |
204
-
205
- All providers accept `RetryConfig` for exponential backoff and share a `CircuitBreaker`.
214
+ The top-level package exports the base providers `OpenAIProvider`, `OpenAIResponsesProvider`, and
215
+ `AnthropicProvider`. **Every other backend is a factory function** in `deepstrike.providers`, with a
216
+ `protocol` argument where a backend speaks both the OpenAI- and Anthropic-compatible wire:
217
+
218
+ ```python
219
+ from deepstrike.providers import deepseek, kimi, minimax
220
+
221
+ ds = deepseek(api_key="...") # OpenAI-compatible wire (default)
222
+ dsA = deepseek(api_key="...", protocol="anthropic") # Anthropic-compatible wire
223
+ mm = minimax(api_key="...") # MiniMax defaults to the Anthropic wire
224
+ ```
225
+
226
+ | Entry | Import from | Backend |
227
+ |-------|-------------|---------|
228
+ | `OpenAIProvider` / `OpenAIResponsesProvider` | `deepstrike` | OpenAI (and OpenAI-compatible) |
229
+ | `AnthropicProvider` | `deepstrike` | Anthropic Messages API |
230
+ | `deepseek` · `kimi` · `qwen` · `glm` · `minimax` · `gemini` · `ollama` | `deepstrike.providers` | the respective vendor (factory functions) |
231
+
232
+ All providers accept `retry_config` for exponential backoff and share a `CircuitBreaker`.
206
233
 
207
234
  `extensions` are forwarded to the provider while SDK-owned structural fields remain protected.
208
235
 
@@ -106,6 +106,102 @@ impl KernelInput {
106
106
  }
107
107
  }
108
108
 
109
+ /// K2: the governance sub-bundle of [`RunConfig`] — the same five fields as the `LoadGovernancePolicy`
110
+ /// event, grouped so a run's whole governance posture travels as one value.
111
+ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
112
+ pub struct GovernanceConfig {
113
+ #[serde(default, skip_serializing_if = "Option::is_none")]
114
+ pub default_action: Option<PolicyAction>,
115
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
116
+ pub rules: Vec<PolicyRule>,
117
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
118
+ pub vetoed_tools: Vec<String>,
119
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
120
+ pub rate_limits: Vec<RateLimitSpec>,
121
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
122
+ pub constraints: Vec<ConstraintSpec>,
123
+ }
124
+
125
+ /// K2: a bundle of run-setup configuration carried by the [`KernelInputEvent::ConfigureRun`] event.
126
+ /// Each field maps 1:1 to a granular `Set*` / `Load*` event; `None`/absent leaves that aspect untouched.
127
+ /// This is the host-side analogue of the SDK's `applyKernelPolicies` — one event for the whole setup.
128
+ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
129
+ pub struct RunConfig {
130
+ #[serde(default, skip_serializing_if = "Option::is_none")]
131
+ pub tools: Option<Vec<ToolSchema>>,
132
+ #[serde(default, skip_serializing_if = "Option::is_none")]
133
+ pub available_skills: Option<Vec<SkillMetadata>>,
134
+ #[serde(default, skip_serializing_if = "Option::is_none")]
135
+ pub stable_core_tools: Option<Vec<String>>,
136
+ #[serde(default, skip_serializing_if = "Option::is_none")]
137
+ pub memory_enabled: Option<bool>,
138
+ #[serde(default, skip_serializing_if = "Option::is_none")]
139
+ pub knowledge_enabled: Option<bool>,
140
+ #[serde(default, skip_serializing_if = "Option::is_none")]
141
+ pub plan_tool_enabled: Option<bool>,
142
+ /// Present (any value) ⇒ reset the token engine to the char-approx estimator (see `SetTokenizer`).
143
+ #[serde(default, skip_serializing_if = "Option::is_none")]
144
+ pub tokenizer: Option<String>,
145
+ #[serde(default, skip_serializing_if = "Option::is_none")]
146
+ pub governance: Option<GovernanceConfig>,
147
+ #[serde(default, skip_serializing_if = "Option::is_none")]
148
+ pub attention_max_queue_size: Option<u32>,
149
+ #[serde(default, skip_serializing_if = "Option::is_none")]
150
+ pub scheduler_max_wall_ms: Option<u64>,
151
+ #[serde(default, skip_serializing_if = "Option::is_none")]
152
+ pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
153
+ }
154
+
155
+ /// Build a [`GovernancePipeline`](crate::governance::pipeline::GovernancePipeline) from the ABI policy
156
+ /// fields. Shared by the `LoadGovernancePolicy` event and the `ConfigureRun` bundle so the two can never
157
+ /// drift in how they interpret rules / vetoes / rate-limits / constraints.
158
+ pub(crate) fn build_governance_pipeline(
159
+ default_action: Option<PolicyAction>,
160
+ rules: Vec<PolicyRule>,
161
+ vetoed_tools: Vec<String>,
162
+ rate_limits: Vec<RateLimitSpec>,
163
+ constraints: Vec<ConstraintSpec>,
164
+ ) -> crate::governance::pipeline::GovernancePipeline {
165
+ use crate::governance::constraint::{ConstraintRule, ParamConstraint};
166
+ use crate::governance::permission::PermissionRule;
167
+ use crate::governance::rate_limit::RateLimit;
168
+ let default = default_action.unwrap_or(PolicyAction::Allow).into();
169
+ let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
170
+ for rule in rules {
171
+ pipeline.permission.add_rule(PermissionRule {
172
+ tool_pattern: rule.tool_pattern.into(),
173
+ action: rule.action.into(),
174
+ });
175
+ }
176
+ for tool in vetoed_tools {
177
+ pipeline.veto.block_tool(tool);
178
+ }
179
+ for rl in rate_limits {
180
+ pipeline.rate_limiter.set_limit(
181
+ rl.tool,
182
+ RateLimit {
183
+ max_calls: rl.max_calls,
184
+ window_ms: rl.window_ms,
185
+ },
186
+ );
187
+ }
188
+ for c in constraints {
189
+ let (tool_name, param_path, rule) = match c {
190
+ ConstraintSpec::Required { tool, path } => (tool, path, ConstraintRule::Required),
191
+ ConstraintSpec::Enum { tool, path, values } => (tool, path, ConstraintRule::Enum(values)),
192
+ ConstraintSpec::Range { tool, path, min, max } => {
193
+ (tool, path, ConstraintRule::Range { min, max })
194
+ }
195
+ };
196
+ pipeline.constraints.add(ParamConstraint {
197
+ tool_name,
198
+ param_path,
199
+ rule,
200
+ });
201
+ }
202
+ pipeline
203
+ }
204
+
109
205
  #[derive(Debug, Clone, Serialize, Deserialize)]
110
206
  #[serde(tag = "kind", rename_all = "snake_case")]
111
207
  pub enum KernelInputEvent {
@@ -195,6 +291,13 @@ pub enum KernelInputEvent {
195
291
  #[serde(default, skip_serializing_if = "Option::is_none")]
196
292
  run_spec: Option<AgentRunSpec>,
197
293
  },
294
+ /// K2: apply a bundle of run-setup configuration in a single event — the consolidation of the
295
+ /// ~10 discrete `Set*` / `Load*` config events the SDK used to fire one-by-one before `StartRun`.
296
+ /// Every field is optional; an absent field leaves that aspect untouched. The granular events
297
+ /// remain for runtime mutation (a skill mount changing tools, a mid-run budget change). ABI-additive.
298
+ ConfigureRun {
299
+ config: RunConfig,
300
+ },
198
301
  CapabilityCommand {
199
302
  command: CapabilityCommand,
200
303
  },
@@ -753,51 +856,74 @@ impl KernelRuntime {
753
856
  rate_limits,
754
857
  constraints,
755
858
  } => {
756
- use crate::governance::constraint::{ConstraintRule, ParamConstraint};
757
- use crate::governance::permission::PermissionRule;
758
- use crate::governance::rate_limit::RateLimit;
759
- let default = default_action.unwrap_or(PolicyAction::Allow).into();
760
- let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
761
- for rule in rules {
762
- pipeline.permission.add_rule(PermissionRule {
763
- tool_pattern: rule.tool_pattern.into(),
764
- action: rule.action.into(),
765
- });
859
+ self.sm.set_governance(build_governance_pipeline(
860
+ default_action,
861
+ rules,
862
+ vetoed_tools,
863
+ rate_limits,
864
+ constraints,
865
+ ));
866
+ return KernelStep::empty(self.sm.take_observations());
867
+ }
868
+ KernelInputEvent::ConfigureRun { config } => {
869
+ // K2: apply a bundle of run-setup config in one event (tools / governance / attention /
870
+ // quota / scheduler / toggles), replacing the ~10 separate `Set*` / `Load*` events the
871
+ // SDK used to fire one-by-one. Each field is optional; an absent field is left untouched.
872
+ // The individual events remain for runtime mutation (skill mount, mid-run budget change).
873
+ // Each branch delegates to exactly the method its granular event uses, so the two paths
874
+ // can never diverge.
875
+ let RunConfig {
876
+ tools,
877
+ available_skills,
878
+ stable_core_tools,
879
+ memory_enabled,
880
+ knowledge_enabled,
881
+ plan_tool_enabled,
882
+ tokenizer,
883
+ governance,
884
+ attention_max_queue_size,
885
+ scheduler_max_wall_ms,
886
+ resource_quota,
887
+ } = config;
888
+ if let Some(tools) = tools {
889
+ self.sm.tools = tools;
766
890
  }
767
- for tool in vetoed_tools {
768
- pipeline.veto.block_tool(tool);
891
+ if let Some(skills) = available_skills {
892
+ self.sm.ctx.set_available_skills(skills);
769
893
  }
770
- for rl in rate_limits {
771
- pipeline.rate_limiter.set_limit(
772
- rl.tool,
773
- RateLimit {
774
- max_calls: rl.max_calls,
775
- window_ms: rl.window_ms,
776
- },
777
- );
894
+ if let Some(ids) = stable_core_tools {
895
+ self.sm.ctx.set_stable_core_tools(ids.into_iter().map(Into::into));
778
896
  }
779
- for c in constraints {
780
- let (tool_name, param_path, rule) = match c {
781
- ConstraintSpec::Required { tool, path } => {
782
- (tool, path, ConstraintRule::Required)
783
- }
784
- ConstraintSpec::Enum { tool, path, values } => {
785
- (tool, path, ConstraintRule::Enum(values))
786
- }
787
- ConstraintSpec::Range {
788
- tool,
789
- path,
790
- min,
791
- max,
792
- } => (tool, path, ConstraintRule::Range { min, max }),
793
- };
794
- pipeline.constraints.add(ParamConstraint {
795
- tool_name,
796
- param_path,
797
- rule,
798
- });
897
+ if let Some(enabled) = memory_enabled {
898
+ self.sm.ctx.set_memory_enabled(enabled);
899
+ }
900
+ if let Some(enabled) = knowledge_enabled {
901
+ self.sm.ctx.set_knowledge_enabled(enabled);
902
+ }
903
+ if let Some(enabled) = plan_tool_enabled {
904
+ self.sm.ctx.set_plan_tool_enabled(enabled);
905
+ }
906
+ if tokenizer.is_some() {
907
+ self.sm.ctx.engine = ContextTokenEngine::char_approx();
908
+ }
909
+ if let Some(g) = governance {
910
+ self.sm.set_governance(build_governance_pipeline(
911
+ g.default_action,
912
+ g.rules,
913
+ g.vetoed_tools,
914
+ g.rate_limits,
915
+ g.constraints,
916
+ ));
917
+ }
918
+ if let Some(max_queue) = attention_max_queue_size {
919
+ self.sm.set_attention(max_queue as usize);
920
+ }
921
+ if let Some(ms) = scheduler_max_wall_ms {
922
+ self.sm.set_wall_budget(Some(ms));
923
+ }
924
+ if let Some(quota) = resource_quota {
925
+ self.sm.set_resource_quota(quota);
799
926
  }
800
- self.sm.set_governance(pipeline);
801
927
  return KernelStep::empty(self.sm.take_observations());
802
928
  }
803
929
  KernelInputEvent::SetAttentionPolicy { max_queue_size } => {
@@ -883,6 +1009,10 @@ impl KernelRuntime {
883
1009
  resumed_completed,
884
1010
  resumed_submissions,
885
1011
  } => {
1012
+ // K1: self-bootstrap the run if the host never fired `StartRun` (stateless
1013
+ // `runWorkflow` caller). Parity with the agent-reachable `SubmitWorkflow`, which already
1014
+ // bootstraps. Idempotent no-op once the root task has left `Ready`.
1015
+ self.sm.ensure_started_for_workflow(&spec);
886
1016
  let action = if resumed_completed.is_empty() && resumed_submissions.is_empty() {
887
1017
  self.sm.load_workflow(spec, &parent_session_id)
888
1018
  } else {
@@ -1542,6 +1672,62 @@ mod tests {
1542
1672
  );
1543
1673
  }
1544
1674
 
1675
+ #[test]
1676
+ fn configure_run_bundle_applies_governance_equivalently_to_load_governance_policy() {
1677
+ // K2: the consolidated `ConfigureRun` bundle must apply governance identically to the granular
1678
+ // `LoadGovernancePolicy` event — a deny rule blocks the matching tool and re-prompts.
1679
+ let mut runtime = KernelRuntime::new(LoopPolicy::default());
1680
+ runtime.step(KernelInput::new(KernelInputEvent::ConfigureRun {
1681
+ config: RunConfig {
1682
+ tools: Some(vec![]),
1683
+ governance: Some(GovernanceConfig {
1684
+ default_action: Some(PolicyAction::Allow),
1685
+ rules: vec![PolicyRule {
1686
+ tool_pattern: "danger.*".to_string(),
1687
+ action: PolicyAction::Deny,
1688
+ }],
1689
+ ..GovernanceConfig::default()
1690
+ }),
1691
+ attention_max_queue_size: Some(32),
1692
+ ..RunConfig::default()
1693
+ },
1694
+ }));
1695
+
1696
+ let step = run_with_tool_call(&mut runtime, "danger.delete");
1697
+
1698
+ assert!(
1699
+ matches!(step.actions.as_slice(), [KernelAction::CallProvider { .. }]),
1700
+ "bundle-configured deny should roll back and re-call provider, got {:?}",
1701
+ step.actions
1702
+ );
1703
+ assert!(
1704
+ step.observations
1705
+ .iter()
1706
+ .any(|o| matches!(o, KernelObservation::Rollbacked { .. })),
1707
+ "expected a Rollbacked observation for the bundle-denied turn",
1708
+ );
1709
+ }
1710
+
1711
+ #[test]
1712
+ fn configure_run_round_trips_over_the_abi() {
1713
+ // The bundle must survive the versioned JSON ABI (replayable / session-loggable) like every
1714
+ // other host event.
1715
+ let event = KernelInputEvent::ConfigureRun {
1716
+ config: RunConfig {
1717
+ resource_quota: Some(crate::governance::quota::ResourceQuota {
1718
+ max_concurrent_subagents: Some(2),
1719
+ ..Default::default()
1720
+ }),
1721
+ scheduler_max_wall_ms: Some(60_000),
1722
+ plan_tool_enabled: Some(true),
1723
+ ..RunConfig::default()
1724
+ },
1725
+ };
1726
+ let json = serde_json::to_string(&event).expect("serialize");
1727
+ let parsed: KernelInputEvent = serde_json::from_str(&json).expect("deserialize");
1728
+ assert!(matches!(parsed, KernelInputEvent::ConfigureRun { .. }));
1729
+ }
1730
+
1545
1731
  #[test]
1546
1732
  fn governance_ask_user_suspends_until_resume() {
1547
1733
  let mut runtime = KernelRuntime::new(LoopPolicy::default());
@@ -1937,6 +2123,60 @@ mod tests {
1937
2123
  )));
1938
2124
  }
1939
2125
 
2126
+ #[test]
2127
+ fn load_workflow_self_bootstraps_with_no_prior_start_run() {
2128
+ // K1: a stateless `runWorkflow` caller fires `LoadWorkflow` with NO preceding `StartRun`. The
2129
+ // host path now self-bootstraps the run (parity with the agent-reachable `SubmitWorkflow`), so
2130
+ // the DAG installs and drives to completion exactly as the started-path test above.
2131
+ use crate::orchestration::workflow::fanout_synthesize;
2132
+ use crate::types::result::{LoopResult, SubAgentResult, TerminationReason};
2133
+
2134
+ let mut runtime = KernelRuntime::new(LoopPolicy::default());
2135
+ // NOTE: deliberately no StartRun here — that is the whole point of K1.
2136
+
2137
+ let spec =
2138
+ fanout_synthesize(vec![RuntimeTask::new("w0"), RuntimeTask::new("w1")], RuntimeTask::new("synth"));
2139
+ let step = runtime.step(KernelInput::new(KernelInputEvent::LoadWorkflow {
2140
+ spec,
2141
+ parent_session_id: "sess".to_string(),
2142
+ resumed_completed: Vec::new(),
2143
+ resumed_submissions: Vec::new(),
2144
+ }));
2145
+ let batch = step
2146
+ .observations
2147
+ .iter()
2148
+ .find_map(|o| match o {
2149
+ KernelObservation::WorkflowBatchSpawned { nodes, .. } => Some(nodes.clone()),
2150
+ _ => None,
2151
+ })
2152
+ .expect("workflow_batch_spawned even without a prior StartRun");
2153
+ assert_eq!(batch.len(), 2);
2154
+
2155
+ let complete = |runtime: &mut KernelRuntime, id: &str| {
2156
+ runtime.step(KernelInput::new(KernelInputEvent::SubAgentCompleted {
2157
+ result: SubAgentResult {
2158
+ agent_id: compact_str::CompactString::new(id),
2159
+ result: LoopResult {
2160
+ termination: TerminationReason::Completed,
2161
+ final_message: None,
2162
+ turns_used: 1,
2163
+ total_tokens_used: 1,
2164
+ loop_continue: None,
2165
+ classify_branch: None,
2166
+ tournament_winner: None,
2167
+ },
2168
+ },
2169
+ }))
2170
+ };
2171
+ complete(&mut runtime, "wf-node0");
2172
+ complete(&mut runtime, "wf-node1");
2173
+ let step = complete(&mut runtime, "wf-node2");
2174
+ assert!(step.observations.iter().any(|o| matches!(
2175
+ o,
2176
+ KernelObservation::WorkflowCompleted { completed, .. } if completed.len() == 3
2177
+ )));
2178
+ }
2179
+
1940
2180
  #[test]
1941
2181
  fn submit_workflow_nodes_input_appends_a_node_over_the_abi() {
1942
2182
  // R3-1: exercise the full serde round-trip of SubmitWorkflowNodes + WorkflowNode over the
@@ -7,6 +7,7 @@ use crate::runtime::session::RollbackReason;
7
7
  use crate::syscall::{Disposition, Syscall};
8
8
  use crate::types::message::Message;
9
9
  use crate::types::result::SubAgentResult;
10
+ use crate::types::task::RuntimeTask;
10
11
 
11
12
  impl LoopStateMachine {
12
13
  /// Whether a workflow DAG is currently in flight.
@@ -14,6 +15,22 @@ impl LoopStateMachine {
14
15
  self.workflow.is_some()
15
16
  }
16
17
 
18
+ /// K1: bring the host `LoadWorkflow` path to parity with the agent-reachable `SubmitWorkflow`,
19
+ /// which already bootstraps a run when none is active. If the root task is still `Ready` (the host
20
+ /// never fired `StartRun` — e.g. a stateless `runWorkflow` caller), run the same initialization
21
+ /// `StartRun` performs so the DAG installs onto a live run. No-op once the root has left `Ready`
22
+ /// (host started it, or we already bootstrapped), so it is safe to call unconditionally and carries
23
+ /// no behavior change for runs that were started normally. The `CallLLM` action `start` returns is
24
+ /// discarded — the workflow drives nodes, not a root provider turn, exactly as the SDK's previous
25
+ /// `start_run` + `load_workflow` sequence already did.
26
+ pub fn ensure_started_for_workflow(&mut self, spec: &crate::orchestration::workflow::WorkflowSpec) {
27
+ if !matches!(self.lifecycle(), TaskState::Ready) {
28
+ return;
29
+ }
30
+ let goal = format!("workflow:{} nodes", spec.nodes.len());
31
+ let _ = self.start(RuntimeTask::new(goal));
32
+ }
33
+
17
34
  /// W0: load a workflow DAG and spawn its first gated batch. On an invalid spec (cycle /
18
35
  /// out-of-range dependency) the workflow is not installed and the loop continues with a
19
36
  /// rollback note, mirroring how a denied effect is surfaced.
@@ -10,6 +10,8 @@ from deepstrike._kernel import (
10
10
  SkillMetadata,
11
11
  )
12
12
  from deepstrike.runtime import (
13
+ run_agent,
14
+ run_fanout,
13
15
  RuntimeRunner,
14
16
  RuntimeOptions,
15
17
  ResourceQuota,
@@ -106,6 +108,9 @@ from deepstrike.collaboration import (
106
108
  CreatorVerifierMode, OrchestrationMode, CreatorVerifierMetrics,
107
109
  )
108
110
  __all__ = [
111
+ # ── Start here: canonical entry points ──
112
+ "run_agent",
113
+ "run_fanout",
109
114
  "RuntimeRunner",
110
115
  "RuntimeOptions",
111
116
  "builtin_reducers",
@@ -47,13 +47,15 @@ from .ollama import OllamaProvider
47
47
  from .kimi import KimiProvider, KimiAnthropicProvider
48
48
  from .gemini import GeminiProvider
49
49
  from .glm import GLMProvider, GLMAnthropicProvider
50
+ # v0.2.3 parity: per-backend factory functions are the public surface; the dual classes above remain
51
+ # importable for advanced subclassing but are no longer advertised in __all__.
52
+ from .factories import deepseek, kimi, qwen, glm, minimax, gemini, ollama
50
53
 
51
54
  __all__ = [
52
55
  "LLMProvider", "RenderedContext", "ProviderRunState", "RuntimePolicy", "AnthropicProvider", "OpenAIProvider",
53
56
  "OpenAIResponsesProvider", "OpenAIResponsesAdapter",
54
- "QwenProvider", "QwenAnthropicProvider", "DeepSeekProvider", "DeepSeekAnthropicProvider",
55
- "MiniMaxAnthropicProvider", "MiniMaxOpenAIProvider", "OllamaProvider",
56
- "KimiProvider", "KimiAnthropicProvider", "GeminiProvider", "GLMProvider", "GLMAnthropicProvider",
57
+ # Backend factories (one per backend; `protocol=` selects the wire where a backend speaks both):
58
+ "deepseek", "kimi", "qwen", "glm", "minimax", "gemini", "ollama",
57
59
  "RetryConfig", "CircuitBreaker", "normalize_tool_call", "parse_tool_arguments",
58
60
  "TokenUsage", "ProviderToolSpec",
59
61
  "to_anthropic_content", "to_anthropic_messages",