Sfoglia il codice sorgente

组装:连接阶段二服务、候选仓储与生产安全校验

SamLee 1 giorno fa
parent
commit
f9b8bea171

+ 91 - 0
script_build_host/src/script_build_host/composition.py

@@ -4,6 +4,7 @@ from __future__ import annotations
 
 from collections.abc import Mapping
 from dataclasses import dataclass, field
+from pathlib import Path
 from typing import Any
 
 from agent import wire_orchestration
@@ -14,9 +15,14 @@ from script_build_host.agents import (
     ScriptBuildEvidenceProvider,
     ScriptBuildValidationPolicy,
     ScriptRoleRunConfigResolver,
+    SnapshotRoleSystemPromptResolver,
     register_script_presets,
 )
 from script_build_host.agents.model_resolver import SnapshotModelManifestSource
+from script_build_host.agents.prompt_catalog import (
+    PHASE_TWO_PRESETS,
+    script_prompt_requests,
+)
 from script_build_host.api import ApiSecurity, UploadedTopicGateway, create_app
 from script_build_host.application.input_snapshot_service import ScriptInputSnapshotService
 from script_build_host.application.mission_factory import ScriptMissionFactory
@@ -25,6 +31,14 @@ from script_build_host.application.mission_service import (
     DirectionReconciler,
     ScriptMissionService,
 )
+from script_build_host.application.phase_two_boundary import ScriptPhaseTwoBoundaryVerifier
+from script_build_host.application.phase_two_candidates import PhaseTwoCandidateService
+from script_build_host.application.phase_two_inputs import (
+    AcceptedInputResolver,
+    ActiveFrontierResolver,
+    StoredTaskContractReader,
+)
+from script_build_host.application.phase_two_planning import PhaseTwoPlanningService
 from script_build_host.domain.ports import (
     BuildAuthorizer,
     InputSnapshotRepository,
@@ -35,7 +49,9 @@ from script_build_host.domain.ports import (
     RuntimeManifestProvider,
     ScriptBusinessArtifactRepository,
 )
+from script_build_host.domain.task_contracts import PhaseTwoLimits
 from script_build_host.infrastructure.outbound import OutboundPolicy
+from script_build_host.infrastructure.task_contract_store import FileScriptTaskContractStore
 from script_build_host.tools import LegacyScriptToolGateway, register_script_tools
 from script_build_host.tools.gateway import ImageAdapter, RetrievalAdapter
 
@@ -64,6 +80,15 @@ class HostDependencies:
     websocket_allowed_origins: tuple[str, ...] = ()
     default_model_manifest: Mapping[str, object] = field(default_factory=dict)
     default_datasource_manifest: Mapping[str, object] = field(default_factory=dict)
+    enabled_phase: int = 1
+    agent_data_root: Path | None = None
+    task_contract_store: Any = None
+    candidate_workspaces: Any = None
+    raw_artifact_store: Any = None
+    max_images: int = 8
+    max_image_bytes: int = 10 * 1024 * 1024
+    max_total_image_bytes: int = 25 * 1024 * 1024
+    phase_two_limits: PhaseTwoLimits = field(default_factory=PhaseTwoLimits)
 
 
 @dataclass(frozen=True)
@@ -72,6 +97,8 @@ class HostComposition:
     coordinator: Any
     mission_service: ScriptMissionService
     tool_gateway: LegacyScriptToolGateway
+    phase_two_planning: PhaseTwoPlanningService
+    phase_two_candidates: PhaseTwoCandidateService
 
 
 def compose_host(dependencies: HostDependencies) -> HostComposition:
@@ -91,6 +118,9 @@ def compose_host(dependencies: HostDependencies) -> HostComposition:
             dependencies.bindings,
         ),
     )
