deepstrike 0.2.17__tar.gz → 0.2.18__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.17 → deepstrike-0.2.18}/Cargo.lock +5 -5
- {deepstrike-0.2.17 → deepstrike-0.2.18}/Cargo.toml +2 -2
- {deepstrike-0.2.17 → deepstrike-0.2.18}/PKG-INFO +1 -1
- deepstrike-0.2.17/crates/deepstrike-core/src/harness/eval_pipeline.rs → deepstrike-0.2.18/crates/deepstrike-core/src/harness/eval.rs +133 -219
- deepstrike-0.2.18/crates/deepstrike-core/src/harness/mod.rs +6 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/workflow/mod.rs +82 -6
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/contract.rs +2 -2
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-py/src/lib.rs +92 -136
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/__init__.py +4 -3
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/harness/harness.py +10 -17
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/kernel/__init__.py +8 -4
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/__init__.py +2 -0
- deepstrike-0.2.18/deepstrike/providers/openai_responses.py +319 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/types/agent.py +20 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/pyproject.toml +1 -1
- deepstrike-0.2.17/crates/deepstrike-core/src/harness/mod.rs +0 -5
- {deepstrike-0.2.17 → deepstrike-0.2.18}/README.md +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/Cargo.toml +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/compression.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/config.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/dashboard.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/manager.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/partitions.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/pressure.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/renderer.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/renewal.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/sections.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/skill_catalog.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/snapshot.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/summarizer.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/task_state.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/text.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/context/token_engine.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/audit.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/constraint.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/permission.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/pipeline.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/quota.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/rate_limit.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/sandbox.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/tool_decision.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/governance/veto.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/lib.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/curator.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/durable.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/extractor.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/idle_pipeline.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/runtime.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/semantic.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/session.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/synthesis.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/trace_analyzer.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/memory/working.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/mm/handle.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/mm/memory.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/mm/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/task_graph.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/tournament.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/workflow/run.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/proc/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/event_log.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/kernel.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/repair.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/replay.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/session.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/runtime/snapshot.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/milestone.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/policy.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/rollback.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/capability.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/eviction.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/gate.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/milestone_exec.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/process.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/signal.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/tests.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/state_machine/workflow.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/scheduler/tcb.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/signals/attention.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/signals/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/signals/queue.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/signals/router.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/syscall/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/agent.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/capability.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/error.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/message.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/milestone.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/mod.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/model.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/policy.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/result.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/signal.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/skill.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/types/task.rs +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-py/Cargo.toml +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/contract.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/handoff.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/harness.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/modes.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/collaboration/pool.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/governance.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/harness/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/knowledge/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/knowledge/source.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/memory/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/memory/agent.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/memory/protocols.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/memory/working.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/anthropic.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/base.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/deepseek.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/gemini.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/glm.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/kimi.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/minimax.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/ollama.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/openai.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/qwen.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/replay.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/replay_validator.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/providers/stream.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/archive.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/credential_vault.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/execution_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/filtered_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/kernel_event_log.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/kernel_step.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/large_result_spool.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/mcp_proxy_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/os_profile.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/os_snapshot.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/output_schema.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/process_sandbox_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/provider_replay.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/reducers.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/remote_vpc_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/replay_sanitize.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/runner.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/session_log.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/session_repair.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/sub_agent_orchestrator.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/workflow_control_flow.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/workflow_store.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/runtime/worktree_plane.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/safety/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/safety/permissions.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/signals/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/signals/gateway.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/signals/scheduled.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/signals/types.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/skills/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/skills/registry.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/skills/watcher.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/tools/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/tools/builtin/__init__.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/tools/builtin/read_file.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/tools/execution.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/deepstrike/tools/registry.py +0 -0
- {deepstrike-0.2.17 → deepstrike-0.2.18}/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.18"
|
|
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.18"
|
|
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.18"
|
|
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.18"
|
|
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.18"
|
|
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.18"
|
|
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.18" }
|
|
13
13
|
notify = "6"
|
|
14
14
|
serde = { version = "1", features = ["derive"] }
|
|
15
15
|
serde_json = "1"
|
|
@@ -1,32 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/// │ → build evaluation prompt │
|
|
19
|
-
/// │ → EvalAction::Evaluate { messages } │ ← SDK calls LLM
|
|
20
|
-
/// └──────────────────────────────────────────────────────┘
|
|
21
|
-
///
|
|
22
|
-
/// Phase 2 — Parse LLM verdict (after SDK returns)
|
|
23
|
-
/// ┌──────────────────────────────────────────────────────┐
|
|
24
|
-
/// │ EvalEvent::EvalResult { content } │
|
|
25
|
-
/// │ → parse JSON → EvalResult { passed, feedback, │
|
|
26
|
-
/// │ skill_candidate } │
|
|
27
|
-
/// │ → EvalAction::Done { result } │ ← SDK acts on result
|
|
28
|
-
/// └──────────────────────────────────────────────────────┘
|
|
29
|
-
/// ```
|
|
1
|
+
//! Evaluation primitives — the agent's "quality gate" compute.
|
|
2
|
+
//!
|
|
3
|
+
//! Pure computation in kernel, I/O in SDK. This module provides the **stateless** building blocks
|
|
4
|
+
//! for the generate → evaluate → retry quality gate:
|
|
5
|
+
//!
|
|
6
|
+
//! - [`build_eval_messages`] assembles the impartial-evaluator prompt from a goal + criteria + the
|
|
7
|
+
//! agent's output (the SDK then calls the eval LLM with it).
|
|
8
|
+
//! - [`parse_verdict`] parses the LLM's JSON response into a structured [`EvalResult`].
|
|
9
|
+
//! - [`verdict_output_schema`] is the JSON Schema for that verdict, used as the `output_schema` of
|
|
10
|
+
//! the eval node in the [`crate::orchestration::workflow::gen_eval`] workflow template.
|
|
11
|
+
//!
|
|
12
|
+
//! **History (0.5.0 fold, OS-axis #6).** This replaces the former `EvalPipeline` state machine +
|
|
13
|
+
//! its public SDK class. The quality gate is now expressed on the workflow substrate: the iterative
|
|
14
|
+
//! retry-with-feedback loop is driven by the SDK `HarnessLoop` (the kernel `NodeKind::Loop` re-arms
|
|
15
|
+
//! a single node, so per-iteration eval cannot be a static DAG), and the declarative
|
|
16
|
+
//! "loop-the-worker-then-verify-with-a-structured-verdict" shape is the `gen_eval` template. Both
|
|
17
|
+
//! reuse these primitives, so the verdict shape stays consistent across the two paths.
|
|
30
18
|
use crate::types::message::{Content, Message, Role};
|
|
31
19
|
|
|
32
20
|
// ---------------------------------------------------------------------------
|
|
@@ -102,6 +90,7 @@ pub struct SkillCandidate {
|
|
|
102
90
|
pub content: String,
|
|
103
91
|
}
|
|
104
92
|
|
|
93
|
+
/// The structured verdict produced by parsing the eval LLM's JSON response.
|
|
105
94
|
#[derive(Debug, Clone)]
|
|
106
95
|
pub struct EvalResult {
|
|
107
96
|
pub passed: bool,
|
|
@@ -114,112 +103,19 @@ pub struct EvalResult {
|
|
|
114
103
|
pub skill_candidate: Option<SkillCandidate>,
|
|
115
104
|
}
|
|
116
105
|
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// State machine types
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
#[derive(Debug)]
|
|
122
|
-
pub enum EvalPhase {
|
|
123
|
-
Idle,
|
|
124
|
-
EvalPending { goal: String, attempt: u32 },
|
|
125
|
-
Done,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
pub enum EvalEvent {
|
|
129
|
-
Outcome {
|
|
130
|
-
goal: String,
|
|
131
|
-
criteria: Vec<Criterion>,
|
|
132
|
-
result: String,
|
|
133
|
-
attempt: u32,
|
|
134
|
-
},
|
|
135
|
-
EvalResult {
|
|
136
|
-
content: String,
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
pub enum EvalAction {
|
|
141
|
-
/// Call the LLM with `messages`, then feed `EvalEvent::EvalResult`.
|
|
142
|
-
Evaluate { messages: Vec<Message> },
|
|
143
|
-
/// Evaluation complete — SDK reads `result` and decides next step.
|
|
144
|
-
Done { result: EvalResult },
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// Policy
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
#[derive(Debug, Clone)]
|
|
152
|
-
pub struct EvalPolicy {
|
|
153
|
-
/// Whether to ask the LLM to propose a skill when the run passes. Default: true.
|
|
154
|
-
pub extract_skill_on_pass: bool,
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
impl Default for EvalPolicy {
|
|
158
|
-
fn default() -> Self {
|
|
159
|
-
Self {
|
|
160
|
-
extract_skill_on_pass: true,
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// Pipeline
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
|
|
169
|
-
pub struct EvalPipeline {
|
|
170
|
-
pub phase: EvalPhase,
|
|
171
|
-
policy: EvalPolicy,
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
impl EvalPipeline {
|
|
175
|
-
pub fn new(policy: EvalPolicy) -> Self {
|
|
176
|
-
Self {
|
|
177
|
-
phase: EvalPhase::Idle,
|
|
178
|
-
policy,
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
pub fn is_idle(&self) -> bool {
|
|
183
|
-
matches!(self.phase, EvalPhase::Idle)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
pub fn feed(&mut self, event: EvalEvent) -> EvalAction {
|
|
187
|
-
match event {
|
|
188
|
-
EvalEvent::Outcome {
|
|
189
|
-
goal,
|
|
190
|
-
criteria,
|
|
191
|
-
result,
|
|
192
|
-
attempt,
|
|
193
|
-
} => {
|
|
194
|
-
let messages = build_eval_prompt(&goal, &criteria, &result, attempt, &self.policy);
|
|
195
|
-
self.phase = EvalPhase::EvalPending { goal, attempt };
|
|
196
|
-
EvalAction::Evaluate { messages }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
EvalEvent::EvalResult { content } => {
|
|
200
|
-
self.phase = EvalPhase::Done;
|
|
201
|
-
EvalAction::Done {
|
|
202
|
-
result: parse_eval_response(&content),
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
pub fn reset(&mut self) {
|
|
209
|
-
self.phase = EvalPhase::Idle;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
106
|
// ---------------------------------------------------------------------------
|
|
214
107
|
// Prompt builder
|
|
215
108
|
// ---------------------------------------------------------------------------
|
|
216
109
|
|
|
217
|
-
|
|
110
|
+
/// Build the impartial-evaluator messages for one attempt: a system instruction describing the
|
|
111
|
+
/// scoring contract + a user message carrying the goal, criteria, and the agent's output. The SDK
|
|
112
|
+
/// calls the eval LLM with these, then feeds the response to [`parse_verdict`].
|
|
113
|
+
pub fn build_eval_messages(
|
|
218
114
|
goal: &str,
|
|
219
115
|
criteria: &[Criterion],
|
|
220
116
|
result: &str,
|
|
221
117
|
attempt: u32,
|
|
222
|
-
|
|
118
|
+
extract_skill_on_pass: bool,
|
|
223
119
|
) -> Vec<Message> {
|
|
224
120
|
let criteria_text = if criteria.is_empty() {
|
|
225
121
|
"No explicit criteria — use general quality judgement.".to_string()
|
|
@@ -246,7 +142,7 @@ fn build_eval_prompt(
|
|
|
246
142
|
|
|
247
143
|
let details_schema = r#"[{"criterion":"...","passed":bool,"score":0.0-1.0,"feedback":"..."}]"#;
|
|
248
144
|
|
|
249
|
-
let skill_instruction = if
|
|
145
|
+
let skill_instruction = if extract_skill_on_pass {
|
|
250
146
|
"\nIf passed=true and the approach is reusable, add a \"skill\" field:\
|
|
251
147
|
\n{\"name\":\"snake_case\",\"description\":\"one sentence\",\"when_to_use\":\"optional hint\",\"content\":\"markdown body (no frontmatter)\"}"
|
|
252
148
|
} else {
|
|
@@ -279,11 +175,59 @@ fn build_eval_prompt(
|
|
|
279
175
|
vec![system, user]
|
|
280
176
|
}
|
|
281
177
|
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Verdict output schema (for the gen_eval workflow template's eval node)
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
/// JSON Schema for the verdict an eval node must produce. Used as the `output_schema` of the eval
|
|
183
|
+
/// node in the [`crate::orchestration::workflow::gen_eval`] template so the SDK can instruct +
|
|
184
|
+
/// validate the verdict. Matches what [`parse_verdict`] reads.
|
|
185
|
+
pub fn verdict_output_schema(extract_skill_on_pass: bool) -> serde_json::Value {
|
|
186
|
+
let mut properties = serde_json::json!({
|
|
187
|
+
"passed": { "type": "boolean", "description": "true iff all [required] criteria pass" },
|
|
188
|
+
"overall_score": { "type": "number", "minimum": 0.0, "maximum": 1.0 },
|
|
189
|
+
"feedback": { "type": "string", "description": "concise summary; on fail, what to fix next attempt" },
|
|
190
|
+
"details": {
|
|
191
|
+
"type": "array",
|
|
192
|
+
"items": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"required": ["criterion", "passed", "score", "feedback"],
|
|
195
|
+
"properties": {
|
|
196
|
+
"criterion": { "type": "string" },
|
|
197
|
+
"passed": { "type": "boolean" },
|
|
198
|
+
"score": { "type": "number", "minimum": 0.0, "maximum": 1.0 },
|
|
199
|
+
"feedback": { "type": "string" }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if extract_skill_on_pass {
|
|
205
|
+
properties["skill"] = serde_json::json!({
|
|
206
|
+
"type": "object",
|
|
207
|
+
"description": "optional reusable skill distilled from a passing run",
|
|
208
|
+
"required": ["name", "description", "content"],
|
|
209
|
+
"properties": {
|
|
210
|
+
"name": { "type": "string", "description": "snake_case" },
|
|
211
|
+
"description": { "type": "string" },
|
|
212
|
+
"when_to_use": { "type": "string" },
|
|
213
|
+
"content": { "type": "string", "description": "markdown body, no frontmatter" }
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
serde_json::json!({
|
|
218
|
+
"type": "object",
|
|
219
|
+
"required": ["passed", "overall_score", "feedback"],
|
|
220
|
+
"properties": properties
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
282
224
|
// ---------------------------------------------------------------------------
|
|
283
225
|
// Response parser
|
|
284
226
|
// ---------------------------------------------------------------------------
|
|
285
227
|
|
|
286
|
-
|
|
228
|
+
/// Parse an eval LLM's JSON response into a structured [`EvalResult`]. Tolerant of markdown fences
|
|
229
|
+
/// and missing fields (defaults: `passed=false`, score derived from `passed`).
|
|
230
|
+
pub fn parse_verdict(content: &str) -> EvalResult {
|
|
287
231
|
let json_str = extract_json(content);
|
|
288
232
|
let v: serde_json::Value = serde_json::from_str(json_str).unwrap_or(serde_json::Value::Null);
|
|
289
233
|
|
|
@@ -378,77 +322,68 @@ fn extract_json(s: &str) -> &str {
|
|
|
378
322
|
mod tests {
|
|
379
323
|
use super::*;
|
|
380
324
|
|
|
381
|
-
|
|
382
|
-
|
|
325
|
+
#[test]
|
|
326
|
+
fn build_eval_messages_carries_goal_and_criteria() {
|
|
327
|
+
let msgs = build_eval_messages(
|
|
328
|
+
"Write a function",
|
|
329
|
+
&[Criterion::required("Must handle errors")],
|
|
330
|
+
"fn foo() {}",
|
|
331
|
+
1,
|
|
332
|
+
true,
|
|
333
|
+
);
|
|
334
|
+
assert_eq!(msgs.len(), 2);
|
|
335
|
+
assert!(matches!(msgs[0].role, Role::System));
|
|
336
|
+
let Content::Text(user) = &msgs[1].content else {
|
|
337
|
+
panic!("expected text")
|
|
338
|
+
};
|
|
339
|
+
assert!(user.contains("Write a function"));
|
|
340
|
+
assert!(user.contains("[required]Must handle errors"));
|
|
341
|
+
assert!(user.contains("attempt 1"));
|
|
342
|
+
// skill instruction present when extract_skill_on_pass=true
|
|
343
|
+
let Content::Text(system) = &msgs[0].content else {
|
|
344
|
+
panic!("expected text")
|
|
345
|
+
};
|
|
346
|
+
assert!(system.contains("\"skill\""));
|
|
383
347
|
}
|
|
384
348
|
|
|
385
349
|
#[test]
|
|
386
|
-
fn
|
|
387
|
-
|
|
350
|
+
fn build_eval_messages_omits_skill_instruction_when_disabled() {
|
|
351
|
+
let msgs = build_eval_messages("g", &[], "r", 1, false);
|
|
352
|
+
let Content::Text(system) = &msgs[0].content else {
|
|
353
|
+
panic!("expected text")
|
|
354
|
+
};
|
|
355
|
+
assert!(!system.contains("\"name\":\"snake_case\""));
|
|
388
356
|
}
|
|
389
357
|
|
|
390
358
|
#[test]
|
|
391
|
-
fn
|
|
392
|
-
let
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
assert!(
|
|
400
|
-
assert!(matches!(p.phase, EvalPhase::EvalPending { .. }));
|
|
359
|
+
fn parse_verdict_failed_no_skill() {
|
|
360
|
+
let result = parse_verdict(
|
|
361
|
+
r#"{"passed":false,"overall_score":0.2,"feedback":"Missing error handling","details":[{"criterion":"Must handle errors","passed":false,"score":0.2,"feedback":"No error handling found"}]}"#,
|
|
362
|
+
);
|
|
363
|
+
assert!(!result.passed);
|
|
364
|
+
assert_eq!(result.feedback, "Missing error handling");
|
|
365
|
+
assert_eq!(result.details.len(), 1);
|
|
366
|
+
assert!(!result.details[0].passed);
|
|
367
|
+
assert!(result.skill_candidate.is_none());
|
|
401
368
|
}
|
|
402
369
|
|
|
403
370
|
#[test]
|
|
404
|
-
fn
|
|
405
|
-
let
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
});
|
|
415
|
-
match action {
|
|
416
|
-
EvalAction::Done { result } => {
|
|
417
|
-
assert!(!result.passed);
|
|
418
|
-
assert_eq!(result.feedback, "Missing error handling");
|
|
419
|
-
assert_eq!(result.details.len(), 1);
|
|
420
|
-
assert!(!result.details[0].passed);
|
|
421
|
-
assert!(result.skill_candidate.is_none());
|
|
422
|
-
}
|
|
423
|
-
_ => panic!("expected Done"),
|
|
424
|
-
}
|
|
371
|
+
fn parse_verdict_passed_with_skill_and_details() {
|
|
372
|
+
let json = r#"{"passed":true,"overall_score":0.95,"feedback":"All criteria met","details":[{"criterion":"Must handle errors","passed":true,"score":1.0,"feedback":"Good error handling"}],"skill":{"name":"robust_api_call","description":"How to call APIs with retries","content":"Robust API Call - Always retry on 5xx."}}"#;
|
|
373
|
+
let result = parse_verdict(json);
|
|
374
|
+
assert!(result.passed);
|
|
375
|
+
assert!(result.overall_score > 0.9);
|
|
376
|
+
assert_eq!(result.details.len(), 1);
|
|
377
|
+
assert!(result.details[0].passed);
|
|
378
|
+
let skill = result.skill_candidate.unwrap();
|
|
379
|
+
assert_eq!(skill.name, "robust_api_call");
|
|
380
|
+
assert!(skill.content.contains("retry"));
|
|
425
381
|
}
|
|
426
382
|
|
|
427
383
|
#[test]
|
|
428
|
-
fn
|
|
429
|
-
let
|
|
430
|
-
|
|
431
|
-
goal: "g".into(),
|
|
432
|
-
criteria: vec![],
|
|
433
|
-
result: "r".into(),
|
|
434
|
-
attempt: 1,
|
|
435
|
-
});
|
|
436
|
-
let json = r#"{"passed":true,"overall_score":0.95,"feedback":"All criteria met","details":[{"criterion":"Must handle errors","passed":true,"score":1.0,"feedback":"Good error handling"}],"skill":{"name":"robust_api_call","description":"How to call APIs with retries","content":"Robust API Call - Always retry on 5xx."}}"#;
|
|
437
|
-
let action = p.feed(EvalEvent::EvalResult {
|
|
438
|
-
content: json.into(),
|
|
439
|
-
});
|
|
440
|
-
match action {
|
|
441
|
-
EvalAction::Done { result } => {
|
|
442
|
-
assert!(result.passed);
|
|
443
|
-
assert!(result.overall_score > 0.9);
|
|
444
|
-
assert_eq!(result.details.len(), 1);
|
|
445
|
-
assert!(result.details[0].passed);
|
|
446
|
-
let skill = result.skill_candidate.unwrap();
|
|
447
|
-
assert_eq!(skill.name, "robust_api_call");
|
|
448
|
-
assert!(skill.content.contains("retry"));
|
|
449
|
-
}
|
|
450
|
-
_ => panic!("expected Done"),
|
|
451
|
-
}
|
|
384
|
+
fn parse_verdict_strips_markdown_fences() {
|
|
385
|
+
let result = parse_verdict("```json\n{\"passed\":true,\"feedback\":\"good\"}\n```");
|
|
386
|
+
assert!(result.passed);
|
|
452
387
|
}
|
|
453
388
|
|
|
454
389
|
#[test]
|
|
@@ -466,36 +401,15 @@ mod tests {
|
|
|
466
401
|
}
|
|
467
402
|
|
|
468
403
|
#[test]
|
|
469
|
-
fn
|
|
470
|
-
let
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
});
|
|
480
|
-
p.reset();
|
|
481
|
-
assert!(p.is_idle());
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
#[test]
|
|
485
|
-
fn strips_markdown_fences() {
|
|
486
|
-
let mut p = pipeline();
|
|
487
|
-
p.feed(EvalEvent::Outcome {
|
|
488
|
-
goal: "g".into(),
|
|
489
|
-
criteria: vec![],
|
|
490
|
-
result: "r".into(),
|
|
491
|
-
attempt: 1,
|
|
492
|
-
});
|
|
493
|
-
let action = p.feed(EvalEvent::EvalResult {
|
|
494
|
-
content: "```json\n{\"passed\":true,\"feedback\":\"good\"}\n```".into(),
|
|
495
|
-
});
|
|
496
|
-
match action {
|
|
497
|
-
EvalAction::Done { result } => assert!(result.passed),
|
|
498
|
-
_ => panic!(),
|
|
499
|
-
}
|
|
404
|
+
fn verdict_output_schema_shape() {
|
|
405
|
+
let schema = verdict_output_schema(true);
|
|
406
|
+
assert_eq!(schema["type"], "object");
|
|
407
|
+
assert!(schema["properties"]["passed"].is_object());
|
|
408
|
+
assert!(schema["properties"]["overall_score"].is_object());
|
|
409
|
+
assert!(schema["properties"]["details"].is_object());
|
|
410
|
+
assert!(schema["properties"]["skill"].is_object());
|
|
411
|
+
// skill property dropped when extraction is disabled
|
|
412
|
+
let no_skill = verdict_output_schema(false);
|
|
413
|
+
assert!(no_skill["properties"]["skill"].is_null());
|
|
500
414
|
}
|
|
501
415
|
}
|
{deepstrike-0.2.17 → deepstrike-0.2.18}/crates/deepstrike-core/src/orchestration/workflow/mod.rs
RENAMED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
//! loop-until-done, classify-and-act, and tournament — are now first-class [`NodeKind`] variants
|
|
11
11
|
//! ([`NodeKind::Loop`] / [`NodeKind::Classify`] / [`NodeKind::Tournament`]) driven by the unified
|
|
12
12
|
//! workflow executor; the former standalone `loop_until_done` / `tournament` SDK primitives were
|
|
13
|
-
//! removed in their favor (A#1).
|
|
14
|
-
//! [`crate::harness::
|
|
13
|
+
//! removed in their favor (A#1). The generate→evaluate→retry quality gate is the [`gen_eval`]
|
|
14
|
+
//! template (a `Loop` worker + a `Verify` eval node carrying [`crate::harness::verdict_output_schema`]);
|
|
15
|
+
//! its eval/verdict compute lives in [`crate::harness`].
|
|
15
16
|
//!
|
|
16
17
|
//! Pure: no I/O, no clock, no spawning. Validation reuses [`TaskGraph::topological_sort`].
|
|
17
18
|
|
|
@@ -338,7 +339,7 @@ pub fn fanout_synthesize(workers: Vec<RuntimeTask>, synthesize: RuntimeTask) ->
|
|
|
338
339
|
///
|
|
339
340
|
/// Structurally a fan-out barrier, but semantically distinct: generators are `Implement`
|
|
340
341
|
/// agents producing candidates; the filter is a `Verify` agent that ranks/dedupes against
|
|
341
|
-
/// a rubric (pair with [`
|
|
342
|
+
/// a rubric (pair with the [`gen_eval`] verdict schema for the rubric).
|
|
342
343
|
pub fn generate_and_filter(generators: Vec<RuntimeTask>, filter: RuntimeTask) -> WorkflowSpec {
|
|
343
344
|
let mut nodes: Vec<WorkflowNode> = generators
|
|
344
345
|
.into_iter()
|
|
@@ -365,9 +366,9 @@ pub fn generate_and_filter(generators: Vec<RuntimeTask>, filter: RuntimeTask) ->
|
|
|
365
366
|
/// against self-preferential bias). The optional `skeptic` depends on all verifiers and reviews
|
|
366
367
|
/// their flags (real violation vs. false positive). Runs on the W0 workflow executor.
|
|
367
368
|
///
|
|
368
|
-
///
|
|
369
|
-
///
|
|
370
|
-
///
|
|
369
|
+
/// For unknown-size rule sets (claim extraction), a dynamic-fan-out variant is a later round; this
|
|
370
|
+
/// covers the case where the rule/claim set is known up front. For the generate→evaluate→retry
|
|
371
|
+
/// quality gate (scoring one author's output against criteria), see [`gen_eval`].
|
|
371
372
|
pub fn verify_rules(rules: Vec<RuntimeTask>, skeptic: Option<RuntimeTask>) -> WorkflowSpec {
|
|
372
373
|
let mut nodes: Vec<WorkflowNode> = rules
|
|
373
374
|
.into_iter()
|
|
@@ -383,6 +384,44 @@ pub fn verify_rules(rules: Vec<RuntimeTask>, skeptic: Option<RuntimeTask>) -> Wo
|
|
|
383
384
|
WorkflowSpec::new(nodes)
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// Quality gate — generate → evaluate (#6, the EvalPipeline successor)
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
/// The generate→evaluate quality gate as a workflow: a `Loop` **worker** node (the task, re-run up
|
|
392
|
+
/// to `max_iters`, stopping early on a `loop_continue=false` self-signal) followed by a `Verify`
|
|
393
|
+
/// **eval** node that scores the worker's output against the goal/criteria and emits a structured
|
|
394
|
+
/// verdict ([`crate::harness::verdict_output_schema`] as its `output_schema`).
|
|
395
|
+
///
|
|
396
|
+
/// This is the declarative substrate form of the former `EvalPipeline` (0.5.0 fold, OS-axis #6).
|
|
397
|
+
/// The eval node is a `Verify` agent — [`role_defaults`] gives it `ReadOnly` + [`ContextInheritance::None`]
|
|
398
|
+
/// so it does not inherit the worker's reasoning (bias resistance); it evaluates the worker's
|
|
399
|
+
/// *output*, carried in via its task goal. The verdict's `passed` is the gate.
|
|
400
|
+
///
|
|
401
|
+
/// For the **iterative retry-with-feedback** variant (re-run the worker with the eval's feedback
|
|
402
|
+
/// folded into the next attempt), the SDK `HarnessLoop` drives this with the same
|
|
403
|
+
/// [`crate::harness::build_eval_messages`] / [`crate::harness::parse_verdict`] primitives — the
|
|
404
|
+
/// kernel `Loop` re-arms a single node, so per-iteration eval is necessarily SDK-driven.
|
|
405
|
+
pub fn gen_eval(
|
|
406
|
+
worker: RuntimeTask,
|
|
407
|
+
eval: RuntimeTask,
|
|
408
|
+
max_iters: usize,
|
|
409
|
+
extract_skill_on_pass: bool,
|
|
410
|
+
) -> WorkflowSpec {
|
|
411
|
+
let worker_node = WorkflowNode::new(
|
|
412
|
+
worker.with_lane(TaskLane::new(TaskLane::ORCHESTRATE)),
|
|
413
|
+
AgentRole::Implement,
|
|
414
|
+
)
|
|
415
|
+
.with_loop(max_iters.max(1));
|
|
416
|
+
let eval_node = WorkflowNode::new(
|
|
417
|
+
eval.with_lane(TaskLane::new(TaskLane::VERIFY)),
|
|
418
|
+
AgentRole::Verify,
|
|
419
|
+
)
|
|
420
|
+
.with_depends_on(vec![0])
|
|
421
|
+
.with_output_schema(crate::harness::verdict_output_schema(extract_skill_on_pass));
|
|
422
|
+
WorkflowSpec::new(vec![worker_node, eval_node])
|
|
423
|
+
}
|
|
424
|
+
|
|
386
425
|
// ---------------------------------------------------------------------------
|
|
387
426
|
// Pattern 3 — Classify-and-act
|
|
388
427
|
// ---------------------------------------------------------------------------
|
|
@@ -489,6 +528,43 @@ mod tests {
|
|
|
489
528
|
spec.validate().unwrap();
|
|
490
529
|
}
|
|
491
530
|
|
|
531
|
+
#[test]
|
|
532
|
+
fn gen_eval_shape() {
|
|
533
|
+
// Worker loops; eval is a bias-resistant Verify node gated on the worker, carrying the
|
|
534
|
+
// verdict output_schema.
|
|
535
|
+
let spec = gen_eval(task("implement feature"), task("score against criteria"), 3, true);
|
|
536
|
+
assert_eq!(spec.nodes.len(), 2);
|
|
537
|
+
|
|
538
|
+
let worker = &spec.nodes[0];
|
|
539
|
+
assert_eq!(worker.role, AgentRole::Implement);
|
|
540
|
+
assert_eq!(worker.kind, NodeKind::Loop { max_iters: 3 });
|
|
541
|
+
assert!(worker.depends_on.is_empty());
|
|
542
|
+
|
|
543
|
+
let eval = &spec.nodes[1];
|
|
544
|
+
assert_eq!(eval.role, AgentRole::Verify);
|
|
545
|
+
assert_eq!(eval.context_inheritance, ContextInheritance::None);
|
|
546
|
+
assert_eq!(eval.isolation, AgentIsolation::ReadOnly);
|
|
547
|
+
assert_eq!(eval.depends_on, vec![0]);
|
|
548
|
+
let schema = eval.output_schema.as_ref().expect("eval node carries verdict schema");
|
|
549
|
+
assert!(schema["properties"]["passed"].is_object());
|
|
550
|
+
assert!(schema["properties"]["skill"].is_object()); // extract_skill_on_pass=true
|
|
551
|
+
|
|
552
|
+
spec.validate().unwrap();
|
|
553
|
+
// Worker is the only initially-ready node; eval is gated.
|
|
554
|
+
assert_eq!(spec.to_task_graph().unwrap().ready_tasks(), vec![0]);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
#[test]
|
|
558
|
+
fn gen_eval_max_iters_floor_and_no_skill() {
|
|
559
|
+
// max_iters=0 would be an invalid loop; the template floors it to 1.
|
|
560
|
+
let spec = gen_eval(task("w"), task("e"), 0, false);
|
|
561
|
+
assert_eq!(spec.nodes[0].kind, NodeKind::Loop { max_iters: 1 });
|
|
562
|
+
// extract_skill_on_pass=false ⇒ no skill property in the verdict schema.
|
|
563
|
+
let schema = spec.nodes[1].output_schema.as_ref().unwrap();
|
|
564
|
+
assert!(schema["properties"]["skill"].is_null());
|
|
565
|
+
spec.validate().unwrap();
|
|
566
|
+
}
|
|
567
|
+
|
|
492
568
|
#[test]
|
|
493
569
|
fn verify_rules_empty_with_skeptic_is_just_skeptic() {
|
|
494
570
|
// No rules → skeptic has nothing to depend on; still a valid single-node spec.
|
|
@@ -129,8 +129,8 @@ impl VerificationContract {
|
|
|
129
129
|
out
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/// Derives a flat `Vec<String>` of criterion texts for use with the
|
|
133
|
-
/// `
|
|
132
|
+
/// Derives a flat `Vec<String>` of criterion texts for use with the
|
|
133
|
+
/// `HarnessLoop` / [`crate::harness::build_eval_messages`] criteria API.
|
|
134
134
|
pub fn to_criteria_strings(&self) -> Vec<String> {
|
|
135
135
|
self.acceptance.iter().map(|c| c.text.clone()).collect()
|
|
136
136
|
}
|