|
|
@@ -1,6 +1,7 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import asyncio
|
|
|
+from types import SimpleNamespace
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
@@ -26,14 +27,21 @@ from agent.orchestration.models import (
|
|
|
ValidationVerdict,
|
|
|
)
|
|
|
from agent.orchestration.protocols import WorkerRunResult
|
|
|
+from agent.orchestration.store import FileSystemArtifactStore, FileSystemTaskStore
|
|
|
from agent.orchestration.validation_policy import (
|
|
|
DefaultValidationPolicy,
|
|
|
DeterministicRuleResult,
|
|
|
RuleBasedDeterministicValidator,
|
|
|
ValidationContext,
|
|
|
)
|
|
|
+from agent.orchestration.wiring import wire_orchestration
|
|
|
|
|
|
-from test_coordinator_integration import FakeExecutor, create_task, make_coordinator
|
|
|
+from test_coordinator_integration import (
|
|
|
+ ROOT_TASK_SPEC,
|
|
|
+ FakeExecutor,
|
|
|
+ create_task,
|
|
|
+ make_coordinator,
|
|
|
+)
|
|
|
|
|
|
|
|
|
def make_context(root: str = "root") -> ValidationContext:
|
|
|
@@ -235,6 +243,63 @@ async def test_coordinator_executes_deterministic_plan_without_agent_validator(
|
|
|
assert report.criterion_results[0].criterion_id == "c1"
|
|
|
|
|
|
|
|
|
+def test_wire_orchestration_preserves_legacy_positional_defaults(tmp_path):
|
|
|
+ runner = SimpleNamespace(trace_store=None, task_coordinator=None)
|
|
|
+ config = OrchestrationConfig(max_parallel_tasks=2)
|
|
|
+ event_sink = object()
|
|
|
+
|
|
|
+ coordinator = wire_orchestration(
|
|
|
+ runner,
|
|
|
+ FileSystemTaskStore(str(tmp_path)),
|
|
|
+ FileSystemArtifactStore(str(tmp_path)),
|
|
|
+ config,
|
|
|
+ event_sink,
|
|
|
+ )
|
|
|
+
|
|
|
+ assert runner.task_coordinator is coordinator
|
|
|
+ assert coordinator.config is config
|
|
|
+ assert coordinator.event_sink is event_sink
|
|
|
+ assert isinstance(coordinator.validation_policy, DefaultValidationPolicy)
|
|
|
+ assert coordinator.deterministic_validator is None
|
|
|
+ assert coordinator.evidence_provider is None
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_wire_orchestration_applies_custom_validation_dependencies(tmp_path):
|
|
|
+ runner = SimpleNamespace(trace_store=None, task_coordinator=None)
|
|
|
+ policy = DeterministicPolicy()
|
|
|
+ validator = RuleBasedDeterministicValidator(
|
|
|
+ [StaticRule("c1", ValidationVerdict.PASSED)]
|
|
|
+ )
|
|
|
+ provider = EvidenceProvider()
|
|
|
+ coordinator = wire_orchestration(
|
|
|
+ runner,
|
|
|
+ FileSystemTaskStore(str(tmp_path)),
|
|
|
+ FileSystemArtifactStore(str(tmp_path)),
|
|
|
+ validation_policy=policy,
|
|
|
+ deterministic_validator=validator,
|
|
|
+ evidence_provider=provider,
|
|
|
+ )
|
|
|
+ executor = FakeExecutor([])
|
|
|
+ executor.coordinator = coordinator
|
|
|
+ coordinator.set_executor(executor)
|
|
|
+ await coordinator.ensure_ledger("root", ROOT_TASK_SPEC)
|
|
|
+ task_id = await create_task(coordinator, "wired deterministic validation")
|
|
|
+
|
|
|
+ result = (await coordinator.dispatch_tasks("root", [task_id]))[0]
|
|
|
+ report = (await coordinator.task_store.load("root")).validations[
|
|
|
+ result.validation_id
|
|
|
+ ]
|
|
|
+
|
|
|
+ assert runner.task_coordinator is coordinator
|
|
|
+ assert coordinator.validation_policy is policy
|
|
|
+ assert coordinator.deterministic_validator is validator
|
|
|
+ assert coordinator.evidence_provider is provider
|
|
|
+ assert executor.validator_calls == 0
|
|
|
+ assert report.validation_plan.mode == ValidationMode.DETERMINISTIC
|
|
|
+ assert report.verdict == ValidationVerdict.PASSED
|
|
|
+
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
|
@pytest.mark.parametrize("rule_ids", [[], ["unknown"]])
|
|
|
async def test_deterministic_plan_must_cover_hard_criteria_without_unknowns(
|
|
|
@@ -363,6 +428,62 @@ async def running_evidence_validation(tmp_path, provider, *, max_queries=1, time
|
|
|
return coordinator, store, operation, actor, request
|
|
|
|
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_wire_orchestration_applies_custom_evidence_provider(tmp_path):
|
|
|
+ runner = SimpleNamespace(trace_store=None, task_coordinator=None)
|
|
|
+ provider = EvidenceProvider()
|
|
|
+ policy = EvidencePolicy(max_queries=1, timeout=0.1)
|
|
|
+ coordinator = wire_orchestration(
|
|
|
+ runner,
|
|
|
+ FileSystemTaskStore(str(tmp_path)),
|
|
|
+ FileSystemArtifactStore(str(tmp_path)),
|
|
|
+ OrchestrationConfig(stop_grace_seconds=0),
|
|
|
+ validation_policy=policy,
|
|
|
+ evidence_provider=provider,
|
|
|
+ )
|
|
|
+ executor = BlockingValidatorExecutor()
|
|
|
+ executor.coordinator = coordinator
|
|
|
+ coordinator.set_executor(executor)
|
|
|
+ await coordinator.ensure_ledger("root", ROOT_TASK_SPEC)
|
|
|
+ task_id = await create_task(coordinator, "wired evidence provider")
|
|
|
+ operation = await coordinator.start_operation(
|
|
|
+ "root",
|
|
|
+ "dispatch",
|
|
|
+ task_ids=[task_id],
|
|
|
+ )
|
|
|
+ try:
|
|
|
+ await asyncio.wait_for(executor.context_ready.wait(), timeout=1)
|
|
|
+ context = executor.context
|
|
|
+ actor = {
|
|
|
+ **context,
|
|
|
+ "role": AgentRole.VALIDATOR.value,
|
|
|
+ "trace_id": context["validator_trace_id"],
|
|
|
+ "tool_call_id": "wired-evidence-1",
|
|
|
+ }
|
|
|
+ request = EvidenceQuery(
|
|
|
+ root_trace_id="root",
|
|
|
+ task_id=task_id,
|
|
|
+ attempt_id=context["attempt_id"],
|
|
|
+ snapshot_id=context["snapshot_id"],
|
|
|
+ validation_id=context["validation_id"],
|
|
|
+ query="wired facts",
|
|
|
+ limit=100,
|
|
|
+ )
|
|
|
+ response = await coordinator.query_evidence(actor, request)
|
|
|
+ report = (await coordinator.task_store.load("root")).validations[
|
|
|
+ request.validation_id
|
|
|
+ ]
|
|
|
+
|
|
|
+ assert coordinator.validation_policy is policy
|
|
|
+ assert coordinator.evidence_provider is provider
|
|
|
+ assert len(provider.requests) == 1
|
|
|
+ assert provider.requests[0].limit == 1
|
|
|
+ assert response.items == [{"index": 0}]
|
|
|
+ assert report.evidence_queries_used == 1
|
|
|
+ finally:
|
|
|
+ await coordinator.stop_operation("root", operation.operation_id)
|
|
|
+
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
|
async def test_coordinator_evidence_ownership_and_persistent_budget(tmp_path):
|
|
|
provider = EvidenceProvider()
|