+    prompt_resolver = SnapshotRoleSystemPromptResolver(
+        dependencies.bindings, dependencies.input_snapshots
+    )
     coordinator = wire_orchestration(
         dependencies.runner,
         dependencies.task_store,
@@ -101,7 +131,55 @@ def compose_host(dependencies: HostDependencies) -> HostComposition:
         deterministic_validator=ScriptBuildDeterministicValidator(),
         evidence_provider=evidence_provider,
         role_run_config_resolver=model_resolver,
+        role_system_prompt_resolver=prompt_resolver,
+    )
+    contract_store = dependencies.task_contract_store
+    if contract_store is None:
+        data_root = dependencies.agent_data_root
+        if data_root is None:
+            artifact_root = getattr(dependencies.framework_artifact_store, "base_path", None)
+            if artifact_root is None:
+                raise RuntimeError(
+                    "Host requires agent_data_root or an explicit task_contract_store"
+                )
+            data_root = Path(artifact_root)
+        contract_store = FileScriptTaskContractStore(Path(data_root))
+    contract_reader = StoredTaskContractReader(contract_store)
+    accepted_inputs = AcceptedInputResolver(
+        task_store=dependencies.task_store,
+        artifact_store=dependencies.framework_artifact_store,
+        artifacts=dependencies.business_artifacts,
+        contracts=contract_reader,
+        input_snapshots=dependencies.input_snapshots,
+    )
+    boundary_verifier = ScriptPhaseTwoBoundaryVerifier(
+        task_store=dependencies.task_store,
+        artifact_store=dependencies.framework_artifact_store,
+        artifacts=dependencies.business_artifacts,
+        bindings=dependencies.bindings,
+        accepted_inputs=accepted_inputs,
     )
+    planning_service = PhaseTwoPlanningService(
+        coordinator=coordinator,
+        bindings=dependencies.bindings,
+        contracts=contract_store,
+        closure_gate=boundary_verifier,
+        limits=dependencies.phase_two_limits,
+    )
+    candidate_service = PhaseTwoCandidateService(
+        bindings=dependencies.bindings,
+        task_store=dependencies.task_store,
+        framework_artifact_store=dependencies.framework_artifact_store,
+        artifacts=dependencies.business_artifacts,
+        accepted_inputs=accepted_inputs,
+        active_frontier=ActiveFrontierResolver(),
+        workspaces=dependencies.candidate_workspaces,
+        raw_artifacts=dependencies.raw_artifact_store,
+        max_images=dependencies.max_images,
+        max_image_bytes=dependencies.max_image_bytes,
+        max_total_image_bytes=dependencies.max_total_image_bytes,
+    )
+    planning_service.workspace_lifecycle = candidate_service
     transition_gate = BuildTransitionGate()
     direction_reconciler = DirectionReconciler(
         coordinator=coordinator,
@@ -122,7 +200,15 @@ def compose_host(dependencies: HostDependencies) -> HostComposition:
         direction_reconciler=direction_reconciler,
         transition_gate=transition_gate,
         runtime_manifest_provider=dependencies.runtime_manifest_provider,
+        enabled_phase=dependencies.enabled_phase,
+        phase_two_prompt_requests=tuple(
+            item for item in script_prompt_requests() if item.preset in PHASE_TWO_PRESETS
+        ),
+        phase_two_required_presets=tuple(sorted(PHASE_TWO_PRESETS)),
+        phase_two_model_manifest=dict(dependencies.default_model_manifest),
+        phase_two_boundary_verifier=boundary_verifier,
     )
+    planning_service.dispatch_guard = mission_service
     gateway = LegacyScriptToolGateway(
         bindings=dependencies.bindings,
         snapshots=dependencies.input_snapshots,
@@ -131,6 +217,9 @@ def compose_host(dependencies: HostDependencies) -> HostComposition:
         retrieval_adapters=dependencies.retrieval_adapters,
         image_adapter=dependencies.image_adapter,
         dispatch_guard=mission_service,
+        planner_tools=planning_service,
+        candidate_tools=candidate_service,
+        budget_guard=planning_service,
     )
     register_script_tools(dependencies.runner.tools, gateway)
     security = ApiSecurity(
@@ -157,6 +246,8 @@ def compose_host(dependencies: HostDependencies) -> HostComposition:
         coordinator=coordinator,
         mission_service=mission_service,
         tool_gateway=gateway,
+        phase_two_planning=planning_service,
+        phase_two_candidates=candidate_service,
     )
 
 

+ 45 - 4
script_build_host/src/script_build_host/production.py

@@ -7,7 +7,7 @@ from pathlib import Path
 from typing import Any
 
 import httpx
