deepstrike 0.2.16__tar.gz → 0.2.17__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.
- {deepstrike-0.2.16 → deepstrike-0.2.17}/Cargo.lock +5 -5
- {deepstrike-0.2.16 → deepstrike-0.2.17}/Cargo.toml +2 -2
- {deepstrike-0.2.16 → deepstrike-0.2.17}/PKG-INFO +1 -1
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +13 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/workflow/run.rs +225 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/event_log.rs +2 -1
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/kernel.rs +96 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +151 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +146 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +351 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +153 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +872 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/process.rs +195 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +165 -0
- deepstrike-0.2.16/crates/deepstrike-core/src/scheduler/state_machine_tests.rs → deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +175 -0
- deepstrike-0.2.17/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +352 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/scheduler/tcb.rs +11 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/syscall/mod.rs +7 -4
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/__init__.py +20 -2
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/__init__.py +24 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/execution_plane.py +5 -1
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/process_sandbox_plane.py +12 -7
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/runner.py +289 -43
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/sub_agent_orchestrator.py +65 -18
- deepstrike-0.2.17/deepstrike/runtime/workflow_control_flow.py +94 -0
- deepstrike-0.2.17/deepstrike/runtime/workflow_store.py +56 -0
- deepstrike-0.2.17/deepstrike/runtime/worktree_plane.py +101 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/tools/registry.py +12 -2
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/types/agent.py +207 -36
- {deepstrike-0.2.16 → deepstrike-0.2.17}/pyproject.toml +1 -1
- deepstrike-0.2.16/crates/deepstrike-core/src/scheduler/state_machine.rs +0 -2190
- {deepstrike-0.2.16 → deepstrike-0.2.17}/README.md +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/Cargo.toml +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/compression.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/config.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/manager.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/partitions.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/pressure.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/renderer.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/renewal.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/sections.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/task_state.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/text.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/audit.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/permission.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/quota.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/governance/veto.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/harness/eval_pipeline.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/harness/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/lib.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/curator.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/durable.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/session.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/memory/working.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/mm/handle.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/mm/memory.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/mm/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/proc/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/session.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/signals/attention.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/signals/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/signals/queue.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/signals/router.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/agent.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/capability.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/contract.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/error.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/message.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/milestone.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/mod.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/model.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/policy.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/result.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/signal.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/skill.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/types/task.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-py/Cargo.toml +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-py/src/lib.rs +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/contract.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/handoff.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/harness.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/modes.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/collaboration/pool.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/governance.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/harness/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/harness/harness.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/kernel/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/knowledge/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/knowledge/source.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/memory/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/memory/agent.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/memory/protocols.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/memory/working.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/anthropic.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/base.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/deepseek.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/gemini.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/glm.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/kimi.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/minimax.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/ollama.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/openai.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/qwen.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/replay.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/replay_validator.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/providers/stream.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/archive.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/credential_vault.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/filtered_plane.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/kernel_event_log.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/kernel_step.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/large_result_spool.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/os_profile.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/os_snapshot.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/output_schema.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/provider_replay.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/reducers.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/remote_vpc_plane.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/replay_sanitize.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/session_log.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/runtime/session_repair.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/safety/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/safety/permissions.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/signals/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/signals/gateway.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/signals/scheduled.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/signals/types.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/skills/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/skills/registry.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/skills/watcher.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/tools/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/tools/builtin/__init__.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/tools/builtin/read_file.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/tools/execution.py +0 -0
- {deepstrike-0.2.16 → deepstrike-0.2.17}/deepstrike/types/__init__.py +0 -0
|
@@ -194,7 +194,7 @@ dependencies = [
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "deepstrike-core"
|
|
197
|
-
version = "0.2.
|
|
197
|
+
version = "0.2.17"
|
|
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.
|
|
209
|
+
version = "0.2.17"
|
|
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.
|
|
221
|
+
version = "0.2.17"
|
|
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.
|
|
232
|
+
version = "0.2.17"
|
|
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.
|
|
266
|
+
version = "0.2.17"
|
|
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.
|
|
6
|
+
version = "0.2.17"
|
|
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.
|
|
12
|
+
deepstrike-core = { path = "crates/deepstrike-core", version = "0.2.17" }
|
|
13
13
|
notify = "6"
|
|
14
14
|
serde = { version = "1", features = ["derive"] }
|
|
15
15
|
serde_json = "1"
|
{deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/workflow/mod.rs
RENAMED
|
@@ -102,6 +102,12 @@ pub struct WorkflowNode {
|
|
|
102
102
|
/// Control-flow kind. Default `Spawn` (run once).
|
|
103
103
|
#[serde(default, skip_serializing_if = "is_spawn")]
|
|
104
104
|
pub kind: NodeKind,
|
|
105
|
+
/// M4/G5: optional per-node cumulative token cap. The kernel carries it to the spawn descriptor;
|
|
106
|
+
/// the SDK sets the node's child-run `max_total_tokens` to it, so an expensive node self-terminates
|
|
107
|
+
/// at the cap (the "use N tokens" budget, applied per node). Additive: omitted on the wire when
|
|
108
|
+
/// `None`.
|
|
109
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
110
|
+
pub token_budget: Option<u64>,
|
|
105
111
|
/// Indices into [`WorkflowSpec::nodes`] this node depends on.
|
|
106
112
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
107
113
|
pub depends_on: Vec<usize>,
|
|
@@ -128,10 +134,17 @@ impl WorkflowNode {
|
|
|
128
134
|
trust: NodeTrust::Trusted,
|
|
129
135
|
output_schema: None,
|
|
130
136
|
kind: NodeKind::Spawn,
|
|
137
|
+
token_budget: None,
|
|
131
138
|
depends_on: Vec::new(),
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
/// M4/G5: cap this node's child run at `tokens` cumulative tokens.
|
|
143
|
+
pub fn with_token_budget(mut self, tokens: u64) -> Self {
|
|
144
|
+
self.token_budget = Some(tokens);
|
|
145
|
+
self
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
/// Make this a loop node: re-run the agent up to `max_iters` times before completing.
|
|
136
149
|
/// Dependents wait for the whole loop to finish.
|
|
137
150
|
pub fn with_loop(mut self, max_iters: usize) -> Self {
|
{deepstrike-0.2.16 → deepstrike-0.2.17}/crates/deepstrike-core/src/orchestration/workflow/run.rs
RENAMED
|
@@ -62,6 +62,24 @@ pub struct WorkflowSpawnInfo {
|
|
|
62
62
|
/// (entrant / spawn / loop / classify) node. Additive ABI: omitted on the wire when `None`.
|
|
63
63
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
64
64
|
pub judge_match: Option<JudgeMatch>,
|
|
65
|
+
/// Present only for a [`NodeKind::Loop`] iteration spawn (A#2 v2): the loop's `max_iters`. It
|
|
66
|
+
/// both *marks* the spawn as a loop iteration — so the SDK knows to solicit and report a
|
|
67
|
+
/// `loop_continue` stop signal from the agent — and gives the cap for the agent's prompt. `None`
|
|
68
|
+
/// for every non-loop node. Mirrors how `reducer` / `judge_match` distinguish reduce / judge
|
|
69
|
+
/// spawns. Additive ABI: omitted on the wire when `None`.
|
|
70
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
71
|
+
pub loop_max_iters: Option<usize>,
|
|
72
|
+
/// Present only for a [`NodeKind::Classify`] spawn (A#2): the branch labels the classifier must
|
|
73
|
+
/// choose among. Non-empty *marks* the spawn as a classifier — the SDK instructs the agent to
|
|
74
|
+
/// pick exactly one label and reports it in the result's `classify_branch`. Empty for every
|
|
75
|
+
/// non-classify node. Additive ABI: omitted on the wire when empty.
|
|
76
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
77
|
+
pub classify_labels: Vec<String>,
|
|
78
|
+
/// M4/G5: the node's per-node cumulative token cap, if set. The SDK sets the child run's
|
|
79
|
+
/// `max_total_tokens` to this so the node self-terminates at the cap. Additive ABI: omitted when
|
|
80
|
+
/// `None`.
|
|
81
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
82
|
+
pub token_budget: Option<u64>,
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
fn default_trust() -> String {
|
|
@@ -102,6 +120,18 @@ pub struct WorkflowBudget {
|
|
|
102
120
|
/// how many of a submission's nodes can spawn *immediately* rather than deferring for a slot.
|
|
103
121
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
104
122
|
pub concurrency_remaining: Option<usize>,
|
|
123
|
+
/// M4/G5: cumulative tokens spent across the run so far (the scheduler's `total_tokens`).
|
|
124
|
+
/// `#[serde(default)]` keeps older JSON (without this field) deserializing to 0 — additive ABI.
|
|
125
|
+
#[serde(default)]
|
|
126
|
+
pub tokens_used: u64,
|
|
127
|
+
/// M4/G5: `SchedulerBudget::max_total_tokens` — the run's cumulative token cap.
|
|
128
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
129
|
+
pub tokens_max: Option<u64>,
|
|
130
|
+
/// M4/G5: `tokens_max - tokens_used` (saturating) — how many tokens remain before the run-level
|
|
131
|
+
/// token budget terminates the workflow. Lets a coordinator scale its next submission to token
|
|
132
|
+
/// headroom (the analogue of "use 10k tokens").
|
|
133
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
134
|
+
pub tokens_remaining: Option<u64>,
|
|
105
135
|
}
|
|
106
136
|
|
|
107
137
|
fn role_label(role: AgentRole) -> &'static str {
|
|
@@ -307,6 +337,17 @@ impl WorkflowRun {
|
|
|
307
337
|
),
|
|
308
338
|
_ => (None, Vec::new()),
|
|
309
339
|
};
|
|
340
|
+
// A#2 v2 / classify: surface the control-flow kind so the SDK can solicit + report the
|
|
341
|
+
// matching result signal (`loop_continue` / `classify_branch`), mirroring how `reducer` /
|
|
342
|
+
// `judge_match` distinguish reduce / judge spawns.
|
|
343
|
+
let loop_max_iters = match &n.kind {
|
|
344
|
+
NodeKind::Loop { max_iters } => Some(*max_iters),
|
|
345
|
+
_ => None,
|
|
346
|
+
};
|
|
347
|
+
let classify_labels = match &n.kind {
|
|
348
|
+
NodeKind::Classify { branches } => branches.iter().map(|b| b.label.clone()).collect(),
|
|
349
|
+
_ => Vec::new(),
|
|
350
|
+
};
|
|
310
351
|
WorkflowSpawnInfo {
|
|
311
352
|
agent_id: self.current_agent_id(node),
|
|
312
353
|
goal: n.task.goal.clone(),
|
|
@@ -319,6 +360,9 @@ impl WorkflowRun {
|
|
|
319
360
|
reducer,
|
|
320
361
|
input_agent_ids,
|
|
321
362
|
judge_match: self.judge_matches.get(&node).cloned(),
|
|
363
|
+
loop_max_iters,
|
|
364
|
+
classify_labels,
|
|
365
|
+
token_budget: n.token_budget,
|
|
322
366
|
}
|
|
323
367
|
}
|
|
324
368
|
|
|
@@ -640,6 +684,7 @@ impl WorkflowRun {
|
|
|
640
684
|
|
|
641
685
|
pub fn submit_nodes(&mut self, nodes: Vec<WorkflowNode>) -> Vec<usize> {
|
|
642
686
|
let base = self.nodes.len();
|
|
687
|
+
let batch_len = nodes.len();
|
|
643
688
|
let mut ids = Vec::with_capacity(nodes.len());
|
|
644
689
|
for (offset, mut node) in nodes.into_iter().enumerate() {
|
|
645
690
|
let deps: Vec<usize> = node
|
|
@@ -649,6 +694,21 @@ impl WorkflowRun {
|
|
|
649
694
|
.map(|&d| base + d)
|
|
650
695
|
.collect();
|
|
651
696
|
node.depends_on = deps.clone();
|
|
697
|
+
// A#2/G2: a submitted `Classify` node's branch indices are *batch-relative* — they point
|
|
698
|
+
// at other nodes in this same submission, whose absolute graph index the submitter cannot
|
|
699
|
+
// know. Remap each branch node index `d` (0-based within the batch) to its absolute index
|
|
700
|
+
// `base + d`, dropping out-of-range references. Mirrors the `depends_on` batch-relative
|
|
701
|
+
// convention; without it a runtime-submitted classifier would prune the wrong nodes.
|
|
702
|
+
if let NodeKind::Classify { branches } = &mut node.kind {
|
|
703
|
+
for branch in branches.iter_mut() {
|
|
704
|
+
branch.nodes = branch
|
|
705
|
+
.nodes
|
|
706
|
+
.iter()
|
|
707
|
+
.filter(|&&d| d < batch_len)
|
|
708
|
+
.map(|&d| base + d)
|
|
709
|
+
.collect();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
652
712
|
let idx = self.graph.add(node.task.clone(), deps);
|
|
653
713
|
debug_assert_eq!(idx, self.nodes.len(), "graph/nodes index drift");
|
|
654
714
|
self.nodes.push(node);
|
|
@@ -702,6 +762,21 @@ impl WorkflowRun {
|
|
|
702
762
|
(completed, failed)
|
|
703
763
|
}
|
|
704
764
|
|
|
765
|
+
/// #2-B abort: outcome when the workflow is preempted — every node that has not already
|
|
766
|
+
/// `Completed` counts as `failed` (running / ready / pending all abort). Used to emit a terminal
|
|
767
|
+
/// `WorkflowCompleted` when an `InterruptNow` tears the whole `WorkflowRun` down.
|
|
768
|
+
pub fn abort_outcome(&self) -> (Vec<String>, Vec<String>) {
|
|
769
|
+
let mut completed = Vec::new();
|
|
770
|
+
let mut failed = Vec::new();
|
|
771
|
+
for i in 0..self.graph.len() {
|
|
772
|
+
match self.graph.get(i).map(|n| n.status) {
|
|
773
|
+
Some(TaskStatus::Completed) => completed.push(node_agent_id(i)),
|
|
774
|
+
_ => failed.push(node_agent_id(i)),
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
(completed, failed)
|
|
778
|
+
}
|
|
779
|
+
|
|
705
780
|
/// Total node count.
|
|
706
781
|
pub fn len(&self) -> usize {
|
|
707
782
|
self.graph.len()
|
|
@@ -1018,6 +1093,110 @@ mod tests {
|
|
|
1018
1093
|
assert!(run.is_complete(), "submitted loop ran its 2 iterations then finished");
|
|
1019
1094
|
}
|
|
1020
1095
|
|
|
1096
|
+
#[test]
|
|
1097
|
+
fn submitted_tournament_runs_bracket_then_promotes_submitted_dependent() {
|
|
1098
|
+
// M2: an agent can submit a Tournament *controller* (plus a dependent) at runtime. The
|
|
1099
|
+
// controller expands into entrant children + a judge via the same bracket machinery, and the
|
|
1100
|
+
// dependent's batch-relative `depends_on` links it to the submitted controller.
|
|
1101
|
+
use crate::orchestration::workflow::{WorkflowNode, WorkflowSpec};
|
|
1102
|
+
use crate::types::agent::AgentRole;
|
|
1103
|
+
|
|
1104
|
+
let spec = WorkflowSpec::new(vec![WorkflowNode::new(
|
|
1105
|
+
RuntimeTask::new("root"),
|
|
1106
|
+
AgentRole::Implement,
|
|
1107
|
+
)]);
|
|
1108
|
+
let mut run = WorkflowRun::new(&spec, "sess").unwrap();
|
|
1109
|
+
let id0 = run.current_agent_id(0);
|
|
1110
|
+
run.mark_spawned(0, &id0);
|
|
1111
|
+
run.record_completion(&id0, done());
|
|
1112
|
+
|
|
1113
|
+
// Submit [tournament@batch0, dependent@batch1 depends_on [0]] (batch-relative).
|
|
1114
|
+
let ids = run.submit_nodes(vec![
|
|
1115
|
+
WorkflowNode::new(RuntimeTask::new("pick best"), AgentRole::Plan)
|
|
1116
|
+
.with_tournament(vec![RuntimeTask::new("x"), RuntimeTask::new("y")]),
|
|
1117
|
+
WorkflowNode::new(RuntimeTask::new("use winner"), AgentRole::Implement)
|
|
1118
|
+
.with_depends_on(vec![0]),
|
|
1119
|
+
]);
|
|
1120
|
+
assert_eq!(ids, vec![1, 2], "appended controller=1, dependent=2");
|
|
1121
|
+
|
|
1122
|
+
// Controller (node 1) expands into 2 entrant children (3,4); spawns no agent of its own.
|
|
1123
|
+
let entrants = spawn_round(&mut run);
|
|
1124
|
+
let entrant_nodes: Vec<usize> = entrants.iter().map(|(n, _)| *n).collect();
|
|
1125
|
+
assert_eq!(entrant_nodes, vec![3, 4], "two entrant children appended after the dependent");
|
|
1126
|
+
for (_, id) in &entrants {
|
|
1127
|
+
run.record_completion(id, done());
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// One judge over the two entrants; dependent (node 2) gated until the bracket resolves.
|
|
1131
|
+
let r1 = spawn_round(&mut run);
|
|
1132
|
+
assert_eq!(r1.len(), 1, "one judge for two entrants");
|
|
1133
|
+
let jm = run.spawn_info(r1[0].0).judge_match.expect("judge carries a match");
|
|
1134
|
+
assert_eq!(jm, JudgeMatch { left: node_agent_id(3), right: node_agent_id(4) });
|
|
1135
|
+
|
|
1136
|
+
// Entrant 3 wins → controller completes with the champion → dependent unblocks.
|
|
1137
|
+
run.record_completion(&r1[0].1, judge_done(&node_agent_id(3)));
|
|
1138
|
+
assert_eq!(run.ready_batch(), vec![2], "submitted dependent unblocks after the bracket");
|
|
1139
|
+
let last = spawn_round(&mut run);
|
|
1140
|
+
assert_eq!(last, vec![(2, node_agent_id(2))]);
|
|
1141
|
+
run.record_completion(&last[0].1, done());
|
|
1142
|
+
assert!(run.is_complete());
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
#[test]
|
|
1146
|
+
fn submitted_classify_remaps_branch_indices_and_prunes() {
|
|
1147
|
+
// M2: a submitted Classify node's branch `nodes` are batch-relative; `submit_nodes` remaps
|
|
1148
|
+
// them to absolute indices so the chosen branch runs and the rest are pruned. Without the
|
|
1149
|
+
// remap a runtime-submitted classifier would prune the wrong nodes.
|
|
1150
|
+
use crate::orchestration::workflow::{ClassifyBranch, NodeKind, WorkflowNode, WorkflowSpec};
|
|
1151
|
+
use crate::types::agent::AgentRole;
|
|
1152
|
+
|
|
1153
|
+
let spec = WorkflowSpec::new(vec![WorkflowNode::new(
|
|
1154
|
+
RuntimeTask::new("root"),
|
|
1155
|
+
AgentRole::Implement,
|
|
1156
|
+
)]);
|
|
1157
|
+
let mut run = WorkflowRun::new(&spec, "sess").unwrap();
|
|
1158
|
+
let id0 = run.current_agent_id(0);
|
|
1159
|
+
run.mark_spawned(0, &id0);
|
|
1160
|
+
run.record_completion(&id0, done());
|
|
1161
|
+
|
|
1162
|
+
// Submit [classify@batch0 (a→[1] b→[2]), branchA@batch1 dep[0], branchB@batch2 dep[0]].
|
|
1163
|
+
let ids = run.submit_nodes(vec![
|
|
1164
|
+
WorkflowNode::new(RuntimeTask::new("route"), AgentRole::Plan).with_classify(vec![
|
|
1165
|
+
ClassifyBranch { label: "a".into(), nodes: vec![1] },
|
|
1166
|
+
ClassifyBranch { label: "b".into(), nodes: vec![2] },
|
|
1167
|
+
]),
|
|
1168
|
+
WorkflowNode::new(RuntimeTask::new("branch-a"), AgentRole::Implement)
|
|
1169
|
+
.with_depends_on(vec![0]),
|
|
1170
|
+
WorkflowNode::new(RuntimeTask::new("branch-b"), AgentRole::Implement)
|
|
1171
|
+
.with_depends_on(vec![0]),
|
|
1172
|
+
]);
|
|
1173
|
+
assert_eq!(ids, vec![1, 2, 3], "classify=1, branchA=2, branchB=3");
|
|
1174
|
+
|
|
1175
|
+
// Branch indices were remapped batch-relative → absolute: a→[2], b→[3].
|
|
1176
|
+
if let NodeKind::Classify { branches } = &run.nodes[1].kind {
|
|
1177
|
+
assert_eq!(branches[0].nodes, vec![2], "branch a remapped to absolute node 2");
|
|
1178
|
+
assert_eq!(branches[1].nodes, vec![3], "branch b remapped to absolute node 3");
|
|
1179
|
+
} else {
|
|
1180
|
+
panic!("node 1 should be a classify node");
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Classifier picks "a" → branch-a (node 2) runs, branch-b (node 3) is pruned/failed.
|
|
1184
|
+
let r = spawn_round(&mut run);
|
|
1185
|
+
assert_eq!(r, vec![(1, node_agent_id(1))], "classifier runs first");
|
|
1186
|
+
run.record_completion(&r[0].1, LoopResult { classify_branch: Some("a".into()), ..done() });
|
|
1187
|
+
|
|
1188
|
+
assert_eq!(run.ready_batch(), vec![2], "only branch a is enabled");
|
|
1189
|
+
let (_c, failed) = run.outcome();
|
|
1190
|
+
assert!(failed.contains(&node_agent_id(3)), "branch b pruned/failed");
|
|
1191
|
+
|
|
1192
|
+
let last = spawn_round(&mut run);
|
|
1193
|
+
assert_eq!(last, vec![(2, node_agent_id(2))]);
|
|
1194
|
+
run.record_completion(&last[0].1, done());
|
|
1195
|
+
assert!(run.is_complete());
|
|
1196
|
+
let (completed, _f) = run.outcome();
|
|
1197
|
+
assert!(completed.contains(&node_agent_id(1)) && completed.contains(&node_agent_id(2)));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1021
1200
|
#[test]
|
|
1022
1201
|
fn loop_node_iterates_with_distinct_ids_then_promotes_dependent() {
|
|
1023
1202
|
use crate::orchestration::workflow::{WorkflowNode, WorkflowSpec};
|
|
@@ -1185,6 +1364,52 @@ mod tests {
|
|
|
1185
1364
|
assert_eq!(t.model_hint, None);
|
|
1186
1365
|
}
|
|
1187
1366
|
|
|
1367
|
+
#[test]
|
|
1368
|
+
fn spawn_info_carries_loop_and_classify_hints() {
|
|
1369
|
+
use crate::orchestration::workflow::{ClassifyBranch, WorkflowNode, WorkflowSpec};
|
|
1370
|
+
use crate::types::agent::AgentRole;
|
|
1371
|
+
|
|
1372
|
+
let spec = WorkflowSpec::new(vec![
|
|
1373
|
+
// 0: loop node → descriptor carries the cap so the SDK knows to solicit `loop_continue`.
|
|
1374
|
+
WorkflowNode::new(RuntimeTask::new("refine"), AgentRole::Implement).with_loop(3),
|
|
1375
|
+
// 1: classify node → descriptor carries the branch labels so the SDK can instruct + report.
|
|
1376
|
+
WorkflowNode::new(RuntimeTask::new("route"), AgentRole::Plan).with_classify(vec![
|
|
1377
|
+
ClassifyBranch { label: "bug".into(), nodes: vec![] },
|
|
1378
|
+
ClassifyBranch { label: "feature".into(), nodes: vec![] },
|
|
1379
|
+
]),
|
|
1380
|
+
// 2: plain spawn → neither hint present.
|
|
1381
|
+
WorkflowNode::new(RuntimeTask::new("act"), AgentRole::Implement),
|
|
1382
|
+
]);
|
|
1383
|
+
let run = WorkflowRun::new(&spec, "sess").unwrap();
|
|
1384
|
+
|
|
1385
|
+
let l = run.spawn_info(0);
|
|
1386
|
+
assert_eq!(l.loop_max_iters, Some(3));
|
|
1387
|
+
assert!(l.classify_labels.is_empty());
|
|
1388
|
+
assert_eq!(l.token_budget, None, "no token budget unless set");
|
|
1389
|
+
|
|
1390
|
+
let c = run.spawn_info(1);
|
|
1391
|
+
assert_eq!(c.classify_labels, vec!["bug".to_string(), "feature".to_string()]);
|
|
1392
|
+
assert_eq!(c.loop_max_iters, None);
|
|
1393
|
+
|
|
1394
|
+
let s = run.spawn_info(2);
|
|
1395
|
+
assert_eq!(s.loop_max_iters, None);
|
|
1396
|
+
assert!(s.classify_labels.is_empty());
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
#[test]
|
|
1400
|
+
fn spawn_info_carries_token_budget() {
|
|
1401
|
+
use crate::orchestration::workflow::{WorkflowNode, WorkflowSpec};
|
|
1402
|
+
use crate::types::agent::AgentRole;
|
|
1403
|
+
|
|
1404
|
+
let spec = WorkflowSpec::new(vec![
|
|
1405
|
+
WorkflowNode::new(RuntimeTask::new("expensive"), AgentRole::Implement).with_token_budget(10_000),
|
|
1406
|
+
WorkflowNode::new(RuntimeTask::new("plain"), AgentRole::Implement),
|
|
1407
|
+
]);
|
|
1408
|
+
let run = WorkflowRun::new(&spec, "sess").unwrap();
|
|
1409
|
+
assert_eq!(run.spawn_info(0).token_budget, Some(10_000));
|
|
1410
|
+
assert_eq!(run.spawn_info(1).token_budget, None);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1188
1413
|
// ── Tournament node (A#2) ───────────────────────────────────────────────────────────────────
|
|
1189
1414
|
|
|
1190
1415
|
use crate::orchestration::workflow::{NodeKind, WorkflowNode, WorkflowSpec};
|
|
@@ -88,7 +88,7 @@ pub fn category_for_kind(kind: &str) -> KernelEventCategory {
|
|
|
88
88
|
| "context_renewed"
|
|
89
89
|
| "large_result_spooled" => KernelEventCategory::Mm,
|
|
90
90
|
"agent_process_changed" | "agent_spawned" | "workflow_batch_spawned"
|
|
91
|
-
| "workflow_completed" => KernelEventCategory::Proc,
|
|
91
|
+
| "workflow_completed" | "agent_preempted" => KernelEventCategory::Proc,
|
|
92
92
|
"signal_disposed" => KernelEventCategory::Ipc,
|
|
93
93
|
"memory_written" | "memory_queried" | "memory_validation_failed" => KernelEventCategory::Mm,
|
|
94
94
|
_ => KernelEventCategory::Sched,
|
|
@@ -110,6 +110,7 @@ pub const KERNEL_OBSERVATION_KINDS: &[&str] = &[
|
|
|
110
110
|
"agent_process_changed",
|
|
111
111
|
"workflow_batch_spawned",
|
|
112
112
|
"workflow_completed",
|
|
113
|
+
"agent_preempted",
|
|
113
114
|
"tool_gated",
|
|
114
115
|
"signal_disposed",
|
|
115
116
|
"budget_exceeded",
|
|
@@ -270,6 +270,21 @@ pub enum KernelInputEvent {
|
|
|
270
270
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
271
271
|
submitter_agent_id: Option<String>,
|
|
272
272
|
},
|
|
273
|
+
/// M5/G1: an agent authors a whole `WorkflowSpec` (the article's "model writes its own harness").
|
|
274
|
+
/// The agent-reachable analogue of the host-only `LoadWorkflow`: **bootstraps** the DAG when no
|
|
275
|
+
/// workflow is active, else **flattens** the spec's nodes onto the running DAG (bootstrap-or-flatten,
|
|
276
|
+
/// one kernel / one quota — never a workflow stack). Gated by `Syscall::LoadWorkflow`. Additive ABI:
|
|
277
|
+
/// a brand-new variant, byte-identical on the wire for SDKs that never send it.
|
|
278
|
+
SubmitWorkflow {
|
|
279
|
+
spec: crate::orchestration::workflow::WorkflowSpec,
|
|
280
|
+
/// Used only on bootstrap (no workflow active) to seed child session ids; ignored on flatten.
|
|
281
|
+
#[serde(default)]
|
|
282
|
+
parent_session_id: String,
|
|
283
|
+
/// G1: the authoring node's agent id (flatten case) — a quarantined author's nodes are coerced
|
|
284
|
+
/// quarantined. Additive: omitted (top-level bootstrap) → `None` → the run's own trust applies.
|
|
285
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
286
|
+
submitter_agent_id: Option<String>,
|
|
287
|
+
},
|
|
273
288
|
/// Feed long-term memory entries into the knowledge partition (page-in).
|
|
274
289
|
/// SDK performs retrieval I/O; kernel only applies the result.
|
|
275
290
|
PageIn {
|
|
@@ -480,6 +495,17 @@ pub enum KernelObservation {
|
|
|
480
495
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
481
496
|
failed: Vec<String>,
|
|
482
497
|
},
|
|
498
|
+
/// #2-B: a high-urgency `InterruptNow` signal preempted in-flight work. The kernel has already
|
|
499
|
+
/// marked these agents `Done(UserAbort)` and reclaimed the root to reason about the interrupt; the
|
|
500
|
+
/// SDK must ABORT the listed in-flight child runs and discard their results (do NOT feed their
|
|
501
|
+
/// `SubAgentCompleted`). Additive variant (`agent_preempted`) — byte-identical for SDKs that never
|
|
502
|
+
/// receive it.
|
|
503
|
+
AgentPreempted {
|
|
504
|
+
turn: u32,
|
|
505
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
506
|
+
agent_ids: Vec<String>,
|
|
507
|
+
reason: String,
|
|
508
|
+
},
|
|
483
509
|
/// A tool call needs user approval (governance `AskUser`). Not blocked by the
|
|
484
510
|
/// kernel — the SDK must obtain approval before executing the named call.
|
|
485
511
|
ToolGated {
|
|
@@ -866,6 +892,21 @@ impl KernelRuntime {
|
|
|
866
892
|
}
|
|
867
893
|
return KernelStep::single(action, self.sm.take_observations());
|
|
868
894
|
}
|
|
895
|
+
KernelInputEvent::SubmitWorkflow {
|
|
896
|
+
spec,
|
|
897
|
+
parent_session_id,
|
|
898
|
+
submitter_agent_id,
|
|
899
|
+
} => {
|
|
900
|
+
let action = self.sm.submit_workflow(
|
|
901
|
+
spec,
|
|
902
|
+
&parent_session_id,
|
|
903
|
+
submitter_agent_id.as_deref(),
|
|
904
|
+
);
|
|
905
|
+
if matches!(action, LoopAction::AwaitingResume) {
|
|
906
|
+
return KernelStep::empty(self.sm.take_observations());
|
|
907
|
+
}
|
|
908
|
+
return KernelStep::single(action, self.sm.take_observations());
|
|
909
|
+
}
|
|
869
910
|
KernelInputEvent::SetMemoryPolicy {
|
|
870
911
|
memory_path,
|
|
871
912
|
stale_warning_days,
|
|
@@ -1922,6 +1963,61 @@ mod tests {
|
|
|
1922
1963
|
)));
|
|
1923
1964
|
}
|
|
1924
1965
|
|
|
1966
|
+
#[test]
|
|
1967
|
+
fn submit_workflow_input_bootstraps_a_dag_over_the_abi() {
|
|
1968
|
+
// M5/G1: a top-level agent authors a whole spec over the ABI (full serde round-trip of
|
|
1969
|
+
// SubmitWorkflow + WorkflowSpec) with no workflow active → the kernel bootstraps and drives it.
|
|
1970
|
+
use crate::orchestration::workflow::{WorkflowNode, WorkflowSpec};
|
|
1971
|
+
use crate::types::agent::AgentRole;
|
|
1972
|
+
use crate::types::result::{LoopResult, SubAgentResult, TerminationReason};
|
|
1973
|
+
|
|
1974
|
+
let mut runtime = KernelRuntime::new(LoopPolicy::default());
|
|
1975
|
+
runtime.step(KernelInput::new(KernelInputEvent::StartRun {
|
|
1976
|
+
task: RuntimeTask::new("parent task"),
|
|
1977
|
+
run_spec: None,
|
|
1978
|
+
}));
|
|
1979
|
+
runtime.state_machine_mut().take_observations();
|
|
1980
|
+
|
|
1981
|
+
// No LoadWorkflow first — the agent itself authors the spec.
|
|
1982
|
+
let spec = WorkflowSpec::new(vec![WorkflowNode::new(
|
|
1983
|
+
RuntimeTask::new("authored root"),
|
|
1984
|
+
AgentRole::Implement,
|
|
1985
|
+
)]);
|
|
1986
|
+
let event = KernelInputEvent::SubmitWorkflow {
|
|
1987
|
+
spec,
|
|
1988
|
+
parent_session_id: "sess".to_string(),
|
|
1989
|
+
submitter_agent_id: None,
|
|
1990
|
+
};
|
|
1991
|
+
let json = serde_json::to_string(&event).expect("serialize");
|
|
1992
|
+
let parsed: KernelInputEvent = serde_json::from_str(&json).expect("deserialize");
|
|
1993
|
+
let step = runtime.step(KernelInput::new(parsed));
|
|
1994
|
+
// The authored node bootstraps as wf-node0 in a workflow batch.
|
|
1995
|
+
assert!(step.observations.iter().any(|o| matches!(
|
|
1996
|
+
o,
|
|
1997
|
+
KernelObservation::WorkflowBatchSpawned { nodes, .. }
|
|
1998
|
+
if nodes.len() == 1 && nodes[0].agent_id == "wf-node0" && nodes[0].goal == "authored root"
|
|
1999
|
+
)));
|
|
2000
|
+
|
|
2001
|
+
let step = runtime.step(KernelInput::new(KernelInputEvent::SubAgentCompleted {
|
|
2002
|
+
result: SubAgentResult {
|
|
2003
|
+
agent_id: compact_str::CompactString::new("wf-node0"),
|
|
2004
|
+
result: LoopResult {
|
|
2005
|
+
termination: TerminationReason::Completed,
|
|
2006
|
+
final_message: None,
|
|
2007
|
+
turns_used: 1,
|
|
2008
|
+
total_tokens_used: 1,
|
|
2009
|
+
loop_continue: None,
|
|
2010
|
+
classify_branch: None,
|
|
2011
|
+
tournament_winner: None,
|
|
2012
|
+
},
|
|
2013
|
+
},
|
|
2014
|
+
}));
|
|
2015
|
+
assert!(step.observations.iter().any(|o| matches!(
|
|
2016
|
+
o,
|
|
2017
|
+
KernelObservation::WorkflowCompleted { completed, .. } if completed.len() == 1
|
|
2018
|
+
)));
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1925
2021
|
#[test]
|
|
1926
2022
|
fn load_workflow_resumes_from_completed_nodes() {
|
|
1927
2023
|
use crate::orchestration::workflow::fanout_synthesize;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
//! Capability management impl for [`super::LoopStateMachine`].
|
|
2
|
+
|
|
3
|
+
use super::{KernelObservation, LoopStateMachine};
|
|
4
|
+
|
|
5
|
+
impl LoopStateMachine {
|
|
6
|
+
/// Drop capability leases whose expiry turn has passed. Runs at the head of
|
|
7
|
+
/// every event so expired temporary capabilities are unmounted promptly.
|
|
8
|
+
pub(super) fn sweep_expired_leases(&mut self) {
|
|
9
|
+
let current_turn = self.turn;
|
|
10
|
+
let mut to_remove = Vec::new();
|
|
11
|
+
for cap in self.ctx.capabilities.capabilities() {
|
|
12
|
+
if let Some(ref lease) = cap.lease {
|
|
13
|
+
if current_turn >= lease.expires_at_turn {
|
|
14
|
+
to_remove.push((cap.kind, cap.id.to_string()));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (kind, id) in to_remove {
|
|
19
|
+
self.unmount_capability(kind, &id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Emit a `CapabilityChanged` observation for the current turn.
|
|
24
|
+
/// Single construction site for all mount/unmount/replace/pin changes.
|
|
25
|
+
#[allow(clippy::too_many_arguments)]
|
|
26
|
+
pub(super) fn push_capability_change(
|
|
27
|
+
&mut self,
|
|
28
|
+
added: Vec<String>,
|
|
29
|
+
removed: Vec<String>,
|
|
30
|
+
change_kind: &str,
|
|
31
|
+
capability_id: Option<String>,
|
|
32
|
+
version: Option<String>,
|
|
33
|
+
mounted_by: Option<String>,
|
|
34
|
+
mount_reason: Option<String>,
|
|
35
|
+
) {
|
|
36
|
+
self.observations.push(KernelObservation::CapabilityChanged {
|
|
37
|
+
turn: self.turn,
|
|
38
|
+
added,
|
|
39
|
+
removed,
|
|
40
|
+
change_kind: Some(change_kind.to_string()),
|
|
41
|
+
capability_id,
|
|
42
|
+
version,
|
|
43
|
+
mounted_by,
|
|
44
|
+
mount_reason,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn execute_capability_command(&mut self, cmd: crate::types::capability::CapabilityCommand) {
|
|
49
|
+
use crate::types::capability::CapabilityCommand;
|
|
50
|
+
match cmd {
|
|
51
|
+
CapabilityCommand::Mount {
|
|
52
|
+
capability,
|
|
53
|
+
mounted_by,
|
|
54
|
+
mount_reason,
|
|
55
|
+
} => {
|
|
56
|
+
self.mount_capability(capability, mounted_by, mount_reason);
|
|
57
|
+
}
|
|
58
|
+
CapabilityCommand::Unmount { kind, id } => {
|
|
59
|
+
self.unmount_capability(kind, &id);
|
|
60
|
+
}
|
|
61
|
+
CapabilityCommand::Replace {
|
|
62
|
+
old_kind,
|
|
63
|
+
old_id,
|
|
64
|
+
new_capability,
|
|
65
|
+
} => {
|
|
66
|
+
let new_id = new_capability.id.to_string();
|
|
67
|
+
let version = new_capability.version.clone();
|
|
68
|
+
let old_kind_str = old_kind.label();
|
|
69
|
+
let new_kind_str = new_capability.kind.label();
|
|
70
|
+
|
|
71
|
+
self.ctx.capabilities.remove(old_kind, &old_id);
|
|
72
|
+
self.ctx.capabilities.upsert(new_capability);
|
|
73
|
+
|
|
74
|
+
self.push_capability_change(
|
|
75
|
+
vec![format!("{}:{}", new_kind_str, new_id)],
|
|
76
|
+
vec![format!("{}:{}", old_kind_str, old_id)],
|
|
77
|
+
"replace",
|
|
78
|
+
Some(new_id),
|
|
79
|
+
version,
|
|
80
|
+
None,
|
|
81
|
+
None,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
CapabilityCommand::Pin { kind, id } => {
|
|
85
|
+
let version = self
|
|
86
|
+
.ctx
|
|
87
|
+
.capabilities
|
|
88
|
+
.get_mut(kind, &id)
|
|
89
|
+
.and_then(|c| c.version.clone());
|
|
90
|
+
if let Some(cap) = self.ctx.capabilities.get_mut(kind, &id) {
|
|
91
|
+
cap.is_pinned = true;
|
|
92
|
+
self.push_capability_change(
|
|
93
|
+
vec![],
|
|
94
|
+
vec![],
|
|
95
|
+
"pin",
|
|
96
|
+
Some(id),
|
|
97
|
+
version,
|
|
98
|
+
None,
|
|
99
|
+
None,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub fn mount_capability(
|
|
107
|
+
&mut self,
|
|
108
|
+
mut descriptor: crate::types::capability::CapabilityDescriptor,
|
|
109
|
+
mounted_by: Option<String>,
|
|
110
|
+
mount_reason: Option<String>,
|
|
111
|
+
) {
|
|
112
|
+
if mounted_by.is_some() {
|
|
113
|
+
descriptor.mounted_by = mounted_by.clone();
|
|
114
|
+
}
|
|
115
|
+
if mount_reason.is_some() {
|
|
116
|
+
descriptor.mount_reason = mount_reason.clone();
|
|
117
|
+
}
|
|
118
|
+
let id = descriptor.id.to_string();
|
|
119
|
+
let kind_str = descriptor.kind.label();
|
|
120
|
+
let version = descriptor.version.clone();
|
|
121
|
+
self.ctx.capabilities.upsert(descriptor);
|
|
122
|
+
self.push_capability_change(
|
|
123
|
+
vec![format!("{}:{}", kind_str, id)],
|
|
124
|
+
vec![],
|
|
125
|
+
"mount",
|
|
126
|
+
Some(id),
|
|
127
|
+
version,
|
|
128
|
+
mounted_by,
|
|
129
|
+
mount_reason,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub fn unmount_capability(&mut self, kind: crate::types::capability::CapabilityKind, id: &str) {
|
|
134
|
+
let version = self
|
|
135
|
+
.ctx
|
|
136
|
+
.capabilities
|
|
137
|
+
.get_mut(kind, id)
|
|
138
|
+
.and_then(|c| c.version.clone());
|
|
139
|
+
self.ctx.capabilities.remove(kind, id);
|
|
140
|
+
let kind_str = kind.label();
|
|
141
|
+
self.push_capability_change(
|
|
142
|
+
vec![],
|
|
143
|
+
vec![format!("{}:{}", kind_str, id)],
|
|
144
|
+
"unmount",
|
|
145
|
+
Some(id.to_string()),
|
|
146
|
+
version,
|
|
147
|
+
None,
|
|
148
|
+
None,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|