Parcourir la source

执行器:装配本地 Worker 与独立 Validator 子 Trace

新增 LocalAgentExecutor,将 Worker 和 Validator 通过同一个 AgentRunner 运行在隔离的本地 Sub-Trace 中;repair 续用必须经过 Coordinator 身份校验,远程 preset 在 V1 显式拒绝。提供 wire_orchestration 作为无业务依赖的统一装配入口。
SamLee il y a 3 jours
Parent
commit
b733a7b176

+ 38 - 0
agent/agent/orchestration/__init__.py

@@ -0,0 +1,38 @@
+"""Explicit task execution and independent validation."""
+
+from .config import OrchestrationConfig
+from .models import (
+    AcceptanceCriterion,
+    AgentRole,
+    ArtifactRef,
+    ArtifactSnapshot,
+    AttemptStatus,
+    AttemptSubmission,
+    CompletionPolicy,
+    CriterionResult,
+    DecisionAction,
+    PlannerDecision,
+    TaskAttempt,
+    TaskCycleResult,
+    TaskLedger,
+    TaskRecord,
+    TaskSpec,
+    TaskStatus,
+    ValidationReport,
+    ValidationRunStatus,
+    ValidationVerdict,
+)
+from .protocols import ArtifactStore, TaskStore
+from .state_machine import InvalidTaskTransition
+from .coordinator import TaskCoordinator
+from .store import FileSystemTaskStore, FileSystemArtifactStore
+
+__all__ = [
+    "OrchestrationConfig", "TaskStore", "ArtifactStore", "InvalidTaskTransition",
+    "TaskCoordinator", "FileSystemTaskStore", "FileSystemArtifactStore",
+    "CompletionPolicy", "AgentRole", "TaskStatus", "AttemptStatus",
+    "ValidationRunStatus", "ValidationVerdict", "DecisionAction",
+    "AcceptanceCriterion", "TaskSpec", "TaskRecord", "TaskAttempt",
+    "ArtifactRef", "ArtifactSnapshot", "AttemptSubmission", "CriterionResult",
+    "ValidationReport", "PlannerDecision", "TaskLedger", "TaskCycleResult",
+]

+ 156 - 0
agent/agent/orchestration/executor.py