-from agent import FileSystemArtifactStore, FileSystemTaskStore
+from agent import FileSystemArtifactStore, FileSystemTaskStore, FileSystemTraceStore
 
 from script_build_host.adapters import (
     DatabaseFirstPromptSource,
@@ -24,14 +24,20 @@ from script_build_host.adapters import (
 )
 from script_build_host.application import ScriptInputSnapshotService
 from script_build_host.composition import HostComposition, HostDependencies, compose_host
-from script_build_host.domain.ports import BuildAuthorizer, PrincipalProvider
+from script_build_host.domain.ports import (
+    BuildAuthorizer,
+    DurableTraceStoreVerifier,
+    PrincipalProvider,
+)
 from script_build_host.infrastructure.config import ScriptBuildSettings
 from script_build_host.infrastructure.db import DatabaseSessions, create_database_sessions
 from script_build_host.infrastructure.manifests import SettingsRuntimeManifestProvider
 from script_build_host.infrastructure.outbound import OutboundPolicy
 from script_build_host.infrastructure.raw_artifacts import FileRawArtifactStore
+from script_build_host.infrastructure.trace_verifier import FileSystemTraceStoreVerifier
 from script_build_host.repositories import (
     LegacySqlAlchemyInputReader,
+    SqlAlchemyCandidateWorkspaceRepository,
     SqlAlchemyInputSnapshotRepository,
     SqlAlchemyLegacyBuildStateRepository,
     SqlAlchemyMissionBindingRepository,
@@ -50,6 +56,7 @@ class ProductionRuntimeDependencies:
     orchestration_config: Any = None
     event_sink: Any = None
     http_transport: httpx.AsyncBaseTransport | None = None
+    trace_store_verifier: DurableTraceStoreVerifier | None = None
 
 
 @dataclass(slots=True)
@@ -58,12 +65,19 @@ class ProductionHost:
     database: DatabaseSessions
     http_client: httpx.AsyncClient
     runtime_manifest: SettingsRuntimeManifestProvider
+    trace_store: Any
+    trace_store_verifier: DurableTraceStoreVerifier
+    require_trace_attachments: bool = False
     _closed: bool = False
 
     async def validate_startup(self) -> None:
         """Resolve configured endpoints and sources before serving traffic."""
 
         await self.runtime_manifest.load()
+        await self.trace_store_verifier.verify(
+            self.trace_store,
+            require_attachments=self.require_trace_attachments,
+        )
 
     async def close(self) -> None:
         if self._closed:
@@ -81,6 +95,15 @@ def compose_production_host(
 
     if getattr(runtime.runner, "trace_store", None) is None:
         raise RuntimeError("production AgentRunner must provide a durable trace store")
+    trace_store = runtime.runner.trace_store
+    trace_store_verifier = runtime.trace_store_verifier
+    if trace_store_verifier is None:
+        if isinstance(trace_store, FileSystemTraceStore):
+            trace_store_verifier = FileSystemTraceStoreVerifier()
+        else:
+            raise RuntimeError(
+                "external TraceStore factories must provide a DurableTraceStoreVerifier"
+            )
 
     decode_path: Path | None = None
     if not settings.decode_endpoint:
@@ -155,6 +178,8 @@ def compose_production_host(
         knowledge_path=knowledge_path,
     )
     data_root = settings.agent_data_root.resolve()
+    raw_artifacts = FileRawArtifactStore(data_root)
+    candidate_workspaces = SqlAlchemyCandidateWorkspaceRepository(database.write, artifacts)
     composition = compose_host(
         HostDependencies(
             runner=runtime.runner,
@@ -173,7 +198,7 @@ def compose_production_host(
             retrieval_adapters=retrieval_adapters,
             image_adapter=SafeImageAdapter(
                 safe_http,
-                FileRawArtifactStore(data_root),
+                raw_artifacts,
                 max_images=settings.max_images,
                 max_image_bytes=settings.max_image_bytes,
                 max_total_image_bytes=settings.max_total_image_bytes,
@@ -191,9 +216,25 @@ def compose_production_host(
             websocket_allowed_origins=settings.websocket_allowed_origins,
             default_model_manifest=settings.model_manifest,
             default_datasource_manifest=settings.datasource_manifest,
+            enabled_phase=settings.enabled_phase,
+            agent_data_root=data_root,
+            candidate_workspaces=candidate_workspaces,
+            raw_artifact_store=raw_artifacts,
+            max_images=settings.max_images,
+            max_image_bytes=settings.max_image_bytes,
+            max_total_image_bytes=settings.max_total_image_bytes,
+            phase_two_limits=settings.phase_two_limits(),
         )
     )
-    host = ProductionHost(composition, database, http_client, runtime_manifest)
+    host = ProductionHost(
+        composition,
+        database,
+        http_client,
+        runtime_manifest,
+        trace_store,
+        trace_store_verifier,
+        settings.enable_image_understanding,
+    )
     composition.app.state.production_host = host
     composition.app.router.add_event_handler("startup", host.validate_startup)
     composition.app.router.add_event_handler("shutdown", host.close)