deepstrike 0.2.30__tar.gz → 0.2.32__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 (186) hide show
  1. {deepstrike-0.2.30 → deepstrike-0.2.32}/Cargo.lock +5 -5
  2. {deepstrike-0.2.30 → deepstrike-0.2.32}/Cargo.toml +2 -2
  3. {deepstrike-0.2.30 → deepstrike-0.2.32}/PKG-INFO +1 -1
  4. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/quota.rs +8 -0
  5. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/kernel.rs +129 -0
  6. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +26 -2
  7. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +36 -1
  8. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/signals/queue.rs +28 -0
  9. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/signals/router.rs +49 -0
  10. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/signal.rs +20 -0
  11. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-py/src/lib.rs +32 -1
  12. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/__init__.py +46 -0
  13. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/harness/harness.py +14 -34
  14. deepstrike-0.2.32/deepstrike/harness/judge.py +98 -0
  15. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/anthropic.py +34 -18
  16. deepstrike-0.2.32/deepstrike/providers/anthropic_compatible.py +38 -0
  17. deepstrike-0.2.32/deepstrike/providers/deepseek.py +132 -0
  18. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/factories.py +14 -4
  19. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/gemini.py +48 -4
  20. deepstrike-0.2.32/deepstrike/providers/glm.py +62 -0
  21. deepstrike-0.2.32/deepstrike/providers/kimi.py +140 -0
  22. deepstrike-0.2.32/deepstrike/providers/minimax.py +115 -0
  23. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/openai.py +135 -28
  24. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/openai_responses.py +25 -5
  25. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/qwen.py +147 -31
  26. deepstrike-0.2.32/deepstrike/providers/vendor_profiles.py +133 -0
  27. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/__init__.py +56 -0
  28. deepstrike-0.2.32/deepstrike/runtime/event_stream.py +80 -0
  29. deepstrike-0.2.32/deepstrike/runtime/reactive_session.py +187 -0
  30. deepstrike-0.2.32/deepstrike/runtime/run_group.py +134 -0
  31. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/runner.py +88 -5
  32. deepstrike-0.2.32/deepstrike/runtime/turn_policy.py +103 -0
  33. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/signals/gateway.py +14 -3
  34. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/signals/types.py +13 -2
  35. {deepstrike-0.2.30 → deepstrike-0.2.32}/pyproject.toml +1 -1
  36. deepstrike-0.2.30/deepstrike/providers/deepseek.py +0 -238
  37. deepstrike-0.2.30/deepstrike/providers/glm.py +0 -50
  38. deepstrike-0.2.30/deepstrike/providers/kimi.py +0 -49
  39. deepstrike-0.2.30/deepstrike/providers/minimax.py +0 -260
  40. {deepstrike-0.2.30 → deepstrike-0.2.32}/README.md +0 -0
  41. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/Cargo.toml +0 -0
  42. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/compression.rs +0 -0
  43. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/config.rs +0 -0
  44. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
  45. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/manager.rs +0 -0
  46. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/mod.rs +0 -0
  47. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/partitions.rs +0 -0
  48. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/pressure.rs +0 -0
  49. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/renderer.rs +0 -0
  50. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/renewal.rs +0 -0
  51. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/sections.rs +0 -0
  52. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
  53. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
  54. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
  55. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/task_state.rs +0 -0
  56. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/text.rs +0 -0
  57. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
  58. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/audit.rs +0 -0
  59. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
  60. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/mod.rs +0 -0
  61. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/permission.rs +0 -0
  62. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
  63. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
  64. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
  65. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
  66. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/governance/veto.rs +0 -0
  67. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/harness/eval.rs +0 -0
  68. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/harness/mod.rs +0 -0
  69. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/lib.rs +0 -0
  70. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/curator.rs +0 -0
  71. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/durable.rs +0 -0
  72. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
  73. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
  74. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/mod.rs +0 -0
  75. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
  76. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
  77. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/session.rs +0 -0
  78. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
  79. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
  80. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/memory/working.rs +0 -0
  81. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/mm/handle.rs +0 -0
  82. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/mm/memory.rs +0 -0
  83. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/mm/mod.rs +0 -0
  84. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
  85. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
  86. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
  87. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +0 -0
  88. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
  89. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/proc/mod.rs +0 -0
  90. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
  91. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
  92. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
  93. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
  94. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/session.rs +0 -0
  95. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
  96. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
  97. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
  98. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
  99. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
  100. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
  101. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +0 -0
  102. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
  103. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
  104. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
  105. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +0 -0
  106. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +0 -0
  107. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
  108. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/signals/attention.rs +0 -0
  109. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/signals/mod.rs +0 -0
  110. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
  111. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/agent.rs +0 -0
  112. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/capability.rs +0 -0
  113. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/contract.rs +0 -0
  114. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/error.rs +0 -0
  115. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/message.rs +0 -0
  116. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/milestone.rs +0 -0
  117. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/mod.rs +0 -0
  118. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/model.rs +0 -0
  119. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/policy.rs +0 -0
  120. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/result.rs +0 -0
  121. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/skill.rs +0 -0
  122. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-core/src/types/task.rs +0 -0
  123. {deepstrike-0.2.30 → deepstrike-0.2.32}/crates/deepstrike-py/Cargo.toml +0 -0
  124. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/__init__.py +0 -0
  125. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/contract.py +0 -0
  126. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/handoff.py +0 -0
  127. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/harness.py +0 -0
  128. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/modes.py +0 -0
  129. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/collaboration/pool.py +0 -0
  130. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/governance.py +0 -0
  131. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/harness/__init__.py +0 -0
  132. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/kernel/__init__.py +0 -0
  133. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/knowledge/__init__.py +0 -0
  134. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/knowledge/source.py +0 -0
  135. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/memory/__init__.py +0 -0
  136. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/memory/agent.py +0 -0
  137. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/memory/in_memory_store.py +0 -0
  138. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/memory/protocols.py +0 -0
  139. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/memory/working.py +0 -0
  140. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/__init__.py +0 -0
  141. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/base.py +0 -0
  142. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/ollama.py +0 -0
  143. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/replay.py +0 -0
  144. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/replay_validator.py +0 -0
  145. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/providers/stream.py +0 -0
  146. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/archive.py +0 -0
  147. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/credential_vault.py +0 -0
  148. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/eval.py +0 -0
  149. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/execution_plane.py +0 -0
  150. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/facade.py +0 -0
  151. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/filtered_plane.py +0 -0
  152. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/kernel_event_log.py +0 -0
  153. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/kernel_step.py +0 -0
  154. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/large_result_spool.py +0 -0
  155. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
  156. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/os_profile.py +0 -0
  157. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/os_snapshot.py +0 -0
  158. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/output_schema.py +0 -0
  159. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/process_sandbox_plane.py +0 -0
  160. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/provider_replay.py +0 -0
  161. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/reducers.py +0 -0
  162. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/remote_vpc_plane.py +0 -0
  163. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/replay_fixture.py +0 -0
  164. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/replay_provider.py +0 -0
  165. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/replay_sanitize.py +0 -0
  166. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/session_log.py +0 -0
  167. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/session_repair.py +0 -0
  168. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
  169. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/workflow_control_flow.py +0 -0
  170. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/workflow_store.py +0 -0
  171. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/runtime/worktree_plane.py +0 -0
  172. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/safety/__init__.py +0 -0
  173. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/safety/permissions.py +0 -0
  174. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/signals/__init__.py +0 -0
  175. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/signals/scheduled.py +0 -0
  176. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/skills/__init__.py +0 -0
  177. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/skills/registry.py +0 -0
  178. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/skills/watcher.py +0 -0
  179. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/__init__.py +0 -0
  180. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/builtin/__init__.py +0 -0
  181. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/builtin/read_file.py +0 -0
  182. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/errors.py +0 -0
  183. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/execution.py +0 -0
  184. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/tools/registry.py +0 -0
  185. {deepstrike-0.2.30 → deepstrike-0.2.32}/deepstrike/types/__init__.py +0 -0
  186. {deepstrike-0.2.30 → deepstrike-0.2.32}/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.30"