@@ -0,0 +1,156 @@
+"""Local sub-trace executor adapter."""
+
+from __future__ import annotations
+
+import json
+from typing import Any, Dict
+
+from agent.core.runner import RunConfig
+from agent.tools.builtin.knowledge import KnowledgeConfig
+
+from .models import CompletionPolicy
+from .protocols import ValidatorRunResult, WorkerRunResult
+
+
+class LocalAgentExecutor:
+    """Run workers and validators with the same Runner in isolated traces."""
+
+    def __init__(self, runner: Any) -> None:
+        self.runner = runner
+
+    async def run_worker(self, context: Dict[str, Any]) -> WorkerRunResult:
+        preset = context["worker_preset"]
+        if str(preset).startswith("remote_"):
+            return WorkerRunResult(
+                trace_id=context["worker_trace_id"],
+                status="failed",
+                error="V1 explicit_validation supports local Sub-Traces only",
+            )
+        protected = {
+            "root_trace_id": context["root_trace_id"],
+            "task_id": context["task_id"],
+            "spec_version": context["spec_version"],
+            "attempt_id": context["attempt_id"],
+            "completion_policy": CompletionPolicy.EXPLICIT_VALIDATION.value,
+        }
+        continue_trace = context.get("continue_trace_id")
+        if continue_trace:
+            await self.runner.task_coordinator.validate_continue_from(
+                context["root_trace_id"], context["task_id"],
+                self._prior_attempt_id(context),
+            )
+            trace = await self.runner.trace_store.get_trace(continue_trace)
+            trace_context = dict(trace.context or {})
+            trace_context.update(protected)
+            await self.runner.trace_store.update_trace(continue_trace, context=trace_context)
+
+        config = RunConfig(
+            agent_type=preset,
+            completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
+            trace_id=continue_trace,
+            new_trace_id=None if continue_trace else context["worker_trace_id"],
+            parent_trace_id=context["root_trace_id"],
+            tools=None,
+            tool_groups=None,
+            parallel_tool_execution=False,
+            enable_memory=False,
+            enable_research_flow=False,
+            knowledge=_disabled_knowledge(),
+            context=protected,
+            name=f"Worker {context['task_id']}",
+        )
+        prompt = {
+            "task_spec": context["task_spec"],
+            "attempt_id": context["attempt_id"],
+            "repair_feedback": context.get("repair_feedback"),
+            "instruction": "Execute this TaskSpec and finish with submit_attempt.",
+        }
+        try:
+            result = await self.runner.run_result(
+                messages=[{"role": "user", "content": json.dumps(prompt, ensure_ascii=False, indent=2)}],
+                config=config,
+            )
+            return WorkerRunResult(
+                trace_id=result.get("trace_id") or context["worker_trace_id"],
+                status=result.get("status", "failed"),
+                summary=result.get("summary", ""),
+                error=result.get("error"),
+            )
+        except Exception as exc:
+            return WorkerRunResult(
+                trace_id=context["worker_trace_id"], status="failed", error=str(exc)
+            )
+
+    async def run_validator(self, context: Dict[str, Any]) -> ValidatorRunResult:
+        preset = context["validator_preset"]
+        if str(preset).startswith("remote_"):
+            return ValidatorRunResult(
+                trace_id=context["validator_trace_id"],
+                status="failed",
+                error="V1 explicit_validation supports local Validator Sub-Traces only",
+            )
+        protected = {
+            "root_trace_id": context["root_trace_id"],
+            "task_id": context["task_id"],
+            "spec_version": context["spec_version"],
+            "attempt_id": context["attempt_id"],
+            "snapshot_id": context["snapshot_id"],
+            "validation_id": context["validation_id"],
+            "completion_policy": CompletionPolicy.EXPLICIT_VALIDATION.value,
+        }
+        config = RunConfig(
+            agent_type=preset,
+            completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
+            new_trace_id=context["validator_trace_id"],
+            parent_trace_id=context["root_trace_id"],
+            tools=None,
+            tool_groups=None,
+            parallel_tool_execution=False,
+            enable_memory=False,
+            enable_research_flow=False,
+            knowledge=_disabled_knowledge(),
+            context=protected,
+            name=f"Validator {context['task_id']}",
+        )
+        prompt = {
+            "task_spec": context["task_spec"],
+            "attempt_id": context["attempt_id"],
+            "artifact_snapshot": context["artifact_snapshot"],
+            "validation_id": context["validation_id"],
+            "instruction": "Independently verify every criterion and finish with submit_validation.",
+        }
+        try:
+            result = await self.runner.run_result(
+                messages=[{"role": "user", "content": json.dumps(prompt, ensure_ascii=False, indent=2)}],
+                config=config,
+            )
+            return ValidatorRunResult(
+                trace_id=result.get("trace_id") or context["validator_trace_id"],
+                status=result.get("status", "failed"),
+                summary=result.get("summary", ""),
+                error=result.get("error"),
+            )
+        except Exception as exc:
+            return ValidatorRunResult(
+                trace_id=context["validator_trace_id"], status="failed", error=str(exc)
+            )
+
+    def _prior_attempt_id(self, context: Dict[str, Any]) -> str:
+        """Derive the previous attempt; callers cannot provide arbitrary trace IDs."""
+        # The current attempt is already appended. The immediately previous
+        # Task attempt is the only valid repair source.
+        # This helper is sync only for shape; validation itself remains async.
+        # Coordinator protects and validates the result before Runner resumes.
+        # A temporary marker is populated by coordinator in worker context.
+        return context["prior_attempt_id"]
+
+
+def _disabled_knowledge() -> KnowledgeConfig:
+    return KnowledgeConfig(
+        enable_extraction=False,
+        enable_completion_extraction=False,
+        enable_injection=False,
+    )
+
+
+__all__ = ["LocalAgentExecutor"]

+ 35 - 0
agent/agent/orchestration/wiring.py

@@ -0,0 +1,35 @@
+"""Composition root for explicit orchestration."""
+
+from __future__ import annotations
+
+from typing import Optional
+
+from .config import OrchestrationConfig
+from .coordinator import TaskCoordinator
+from .executor import LocalAgentExecutor
+from .protocols import ArtifactStore, TaskStore
+from .store import TraceEventSink
+
+
+def wire_orchestration(
+    runner,
+    task_store: TaskStore,
+    artifact_store: ArtifactStore,
+    config: Optional[OrchestrationConfig] = None,
+) -> TaskCoordinator:
+    """Wire ports and adapters without importing any business project."""
+    base_path = getattr(task_store, "base_path", ".trace")
+    coordinator = TaskCoordinator(
+        task_store=task_store,
+        artifact_store=artifact_store,
+        trace_store=runner.trace_store,
+        config=config or OrchestrationConfig(),
+        event_sink=TraceEventSink(str(base_path)),
+    )
+    executor = LocalAgentExecutor(runner)
+    coordinator.set_executor(executor)
+    runner.task_coordinator = coordinator
+    return coordinator
+
+
+__all__ = ["wire_orchestration"]