197
+ version = "0.2.32"
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.30"
209
+ version = "0.2.32"
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.30"
221
+ version = "0.2.32"
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.30"
232
+ version = "0.2.32"
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.30"
266
+ version = "0.2.32"
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.30"
6
+ version = "0.2.32"
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.30" }
12
+ deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.32" }
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.30
3
+ Version: 0.2.32
4
4
  Requires-Dist: httpx>=0.27
5
5
  Requires-Dist: pyyaml>=6.0
6
6
  Requires-Dist: anyio>=4.0
@@ -17,8 +17,15 @@ use serde::{Deserialize, Serialize};
17
17
  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
18
18
  pub struct ResourceQuota {
19
19
  /// Max sub-agents in the `Running` state at once. Further `Spawn`s are denied while at cap.
20
+ /// *Instantaneous* — vehicle-scoped (cannot span stateless replicas; spec §2.5).
20
21
  #[serde(default, skip_serializing_if = "Option::is_none")]
21
22
  pub max_concurrent_subagents: Option<u32>,
23
+ /// L1 (RunGroup): max sub-agents spawned *cumulatively* over the whole governance domain. Unlike
24
+ /// `max_concurrent_subagents` this counts every spawn ever (running + completed), seeded across
25
+ /// members via `group_spawns_base`, so it spans N stateless top-level runs. A hard `Deny` at cap
26
+ /// (a completed sibling never frees a cumulative slot).
27
+ #[serde(default, skip_serializing_if = "Option::is_none")]
28
+ pub max_total_subagents: Option<u32>,
22
29
  /// Max sub-agent nesting depth (direct children of the root loop are depth 1).
23
30
  #[serde(default, skip_serializing_if = "Option::is_none")]
24
31
  pub max_spawn_depth: Option<u32>,
@@ -37,6 +44,7 @@ impl ResourceQuota {
37
44
  /// Whether any limit is actually set (used to short-circuit the gate when fully open).
38
45
  pub fn is_open(&self) -> bool {
39
46
  self.max_concurrent_subagents.is_none()
47
+ && self.max_total_subagents.is_none()
40
48
  && self.max_spawn_depth.is_none()
41
49
  && self.memory_writes_per_window.is_none()
42
50
  && self.max_workflow_nodes.is_none()
@@ -150,6 +150,16 @@ pub struct RunConfig {
150
150
  pub scheduler_max_wall_ms: Option<u64>,
151
151
  #[serde(default, skip_serializing_if = "Option::is_none")]
152
152
  pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
153
+ /// L1 (RunGroup): cumulative tokens already spent by *other* members of this run's governance
154
+ /// domain, seeded at boot so the run-level token cap (`max_total_tokens`) is enforced across the
155
+ /// whole group, not per-vehicle. `None`/0 ⇒ no group (N=1) ⇒ pre-L1 per-kernel behavior.
156
+ #[serde(default, skip_serializing_if = "Option::is_none")]
157
+ pub group_tokens_base: Option<u64>,
158
+ /// L1 (RunGroup): sub-agents already spawned by *other* members of this run's governance domain,
159
+ /// seeded at boot so `ResourceQuota::max_total_subagents` is enforced across the whole group.
160
+ /// `None`/0 ⇒ no group (N=1) ⇒ pre-L1 per-vehicle behavior.
161
+ #[serde(default, skip_serializing_if = "Option::is_none")]
162
+ pub group_spawns_base: Option<u32>,
153
163
  }
154
164
 
155
165
  /// Build a [`GovernancePipeline`](crate::governance::pipeline::GovernancePipeline) from the ABI policy
@@ -766,6 +776,12 @@ impl KernelRuntime {
766
776
  self.sm.is_terminal()
767
777
  }
768
778
 
779
+ /// L1 (RunGroup): this vehicle's cumulative sub-agent spawns this run, read back by the SDK at run
780
+ /// end to charge the group ledger (so the next member's cumulative spawn cap is seeded correctly).
781
+ pub fn local_subagents_spawned(&self) -> u32 {
782
+ self.sm.local_subagents_spawned()
783
+ }
784
+
769
785
  pub fn step(&mut self, input: KernelInput) -> KernelStep {
770
786
  let action = match input.event {
771
787
  KernelInputEvent::SetTools { tools } => {
@@ -884,6 +900,8 @@ impl KernelRuntime {
884
900
  attention_max_queue_size,
885
901
  scheduler_max_wall_ms,
886
902
  resource_quota,
903
+ group_tokens_base,
904
+ group_spawns_base,
887
905
  } = config;
888
906
  if let Some(tools) = tools {
889
907
  self.sm.tools = tools;
@@ -924,6 +942,12 @@ impl KernelRuntime {
924
942
  if let Some(quota) = resource_quota {
925
943
  self.sm.set_resource_quota(quota);
926
944
  }
945
+ if let Some(base) = group_tokens_base {
946
+ self.sm.seed_group_budget(base);
947
+ }
948
+ if let Some(base) = group_spawns_base {
949
+ self.sm.seed_group_spawns(base);
950
+ }
927
951
  return KernelStep::empty(self.sm.take_observations());
928
952
  }
929
953
  KernelInputEvent::SetAttentionPolicy { max_queue_size } => {
@@ -1396,6 +1420,111 @@ mod tests {
1396
1420
  assert!(!runtime.state_machine().is_suspended());
1397
1421
  }
1398
1422
 
1423
+ #[test]
1424
+ fn group_budget_base_enforces_shared_token_cap() {
1425
+ use crate::types::message::{Content, Message, ToolCall, ToolResult};
1426
+
1427
+ // Drive one tool-calling turn under a 100-token run cap, with `group_base` already spent by
1428
+ // other members of the governance domain. The token-budget axis is checked after the tool
1429
+ // results, against `group_base + local`.
1430
+ fn run_one_turn(group_base: Option<u64>) -> KernelStep {
1431
+ let mut runtime = KernelRuntime::new(LoopPolicy {
1432
+ max_total_tokens: 100,
1433
+ ..LoopPolicy::default()
1434
+ });
1435
+ runtime.step(KernelInput::new(KernelInputEvent::ConfigureRun {
1436
+ config: RunConfig { group_tokens_base: group_base, ..RunConfig::default() },
1437
+ }));
1438
+ runtime.step(KernelInput::new(KernelInputEvent::StartRun {
1439
+ task: RuntimeTask::new("task"),
1440
+ run_spec: None,
1441
+ }));
1442
+ let mut msg = Message::assistant("");
1443
+ msg.token_count = Some(10); // this vehicle's local spend this turn
1444
+ msg.tool_calls.push(ToolCall {
1445
+ id: "c1".into(),
1446
+ name: "echo".into(),
1447
+ arguments: serde_json::json!({}),
1448
+ });
1449
+ runtime.step(KernelInput::new(KernelInputEvent::ProviderResult {
1450
+ message: msg,
1451
+ observed_input_tokens: None,
1452
+ observed_output_tokens: None,
1453
+ now_ms: None,
1454
+ }));
1455
+ runtime.step(KernelInput::new(KernelInputEvent::ToolResults {
1456
+ results: vec![ToolResult {
1457
+ call_id: "c1".into(),
1458
+ output: Content::Text("ok".into()),
1459
+ is_error: false,
1460
+ is_fatal: false,
1461
+ error_kind: None,
1462
+ token_count: None,
1463
+ }],
1464
+ }))
1465
+ }
1466
+
1467
+ let exceeded = |step: &KernelStep| {
1468
+ step.observations.iter().any(|o| {
1469
+ matches!(o, KernelObservation::BudgetExceeded { budget, .. } if budget == "token_budget")
1470
+ })
1471
+ };
1472
+
1473
+ // Group already spent 95; this vehicle's 10 pushes the domain to 105 > 100 → shared cap fires.
1474
+ assert!(
1475
+ exceeded(&run_one_turn(Some(95))),
1476
+ "group token budget must span the whole domain"
1477
+ );
1478
+ // N=1 / no group (base 0): local 10 is far under the cap → pre-L1 behavior unchanged.
1479
+ assert!(
1480
+ !exceeded(&run_one_turn(None)),
1481
+ "no group seed ⇒ per-vehicle budget, well under cap"
1482
+ );
1483
+ }
1484
+
1485
+ #[test]
1486
+ fn group_spawns_base_enforces_cumulative_spawn_cap() {
1487
+ use crate::governance::quota::ResourceQuota;
1488
+ use crate::types::agent::{AgentIdentity, AgentRole, AgentRunSpec};
1489
+
1490
+ // Cumulative cap of 2 sub-agents across the domain. Other members already spawned 2 (seeded),
1491
+ // so this vehicle's very first spawn is denied — the cap spans the whole group.
1492
+ let mut runtime = KernelRuntime::new(LoopPolicy::default());
1493
+ runtime.step(KernelInput::new(KernelInputEvent::ConfigureRun {
1494
+ config: RunConfig {
1495
+ resource_quota: Some(ResourceQuota {
1496
+ max_total_subagents: Some(2),
1497
+ ..ResourceQuota::default()
1498
+ }),
1499
+ group_spawns_base: Some(2),
1500
+ ..RunConfig::default()
1501
+ },
1502
+ }));
1503
+ runtime.step(KernelInput::new(KernelInputEvent::StartRun {
1504
+ task: RuntimeTask::new("task"),
1505
+ run_spec: None,
1506
+ }));
1507
+ runtime.state_machine_mut().take_observations();
1508
+
1509
+ let spec = AgentRunSpec::new(
1510
+ AgentIdentity::sub_agent("worker", "worker-session"),
1511
+ AgentRole::Implement,
1512
+ "do work",
1513
+ );
1514
+ let step = runtime.step(KernelInput::new(KernelInputEvent::SpawnSubAgent {
1515
+ spec,
1516
+ parent_session_id: "parent-session".to_string(),
1517
+ }));
1518
+
1519
+ // Denied: domain already at the cumulative cap → rolled back, no process registered.
1520
+ assert!(matches!(
1521
+ step.actions.as_slice(),
1522
+ [KernelAction::CallProvider { .. }]
1523
+ ));
1524
+ assert!(runtime.state_machine().agent_process("worker").is_none());
1525
+ assert_eq!(runtime.local_subagents_spawned(), 0);
1526
+ }
1527
+
1399
1528
  #[test]
1400
1529
  fn default_runtime_leaves_spawn_unquota_ed() {
1401
1530
  use crate::types::agent::{AgentIdentity, AgentRole, AgentRunSpec};
@@ -85,6 +85,9 @@ impl LoopStateMachine {
85
85
  // M4/G5 token headroom: the run-level cumulative token cap is always set on the scheduler
86
86
  // budget, so a coordinator always sees how many tokens remain (the "use 10k tokens" signal).
87
87
  let tokens_max = self.policy.max_total_tokens;
88
+ // L1: report the governance domain's cumulative token spend (this vehicle + other members'
89
+ // seeded base) so a coordinator scales submissions to the group's remaining headroom.
90
+ let tokens_used = self.total_tokens.saturating_add(self.group_tokens_base);
88
91
  Some(crate::orchestration::workflow::WorkflowBudget {
89
92
  nodes_used,
90
93
  nodes_max,
@@ -93,9 +96,9 @@ impl LoopStateMachine {
93
96
  max_concurrent_subagents,
94
97
  concurrency_remaining: max_concurrent_subagents
95
98
  .map(|m| m.saturating_sub(running_subagents)),
96
- tokens_used: self.total_tokens,
99
+ tokens_used,
97
100
  tokens_max: Some(tokens_max),
98
- tokens_remaining: Some(tokens_max.saturating_sub(self.total_tokens)),
101
+ tokens_remaining: Some(tokens_max.saturating_sub(tokens_used)),
99
102
  })
100
103
  }
101
104
 
@@ -122,6 +125,17 @@ impl LoopStateMachine {
122
125
  };
123
126
  }
124
127
  }
128
+ if let Some(max) = quota.max_total_subagents {
129
+ // L1: cumulative across the whole governance domain (other members' seeded base + this
130
+ // vehicle's spawns ever). A hard Deny — a completed sibling never frees a cumulative slot.
131
+ let total = self.group_spawns_base + self.local_subagents_spawned();
132
+ if total >= max {
133
+ return Disposition::Deny {
134
+ stage: "quota",
135
+ reason: format!("max_total_subagents={max} reached ({total} spawned in domain)"),
136
+ };
137
+ }
138
+ }
125
139
  if let Some(max) = quota.max_spawn_depth {
126
140
  // Sub-agents currently parent to the root task (depth 1). Nested spawning would
127
141
  // generalize this to the spawning task's lineage depth.
@@ -161,6 +175,16 @@ impl LoopStateMachine {
161
175
  return Disposition::Defer { slot: running };
162
176
  }
163
177
  }
178
+ if let Some(max) = quota.max_total_subagents {
179
+ // Cumulative cap is permanent (no sibling completion frees a slot) → hard Deny, not Defer.
180
+ let total = self.group_spawns_base + self.local_subagents_spawned();
181
+ if total >= max {
182
+ return Disposition::Deny {
183
+ stage: "quota",
184
+ reason: format!("max_total_subagents={max} reached ({total} spawned in domain)"),
185
+ };
186
+ }
187
+ }
164
188
  if let Some(max) = quota.max_spawn_depth {
165
189
  let depth = 1u32;
166
190
  if depth > max {
@@ -157,6 +157,14 @@ pub struct LoopStateMachine {
157
157
  pub observations: Vec<KernelObservation>,
158
158
  pub(super) policy: LoopPolicy,
159
159
  pub(super) total_tokens: u64,
160
+ /// L1 (RunGroup): cumulative tokens spent by *other* members of this run's governance domain,
161
+ /// seeded at boot via `seed_group_budget`. The run-level token cap is enforced against
162
+ /// `group_tokens_base + total_tokens` so the budget spans the whole group, not one vehicle.
163
+ /// 0 (default) ⇒ no group (N=1) ⇒ pre-L1 per-kernel behavior (byte-identical).
164
+ pub(super) group_tokens_base: u64,
165
+ /// L1 (RunGroup): sub-agents spawned by *other* members of this run's governance domain, seeded
166
+ /// at boot. `max_total_subagents` is enforced against `group_spawns_base + local spawns`. 0 ⇒ N=1.
167
+ pub(super) group_spawns_base: u32,
160
168
  /// When set, the next LLM call strips tools to force a text response,
161
169
  /// then terminates with this reason once the response arrives.
162
170
  pub(super) pending_termination: Option<TerminationReason>,
@@ -234,6 +242,8 @@ impl LoopStateMachine {
234
242
  observations: Vec::new(),
235
243
  policy,
236
244
  total_tokens: 0,
245
+ group_tokens_base: 0,
246
+ group_spawns_base: 0,
237
247
  pending_termination: None,
238
248
  session_history_baseline: 0,
239
249
  checkpoint: TurnCheckpoint::default(),
@@ -293,7 +303,9 @@ impl LoopStateMachine {
293
303
  fn root_tcb(&self) -> Tcb {
294
304
  let mut tcb = Tcb::root("root", self.policy.clone());
295
305
  tcb.budget.turns = self.turn;
296
- tcb.budget.total_tokens = self.total_tokens;
306
+ // L1: the token-budget axis is evaluated against the whole governance domain's cumulative
307
+ // spend (this vehicle's `total_tokens` plus other members' `group_tokens_base`).
308
+ tcb.budget.total_tokens = self.total_tokens.saturating_add(self.group_tokens_base);
297
309
  tcb.budget.started_at_ms = self.started_at_ms;
298
310
  tcb.state = self.lifecycle();
299
311
  tcb
@@ -318,6 +330,26 @@ impl LoopStateMachine {
318
330
  self.resource_quota = Some(quota);
319
331
  }
320
332
 
333
+ /// L1 (RunGroup): seed the cumulative tokens already spent by other members of this run's
334
+ /// governance domain. The run-level token cap is then enforced against the group total. Seeding
335
+ /// 0 (the default) preserves pre-L1 per-vehicle behavior.
336
+ pub fn seed_group_budget(&mut self, tokens_spent: u64) {
337
+ self.group_tokens_base = tokens_spent;
338
+ }
339
+
340
+ /// L1 (RunGroup): seed the sub-agents already spawned by other members of this run's governance
341
+ /// domain. `max_total_subagents` is then enforced against the group total. 0 ⇒ pre-L1 behavior.
342
+ pub fn seed_group_spawns(&mut self, subagents_spawned: u32) {
343
+ self.group_spawns_base = subagents_spawned;
344
+ }
345
+
346
+ /// L1: this vehicle's cumulative sub-agent spawns this run — every child task ever registered in
347
+ /// the `TaskTable` (running + completed), distinct from the *instantaneous* running count. Used
348
+ /// for the cumulative spawn quota and read back by the SDK to charge the group ledger at run end.
349
+ pub fn local_subagents_spawned(&self) -> u32 {
350
+ self.tasks.all().iter().filter(|t| t.proc.is_some()).count() as u32
351
+ }
352
+
321
353
  /// Install the long-term memory policy (`set_memory_policy`). Once set it gates `write_memory`
322
354
  /// validation and bounds `query_memory` retrieval breadth. Not setting it (the default)
323
355
  /// preserves pre-policy behavior.
@@ -723,6 +755,9 @@ impl LoopStateMachine {
723
755
  observations: Vec::new(),
724
756
  policy,
725
757
  total_tokens: snap.total_tokens,
758
+ // Re-seeded from the replayed `ConfigureRun` (strategy is data, not serialized state).
759
+ group_tokens_base: 0,
760
+ group_spawns_base: 0,
726
761
  pending_termination: None,
727
762
  session_history_baseline: 0,
728
763
  checkpoint: TurnCheckpoint::default(),
@@ -65,6 +65,34 @@ impl SignalQueue {
65
65
  self.heap.pop().map(|ps| ps.signal)
66
66
  }
67
67
 
68
+ /// Pop the highest-priority signal visible to `recipient`: broadcasts
69
+ /// (`signal.recipient == None`) plus those addressed to `recipient`.
70
+ /// Non-matching entries are retained (re-pushed) so heap ordering is preserved.
71
+ /// `recipient == None` ⇒ no filtering (drains anything; back-compat with `pop`).
72
+ pub(super) fn pop_for(&mut self, recipient: Option<&str>) -> Option<RuntimeSignal> {
73
+ let Some(recipient) = recipient else {
74
+ return self.pop();
75
+ };
76
+ let mut skipped: Vec<PrioritizedSignal> = Vec::new();
77
+ let mut found = None;
78
+ while let Some(ps) = self.heap.pop() {
79
+ let visible = ps
80
+ .signal
81
+ .recipient
82
+ .as_deref()
83
+ .is_none_or(|r| r == recipient);
84
+ if visible {
85
+ found = Some(ps.signal);
86
+ break;
87
+ }
88
+ skipped.push(ps);
89
+ }
90
+ for ps in skipped {
91
+ self.heap.push(ps);
92
+ }
93
+ found
94
+ }
95
+
68
96
  pub(super) fn len(&self) -> usize {
69
97
  self.heap.len()
70
98
  }
@@ -54,6 +54,12 @@ impl SignalRouter {
54
54
  self.queue.pop()
55
55
  }
56
56
 
57
+ /// Pull the next queued signal visible to `recipient` (broadcasts plus signals
58
+ /// addressed to it); other recipients' signals stay queued. `None` ⇒ no filter.
59
+ pub fn next_for(&mut self, recipient: Option<&str>) -> Option<RuntimeSignal> {
60
+ self.queue.pop_for(recipient)
61
+ }
62
+
57
63
  /// Number of queued signals.
58
64
  pub fn depth(&self) -> usize {
59
65
  self.queue.len()
@@ -139,6 +145,49 @@ mod tests {
139
145
  assert_eq!(router.ingest(s2, false), SignalDisposition::Dropped);
140
146
  }
141
147
 
148
+ #[test]
149
+ fn next_for_drains_recipient_plus_broadcast_only() {
150
+ let mut router = SignalRouter::new(100);
151
+ let to_a = RuntimeSignal::new(SignalSource::Gateway, SignalType::Event, Urgency::Normal, "a")
152
+ .with_recipient("sess-a")
153
+ .with_timestamp(1);
154
+ let to_b = RuntimeSignal::new(SignalSource::Gateway, SignalType::Event, Urgency::Normal, "b")
155
+ .with_recipient("sess-b")
156
+ .with_timestamp(2);
157
+ let broadcast =
158
+ RuntimeSignal::new(SignalSource::Cron, SignalType::Job, Urgency::Normal, "all")
159
+ .with_timestamp(3);
160
+ router.ingest(to_a, false);
161
+ router.ingest(to_b, false);
162
+ router.ingest(broadcast, false);
163
+
164
+ // Puller "sess-a" sees only its own + the broadcast, never sess-b's.
165
+ let first = router.next_for(Some("sess-a")).unwrap();
166
+ let second = router.next_for(Some("sess-a")).unwrap();
167
+ let mut got: Vec<_> = [first.summary.as_str(), second.summary.as_str()]
168
+ .iter()
169
+ .map(|s| s.to_string())
170
+ .collect();
171
+ got.sort();
172
+ assert_eq!(got, vec!["a".to_string(), "all".to_string()]);
173
+ assert!(router.next_for(Some("sess-a")).is_none());
174
+
175
+ // sess-b's signal is still queued for its own puller.
176
+ assert_eq!(router.next_for(Some("sess-b")).unwrap().summary.as_str(), "b");
177
+ }
178
+
179
+ #[test]
180
+ fn next_for_none_drains_anything() {
181
+ let mut router = SignalRouter::new(100);
182
+ router.ingest(
183
+ RuntimeSignal::new(SignalSource::Gateway, SignalType::Event, Urgency::Normal, "x")
184
+ .with_recipient("sess-a"),
185
+ false,
186
+ );
187
+ // No recipient filter ⇒ back-compat: drains the addressed signal too.
188
+ assert_eq!(router.next_for(None).unwrap().summary.as_str(), "x");
189
+ }
190
+
142
191
  #[test]
143
192
  fn clear_dedup_allows_reingest() {
144
193
  let mut router = SignalRouter::new(100);
@@ -12,6 +12,14 @@ pub struct RuntimeSignal {
12
12
  pub payload: serde_json::Value,
13
13
  #[serde(default, skip_serializing_if = "Option::is_none")]
14
14
  pub dedupe_key: Option<CompactString>,
15
+ /// Target a specific agent/session loop. `None` ⇒ broadcast (drained by any puller).
16
+ /// The canonical key is the recipient's `sessionId` (see R1 / L0).
17
+ #[serde(default, skip_serializing_if = "Option::is_none")]
18
+ pub recipient: Option<CompactString>,
19
+ /// Optional topic for pub/sub-style fan-out. Carried through for subscribers;
20
+ /// multi-subscriber routing is deferred (field-only this phase).
21
+ #[serde(default, skip_serializing_if = "Option::is_none")]
22
+ pub topic: Option<CompactString>,
15
23
  pub timestamp_ms: u64,
16
24
  }
17
25
 
@@ -56,6 +64,8 @@ impl RuntimeSignal {
56
64
  summary: summary.into(),
57
65
  payload: serde_json::Value::Null,
58
66
  dedupe_key: None,
67
+ recipient: None,
68
+ topic: None,
59
69
  timestamp_ms: 0,
60
70
  }
61
71
  }
@@ -65,6 +75,16 @@ impl RuntimeSignal {
65
75
  self
66
76
  }
67
77
 
78
+ pub fn with_recipient(mut self, recipient: impl Into<CompactString>) -> Self {
79
+ self.recipient = Some(recipient.into());
80
+ self
81
+ }
82
+
83
+ pub fn with_topic(mut self, topic: impl Into<CompactString>) -> Self {
84
+ self.topic = Some(topic.into());
85
+ self
86
+ }
87
+
68
88
  pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
69
89
  self.payload = payload;
70
90
  self
@@ -91,13 +91,17 @@ struct RuntimeSignal {
91
91
  #[pyo3(get, set)]
92
92
  dedupe_key: Option<String>,
93
93
  #[pyo3(get, set)]
94
+ recipient: Option<String>,
95
+ #[pyo3(get, set)]
96
+ topic: Option<String>,
97
+ #[pyo3(get, set)]
94
98
  timestamp_ms: f64,
95
99
  }
96
100
 
97
101
  #[pymethods]
98
102
  impl RuntimeSignal {
99
103
  #[new]
100
- #[pyo3(signature = (source, urgency, summary, signal_type="event", payload="null", dedupe_key=None, timestamp_ms=0.0))]
104
+ #[pyo3(signature = (source, urgency, summary, signal_type="event", payload="null", dedupe_key=None, timestamp_ms=0.0, recipient=None, topic=None))]
101
105
  fn new(
102
106
  source: String,
103
107
  urgency: String,
@@ -106,6 +110,8 @@ impl RuntimeSignal {
106
110
  payload: &str,
107
111
  dedupe_key: Option<String>,
108
112
  timestamp_ms: f64,
113
+ recipient: Option<String>,
114
+ topic: Option<String>,
109
115
  ) -> Self {
110
116
  Self {
111
117
  id: uuid::Uuid::new_v4().to_string(),
@@ -115,6 +121,8 @@ impl RuntimeSignal {
115
121
  signal_type: signal_type.into(),
116
122
  payload: payload.into(),
117
123
  dedupe_key,
124
+ recipient,
125
+ topic,
118
126
  timestamp_ms,
119
127
  }
120
128
  }
@@ -154,6 +162,12 @@ impl RuntimeSignal {
154
162
  if let Some(ref key) = self.dedupe_key {
155
163
  sig = sig.with_dedupe(key.as_str());
156
164
  }
165
+ if let Some(ref recipient) = self.recipient {
166
+ sig = sig.with_recipient(recipient.as_str());
167
+ }
168
+ if let Some(ref topic) = self.topic {
169
+ sig = sig.with_topic(topic.as_str());
170
+ }
157
171
  sig
158
172
  }
159
173
 
@@ -183,6 +197,8 @@ impl RuntimeSignal {
183
197
  summary: s.summary.to_string(),
184
198
  payload: serde_json::to_string(&s.payload).unwrap_or_else(|_| "null".into()),
185
199
  dedupe_key: s.dedupe_key.as_ref().map(|k| k.to_string()),
200
+ recipient: s.recipient.as_ref().map(|r| r.to_string()),
201
+ topic: s.topic.as_ref().map(|t| t.to_string()),
186
202
  timestamp_ms: s.timestamp_ms as f64,
187
203
  }
188
204
  }
@@ -865,6 +881,11 @@ impl KernelRuntime {
865
881
  self.inner.state_machine().turn
866
882
  }
867
883
 
884
+ /// L1 (RunGroup): cumulative sub-agent spawns this run, for charging the group ledger at run end.
885
+ fn local_subagents_spawned(&self) -> u32 {
886
+ self.inner.local_subagents_spawned()
887
+ }
888
+
868
889
  fn recovery_content_bytes(&self) -> u32 {
869
890
  let sm = self.inner.state_machine();
870
891
  let tokens = sm.ctx.config.recovery_content_tokens(sm.ctx.max_tokens);
@@ -922,6 +943,16 @@ impl SignalRouter {
922
943
  self.inner.next().as_ref().map(RuntimeSignal::from_rust)
923
944
  }
924
945
 
946
+ /// Pull the next queued signal visible to `recipient` (broadcasts plus signals
947
+ /// addressed to it); other recipients' signals stay queued. None ⇒ no filter.
948
+ #[pyo3(signature = (recipient=None))]
949
+ fn next_for(&mut self, recipient: Option<String>) -> Option<RuntimeSignal> {
950
+ self.inner
951
+ .next_for(recipient.as_deref())
952
+ .as_ref()
953
+ .map(RuntimeSignal::from_rust)
954
+ }
955
+
925
956
  fn depth(&self) -> usize {
926
957
  self.inner.depth()
927
958
  }
@@ -38,6 +38,29 @@ from deepstrike.runtime import (
38
38
  InMemorySessionLog,
39
39
  FileSessionLog,
40
40
  SessionLog,
41
+ RunGroup,
42
+ GroupBudgetStore,
43
+ GroupLedger,
44
+ GroupMember,
45
+ InMemoryGroupBudgetStore,
46
+ SessionLogGroupBudgetStore,
47
+ BlackboardEvent,
48
+ EventStream,
49
+ EventViewer,
50
+ InMemoryEventStream,
51
+ is_visible_to,
52
+ PeerView,
53
+ react_by_mention,
54
+ director_driven,
55
+ round_robin,
56
+ first_non_empty,
57
+ union,
58
+ ReactiveSession,
59
+ ReactivePeerSpec,
60
+ Reaction,
61
+ ReactorContext,
62
+ ReactorTurn,
63
+ read_recent_tool,
41
64
  ProviderReplay,
42
65
  FilteredExecutionPlane,
43
66
  SubAgentOrchestrator,
@@ -137,6 +160,29 @@ __all__ = [
137
160
  "InMemorySessionLog",
138
161
  "FileSessionLog",
139
162
  "SessionLog",
163
+ "RunGroup",
164
+ "GroupBudgetStore",
165
+ "GroupLedger",
166
+ "GroupMember",
167
+ "InMemoryGroupBudgetStore",
168
+ "SessionLogGroupBudgetStore",
169
+ "BlackboardEvent",
170
+ "EventStream",
171
+ "EventViewer",
172
+ "InMemoryEventStream",
173
+ "is_visible_to",
174
+ "PeerView",
175
+ "react_by_mention",
176
+ "director_driven",
177
+ "round_robin",
178
+ "first_non_empty",
179
+ "union",
180
+ "ReactiveSession",
181
+ "ReactivePeerSpec",
182
+ "Reaction",
183
+ "ReactorContext",
184
+ "ReactorTurn",
185
+ "read_recent_tool",
140
186
  "ProviderReplay",
141
187
  "ProviderReplay",
142
188
  "ProviderReplay",