|
|
@@ -1,2294 +0,0 @@
|
|
|
-from __future__ import annotations
|
|
|
-
|
|
|
-import asyncio
|
|
|
-import json
|
|
|
-from collections.abc import Mapping, Sequence
|
|
|
-from datetime import UTC, datetime, timedelta
|
|
|
-from pathlib import Path
|
|
|
-from types import SimpleNamespace
|
|
|
-from typing import Any, cast
|
|
|
-from uuid import uuid4
|
|
|
-
|
|
|
-import httpx
|
|
|
-import pytest
|
|
|
-from agent import (
|
|
|
- AgentRunner,
|
|
|
- FileSystemArtifactStore,
|
|
|
- FileSystemTaskStore,
|
|
|
- FileSystemTraceStore,
|
|
|
- Message,
|
|
|
-)
|
|
|
-from agent.orchestration import ArtifactRef, DecisionAction, TaskStatus, ValidationVerdict
|
|
|
-from sqlalchemy import select
|
|
|
-
|
|
|
-from script_build_host.agents.prompts import (
|
|
|
- phase_one_prompt_manifest,
|
|
|
- phase_three_prompt_manifest,
|
|
|
- script_build_prompt_manifest,
|
|
|
-)
|
|
|
-from script_build_host.application.input_snapshot_service import ScriptInputSnapshotService
|
|
|
-from script_build_host.application.legacy_projection import LegacyDetailProjectionService
|
|
|
-from script_build_host.application.mission_service import PHASE_ONE_CAPABILITY_BOUNDARY
|
|
|
-from script_build_host.application.phase_two_planning import (
|
|
|
- PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
-)
|
|
|
-from script_build_host.composition import HostDependencies, compose_host
|
|
|
-from script_build_host.domain.artifacts import ArtifactKind
|
|
|
-from script_build_host.domain.input_snapshot import ScriptBuildInput
|
|
|
-from script_build_host.domain.records import BuildStatus, Principal
|
|
|
-from script_build_host.infrastructure.canonical_json import canonical_sha256
|
|
|
-from script_build_host.infrastructure.final_publication import (
|
|
|
- SqlAlchemyFinalPublicationUnitOfWork,
|
|
|
-)
|
|
|
-from script_build_host.infrastructure.legacy_tables import (
|
|
|
- script_build_paragraph,
|
|
|
- script_build_record,
|
|
|
-)
|
|
|
-from script_build_host.infrastructure.ownership import FencedCommandGate, OwnerLease
|
|
|
-from script_build_host.infrastructure.tables import (
|
|
|
- artifact_version_table,
|
|
|
- input_snapshot_table,
|
|
|
- mission_binding_table,
|
|
|
- publication_table,
|
|
|
-)
|
|
|
-from script_build_host.infrastructure.task_contract_store import FileScriptTaskContractStore
|
|
|
-from script_build_host.internal_e2e import _collect_diagnostics
|
|
|
-from script_build_host.repositories.legacy_state import SqlAlchemyLegacyBuildStateRepository
|
|
|
-from script_build_host.repositories.sqlalchemy import (
|
|
|
- SqlAlchemyInputSnapshotRepository,
|
|
|
- SqlAlchemyMissionBindingRepository,
|
|
|
- SqlAlchemyPublicationRepository,
|
|
|
- SqlAlchemyScriptBusinessArtifactRepository,
|
|
|
-)
|
|
|
-from script_build_host.repositories.workspace import SqlAlchemyCandidateWorkspaceRepository
|
|
|
-from script_build_host.tools.gateway import RetrievalResult
|
|
|
-
|
|
|
-ROOT = "phase-two-real-runner-root"
|
|
|
-FULL_SCOPE = "script-build://scopes/full"
|
|
|
-
|
|
|
-
|
|
|
-def _tool(call_id: str, name: str, arguments: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
- return {
|
|
|
- "content": "",
|
|
|
- "tool_calls": [
|
|
|
- {
|
|
|
- "id": call_id,
|
|
|
- "type": "function",
|
|
|
- "function": {"name": name, "arguments": json.dumps(arguments)},
|
|
|
- }
|
|
|
- ],
|
|
|
- "finish_reason": "tool_calls",
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-def _role_prompt(messages: Sequence[Mapping[str, Any]]) -> Mapping[str, Any]:
|
|
|
- for message in reversed(messages):
|
|
|
- if message.get("role") != "user" or not isinstance(message.get("content"), str):
|
|
|
- continue
|
|
|
- try:
|
|
|
- value = json.loads(cast(str, message["content"]))
|
|
|
- except ValueError:
|
|
|
- continue
|
|
|
- if isinstance(value, dict) and "task_spec" in value:
|
|
|
- return value
|
|
|
- raise AssertionError("role trace does not contain the protected Task prompt")
|
|
|
-
|
|
|
-
|
|
|
-def _last_tool_value(messages: Sequence[Mapping[str, Any]]) -> Any:
|
|
|
- for message in reversed(messages):
|
|
|
- if message.get("role") != "tool" or not isinstance(message.get("content"), str):
|
|
|
- continue
|
|
|
- try:
|
|
|
- return json.loads(cast(str, message["content"]))
|
|
|
- except ValueError:
|
|
|
- return None
|
|
|
- return None
|
|
|
-
|
|
|
-
|
|
|
-def _artifact_ref_payload(ref: ArtifactRef) -> dict[str, Any]:
|
|
|
- return {
|
|
|
- "uri": ref.uri,
|
|
|
- "kind": ref.kind,
|
|
|
- "version": ref.version,
|
|
|
- "digest": ref.digest,
|
|
|
- "summary": ref.summary,
|
|
|
- "metadata": ref.metadata,
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-def _decision_ref(
|
|
|
- decision_id: str,
|
|
|
- artifact_ref: ArtifactRef,
|
|
|
- *,
|
|
|
- scope_ref: str,
|
|
|
- task_kind: str,
|
|
|
-) -> dict[str, Any]:
|
|
|
- return {
|
|
|
- "decision_id": decision_id,
|
|
|
- "artifact_ref": _artifact_ref_payload(artifact_ref),
|
|
|
- "scope_ref": scope_ref,
|
|
|
- "expected_task_kind": task_kind,
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-def _contract(
|
|
|
- task_kind: str,
|
|
|
- *,
|
|
|
- scope_ref: str,
|
|
|
- write_scope: Sequence[str],
|
|
|
- objective: str | None = None,
|
|
|
- input_decision_refs: Sequence[Mapping[str, Any]] = (),
|
|
|
- base_artifact_ref: Mapping[str, Any] | None = None,
|
|
|
- closure: Sequence[Mapping[str, Any]] = (),
|
|
|
- adopted: Sequence[str] = (),
|
|
|
- held: Sequence[str] = (),
|
|
|
- compose_order: Sequence[str] = (),
|
|
|
- comparison_decision_refs: Sequence[Mapping[str, Any]] = (),
|
|
|
- supersedes_decision_ids: Sequence[str] = (),
|
|
|
- goal_ids: Sequence[str] | None = None,
|
|
|
-) -> dict[str, Any]:
|
|
|
- del write_scope, closure, adopted, held, compose_order
|
|
|
- input_decision_ids = [str(item["decision_id"]) for item in input_decision_refs]
|
|
|
- base_decision_id = None
|
|
|
- if base_artifact_ref is not None:
|
|
|
- base_uri = str(base_artifact_ref["uri"])
|
|
|
- base_decision_id = next(
|
|
|
- (
|
|
|
- str(item["decision_id"])
|
|
|
- for item in input_decision_refs
|
|
|
- if str(cast(Mapping[str, Any], item["artifact_ref"])["uri"]) == base_uri
|
|
|
- ),
|
|
|
- None,
|
|
|
- )
|
|
|
- assert base_decision_id is not None
|
|
|
- criterion = (
|
|
|
- {
|
|
|
- "client_key": f"{task_kind}-closed",
|
|
|
- "criterion_type": "evidence_nonempty",
|
|
|
- "params": {"minimum_count": 1},
|
|
|
- "hard": True,
|
|
|
- }
|
|
|
- if task_kind.endswith("-retrieval")
|
|
|
- else {
|
|
|
- "client_key": f"{task_kind}-closed",
|
|
|
- "criterion_type": "custom",
|
|
|
- "description": "the immutable output is concrete and causally closed",
|
|
|
- "hard": True,
|
|
|
- }
|
|
|
- )
|
|
|
- return {
|
|
|
- "task_kind": task_kind,
|
|
|
- "scope_selector": {
|
|
|
- "anchor": "mission",
|
|
|
- "path": scope_ref.removeprefix("script-build://scopes/").split("/")[:4],
|
|
|
- },
|
|
|
- "intent_class": (
|
|
|
- "compose"
|
|
|
- if task_kind == "compose"
|
|
|
- else "portfolio"
|
|
|
- if task_kind == "candidate-portfolio"
|
|
|
- else "explore"
|
|
|
- ),
|
|
|
- "objective": objective or f"produce one concrete and independently verifiable {task_kind}",
|
|
|
- "input_decision_ids": input_decision_ids,
|
|
|
- "base_decision_id": base_decision_id,
|
|
|
- "gap_key": None,
|
|
|
- "criteria": [criterion],
|
|
|
- "goal_ids": list(goal_ids or ()),
|
|
|
- "supersedes_decision_ids": list(supersedes_decision_ids),
|
|
|
- "comparison_decision_ids": [str(item["decision_id"]) for item in comparison_decision_refs],
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-def _task_kind(task: Any) -> str:
|
|
|
- values = [
|
|
|
- item.rsplit("/", 1)[-1] for item in task.current_spec.context_refs if "/task-kinds/" in item
|
|
|
- ]
|
|
|
- assert len(values) == 1
|
|
|
- return values[0]
|
|
|
-
|
|
|
-
|
|
|
-def _accepted_ref(ledger: Any, task: Any) -> tuple[str, ArtifactRef]:
|
|
|
- decision_id = task.decision_ids[-1]
|
|
|
- decision = ledger.decisions[decision_id]
|
|
|
- attempt = ledger.attempts[decision.attempt_id]
|
|
|
- refs = [*attempt.submission.artifact_refs, *attempt.submission.evidence_refs]
|
|
|
- assert len(refs) == 1
|
|
|
- return decision_id, refs[0]
|
|
|
-
|
|
|
-
|
|
|
-class _PhaseTwoScriptedLLM:
|
|
|
- """Exercise phase-two through the actual preset/tool/LocalExecutor surface."""
|
|
|
-
|
|
|
- def __init__(self, store: FileSystemTaskStore) -> None:
|
|
|
- self.store = store
|
|
|
- self.calls = 0
|
|
|
- self.call_namespace = uuid4().hex
|
|
|
- self.tool_calls_by_name: dict[str, int] = {}
|
|
|
- self.submit_attempt_arguments: list[dict[str, Any]] = []
|
|
|
- self.worker_kinds: list[str] = []
|
|
|
- self.validator_kinds: list[str] = []
|
|
|
- self.phase_two_active = False
|
|
|
- self.compose_candidates_saved = 0
|
|
|
- self.direction_goal_id: str | None = None
|
|
|
-
|
|
|
- def _call(self, name: str, arguments: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
- self.calls += 1
|
|
|
- self.tool_calls_by_name[name] = self.tool_calls_by_name.get(name, 0) + 1
|
|
|
- if name == "submit_attempt":
|
|
|
- self.submit_attempt_arguments.append(dict(arguments))
|
|
|
- return _tool(f"phase2-{self.call_namespace}-{self.calls}", name, arguments)
|
|
|
-
|
|
|
- async def __call__(self, messages, tools, **_kwargs):
|
|
|
- if self.direction_goal_id is None:
|
|
|
- for message in reversed(messages):
|
|
|
- if not isinstance(message.get("content"), str):
|
|
|
- continue
|
|
|
- try:
|
|
|
- value = json.loads(cast(str, message["content"]))
|
|
|
- except ValueError:
|
|
|
- continue
|
|
|
- if isinstance(value, dict) and isinstance(
|
|
|
- value.get("goal_ids_by_client_key"), dict
|
|
|
- ):
|
|
|
- self.direction_goal_id = str(
|
|
|
- value["goal_ids_by_client_key"]["observable-opening"]
|
|
|
- )
|
|
|
- break
|
|
|
- if isinstance(value, dict) and isinstance(
|
|
|
- value.get("active_direction_goal_ids"), list
|
|
|
- ):
|
|
|
- goal_ids = cast(list[str], value["active_direction_goal_ids"])
|
|
|
- if goal_ids:
|
|
|
- self.direction_goal_id = goal_ids[0]
|
|
|
- break
|
|
|
- self.phase_two_active = self.phase_two_active or any(
|
|
|
- PHASE_TWO_CANDIDATE_PORTFOLIO_READY in str(item.get("content", "")) for item in messages
|
|
|
- )
|
|
|
- names = {item["function"]["name"] for item in tools or []}
|
|
|
- if "plan_script_tasks" in names:
|
|
|
- return await self._planner()
|
|
|
- if "submit_validation" in names:
|
|
|
- return self._validator(messages)
|
|
|
- if "submit_attempt" in names:
|
|
|
- return self._worker(messages, names)
|
|
|
- raise AssertionError(f"unexpected tool surface: {sorted(names)}")
|
|
|
-
|
|
|
- async def _planner(self) -> dict[str, Any]:
|
|
|
- ledger = await self.store.load(ROOT)
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- tasks = [task for task in ledger.tasks.values() if task.task_id != ledger.root_task_id]
|
|
|
-
|
|
|
- def by_kind(kind: str) -> list[Any]:
|
|
|
- return [task for task in tasks if _task_kind(task) == kind]
|
|
|
-
|
|
|
- def by_objective(value: str) -> Any | None:
|
|
|
- return next(
|
|
|
- (task for task in tasks if task.current_spec.objective == value),
|
|
|
- None,
|
|
|
- )
|
|
|
-
|
|
|
- direction = next(iter(by_kind("direction")), None)
|
|
|
- if direction is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "direction",
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- write_scope=("script-build://writes/direction",),
|
|
|
- )
|
|
|
- ]
|
|
|
- },
|
|
|
- )
|
|
|
- retrieval = next(
|
|
|
- (
|
|
|
- task
|
|
|
- for task in by_kind("decode-retrieval")
|
|
|
- if task.parent_task_id == direction.task_id
|
|
|
- ),
|
|
|
- None,
|
|
|
- )
|
|
|
- if retrieval is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": direction.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "decode-retrieval",
|
|
|
- scope_ref="script-build://scopes/full/direction-evidence",
|
|
|
- write_scope=(),
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if retrieval.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [retrieval.task_id]})
|
|
|
- if retrieval.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(retrieval, ledger)
|
|
|
- if direction.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [direction.task_id]})
|
|
|
- if direction.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(direction, ledger)
|
|
|
- if not self.phase_two_active:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": root.task_id,
|
|
|
- "action": "block",
|
|
|
- "reason": PHASE_ONE_CAPABILITY_BOUNDARY,
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- direction_decision, direction_ref = _accepted_ref(ledger, direction)
|
|
|
- assert self.direction_goal_id is not None
|
|
|
- goal_ids = (self.direction_goal_id,)
|
|
|
- direction_pin = _decision_ref(
|
|
|
- direction_decision,
|
|
|
- direction_ref,
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- task_kind="direction",
|
|
|
- )
|
|
|
-
|
|
|
- portfolio = next(iter(by_kind("candidate-portfolio")), None)
|
|
|
- if portfolio is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "candidate-portfolio",
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- write_scope=("script-build://writes",),
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ]
|
|
|
- },
|
|
|
- )
|
|
|
- compose = next(iter(by_kind("compose")), None)
|
|
|
- if compose is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": portfolio.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "compose",
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- write_scope=("script-build://writes",),
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- structure = next(iter(by_kind("structure")), None)
|
|
|
- if structure is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "structure",
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if structure.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [structure.task_id]})
|
|
|
- if structure.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(structure, ledger)
|
|
|
-
|
|
|
- structure_decision, structure_ref = _accepted_ref(ledger, structure)
|
|
|
- first_paragraph_objective = "draft opening candidate A"
|
|
|
- replacement_objective = "replace the placeholder opening with retrieved evidence"
|
|
|
- paragraph = by_objective(first_paragraph_objective)
|
|
|
- if paragraph is None:
|
|
|
- structure_pin = _decision_ref(
|
|
|
- structure_decision,
|
|
|
- structure_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="structure",
|
|
|
- )
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "paragraph",
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
- objective=first_paragraph_objective,
|
|
|
- input_decision_refs=(direction_pin, structure_pin),
|
|
|
- base_artifact_ref=_artifact_ref_payload(structure_ref),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if paragraph.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [paragraph.task_id]})
|
|
|
- if paragraph.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(paragraph, ledger)
|
|
|
-
|
|
|
- paragraph_decision, paragraph_ref = _accepted_ref(ledger, paragraph)
|
|
|
- element_objective = "attach one independent element to the opening"
|
|
|
- element = by_objective(element_objective)
|
|
|
- if element is None:
|
|
|
- paragraph_pin = _decision_ref(
|
|
|
- paragraph_decision,
|
|
|
- paragraph_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- )
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "element-set",
|
|
|
- scope_ref="script-build://scopes/full/opening/elements",
|
|
|
- write_scope=("script-build://writes/elements",),
|
|
|
- objective=element_objective,
|
|
|
- input_decision_refs=(direction_pin, paragraph_pin),
|
|
|
- base_artifact_ref=_artifact_ref_payload(paragraph_ref),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if element.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [element.task_id]})
|
|
|
- if element.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(element, ledger)
|
|
|
- element_decision, _element_ref = _accepted_ref(ledger, element)
|
|
|
- if compose.current_spec_version == 1:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": compose.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "pin the adopted paragraph and formal compose order",
|
|
|
- "selected_decision_ids": [
|
|
|
- structure_decision,
|
|
|
- paragraph_decision,
|
|
|
- element_decision,
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if compose.status is TaskStatus.PENDING:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [compose.task_id]})
|
|
|
- if compose.status is TaskStatus.AWAITING_DECISION:
|
|
|
- validation = ledger.validations[compose.validation_ids[-1]]
|
|
|
- if validation.verdict is ValidationVerdict.PASSED:
|
|
|
- return self._accept(compose, ledger)
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": compose.task_id,
|
|
|
- "validation_id": validation.validation_id,
|
|
|
- "action": "split",
|
|
|
- "reason": "replace only the unrealized opening after targeted retrieval",
|
|
|
- "child_contracts": [
|
|
|
- _contract(
|
|
|
- "paragraph",
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
- objective=replacement_objective,
|
|
|
- input_decision_refs=(
|
|
|
- direction_pin,
|
|
|
- _decision_ref(
|
|
|
- structure_decision,
|
|
|
- structure_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="structure",
|
|
|
- ),
|
|
|
- _decision_ref(
|
|
|
- paragraph_decision,
|
|
|
- paragraph_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- ),
|
|
|
- ),
|
|
|
- base_artifact_ref=_artifact_ref_payload(structure_ref),
|
|
|
- supersedes_decision_ids=(paragraph_decision,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- # Compose is now a deterministic Workbench Worker. A complete creative
|
|
|
- # frontier produces one valid artifact instead of asking an LLM Worker
|
|
|
- # to manufacture an invalid first candidate for this fixture.
|
|
|
- if compose.status is TaskStatus.COMPLETED:
|
|
|
- compose_decision, _compose_ref = _accepted_ref(ledger, compose)
|
|
|
- if portfolio.current_spec_version == 1:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": portfolio.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "pin the uniquely adopted StructuredScript",
|
|
|
- "selected_decision_ids": [compose_decision],
|
|
|
- },
|
|
|
- )
|
|
|
- if portfolio.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [portfolio.task_id]})
|
|
|
- if portfolio.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(portfolio, ledger)
|
|
|
- assert portfolio.status is TaskStatus.COMPLETED
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": root.task_id,
|
|
|
- "action": "block",
|
|
|
- "reason": PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- replacement = by_objective(replacement_objective)
|
|
|
- if replacement is None:
|
|
|
- raise AssertionError("failed Compose must SPLIT one bounded replacement Task")
|
|
|
- phase_two_retrieval = next(
|
|
|
- (
|
|
|
- task
|
|
|
- for task in by_kind("decode-retrieval")
|
|
|
- if task.parent_task_id == replacement.task_id
|
|
|
- ),
|
|
|
- None,
|
|
|
- )
|
|
|
- if phase_two_retrieval is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": replacement.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "decode-retrieval",
|
|
|
- scope_ref="script-build://scopes/full/opening/evidence",
|
|
|
- write_scope=(),
|
|
|
- objective="retrieve evidence for the replacement opening",
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if phase_two_retrieval.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [phase_two_retrieval.task_id]})
|
|
|
- if phase_two_retrieval.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(phase_two_retrieval, ledger)
|
|
|
- if replacement.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [replacement.task_id]})
|
|
|
- if replacement.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(replacement, ledger)
|
|
|
-
|
|
|
- replacement_decision, replacement_ref = _accepted_ref(ledger, replacement)
|
|
|
- comparison = next(iter(by_kind("compare")), None)
|
|
|
- if comparison is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "compare",
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- write_scope=(),
|
|
|
- objective="compare opening candidates A and B symmetrically",
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- comparison_decision_refs=(
|
|
|
- _decision_ref(
|
|
|
- paragraph_decision,
|
|
|
- paragraph_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- ),
|
|
|
- _decision_ref(
|
|
|
- replacement_decision,
|
|
|
- replacement_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- ),
|
|
|
- ),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if comparison.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [comparison.task_id]})
|
|
|
- if comparison.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(comparison, ledger)
|
|
|
-
|
|
|
- _comparison_decision, _comparison_ref = _accepted_ref(ledger, comparison)
|
|
|
- if compose.status is TaskStatus.NEEDS_REPLAN:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": compose.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "pin the accepted replacement and the symmetric comparison",
|
|
|
- "selected_decision_ids": [
|
|
|
- structure_decision,
|
|
|
- replacement_decision,
|
|
|
- element_decision,
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if compose.status is TaskStatus.PENDING:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [compose.task_id]})
|
|
|
- if compose.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(compose, ledger)
|
|
|
-
|
|
|
- compose_decision, _compose_ref = _accepted_ref(ledger, compose)
|
|
|
- if portfolio.current_spec_version == 1:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": portfolio.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "pin the uniquely adopted StructuredScript",
|
|
|
- "selected_decision_ids": [compose_decision],
|
|
|
- },
|
|
|
- )
|
|
|
- if portfolio.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [portfolio.task_id]})
|
|
|
- if portfolio.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(portfolio, ledger)
|
|
|
-
|
|
|
- assert portfolio.status is TaskStatus.COMPLETED
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": root.task_id,
|
|
|
- "action": "block",
|
|
|
- "reason": PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- def _accept(self, task: Any, ledger: Any) -> dict[str, Any]:
|
|
|
- validation = ledger.validations[task.validation_ids[-1]]
|
|
|
- assert validation.verdict is ValidationVerdict.PASSED
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": task.task_id,
|
|
|
- "validation_id": validation.validation_id,
|
|
|
- "action": "accept",
|
|
|
- "reason": "the independent Validator passed the frozen artifact",
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- def _worker(self, messages, names: set[str]) -> dict[str, Any]:
|
|
|
- prompt = _role_prompt(messages)
|
|
|
- spec = cast(Mapping[str, Any], prompt["task_spec"])
|
|
|
- refs = cast(Sequence[str], spec["context_refs"])
|
|
|
- kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
|
- if kind not in self.worker_kinds:
|
|
|
- self.worker_kinds.append(kind)
|
|
|
- last = _last_tool_value(messages)
|
|
|
- if kind == "decode-retrieval":
|
|
|
- if not isinstance(last, dict) or not last.get("submitted"):
|
|
|
- return self._call(
|
|
|
- "search_script_decode_case",
|
|
|
- {"return_field": "主脉络", "keyword": "observable reversal", "top_k": 3},
|
|
|
- )
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- if kind == "direction":
|
|
|
- if not isinstance(last, dict) or "direction_handle" not in last:
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- accepted = cast(Sequence[Mapping[str, Any]], role_context["accepted_inputs"])
|
|
|
- return self._call(
|
|
|
- "save_direction_candidate",
|
|
|
- {
|
|
|
- "goals": [
|
|
|
- {
|
|
|
- "client_key": "observable-opening",
|
|
|
- "statement": "open with one observable reversal",
|
|
|
- "rationale": "the frozen topic benefits from a concrete hook",
|
|
|
- "success_criteria": [
|
|
|
- "the opening contains one observable reversal"
|
|
|
- ],
|
|
|
- }
|
|
|
- ],
|
|
|
- "constraints": [
|
|
|
- {
|
|
|
- "client_key": "grounded-direction",
|
|
|
- "statement": "the direction is concrete and testable",
|
|
|
- "rationale": "accepted evidence must remain traceable",
|
|
|
- }
|
|
|
- ],
|
|
|
- "preferences": [],
|
|
|
- "strategy_handles": [],
|
|
|
- "evidence_decision_ids": [accepted[0]["decision_id"]],
|
|
|
- },
|
|
|
- )
|
|
|
- goal_map = cast(Mapping[str, str], last["goal_ids_by_client_key"])
|
|
|
- self.direction_goal_id = goal_map["observable-opening"]
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- if kind == "structure":
|
|
|
- if not isinstance(last, dict) or last.get("status") != "saved":
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- return self._call(
|
|
|
- "save_script_paragraphs",
|
|
|
- {
|
|
|
- "paragraphs": [
|
|
|
- {
|
|
|
- "client_key": "opening",
|
|
|
- "paragraph_index": 1,
|
|
|
- "name": "Opening structure",
|
|
|
- "content_range": {
|
|
|
- "scope": "opening",
|
|
|
- "beats": ["setup", "reversal"],
|
|
|
- },
|
|
|
- "level": 1,
|
|
|
- "theme_elements": [
|
|
|
- {
|
|
|
- "原子点": "assumption changes after evidence",
|
|
|
- "维度": "claim",
|
|
|
- "维度类型": "主维度",
|
|
|
- }
|
|
|
- ],
|
|
|
- "form_elements": [
|
|
|
- {
|
|
|
- "原子点": "setup then concrete reversal",
|
|
|
- "维度": "shape",
|
|
|
- "维度类型": "主维度",
|
|
|
- }
|
|
|
- ],
|
|
|
- "function_elements": [
|
|
|
- {
|
|
|
- "原子点": "create curiosity without abstraction",
|
|
|
- "维度": "job",
|
|
|
- "维度类型": "主维度",
|
|
|
- }
|
|
|
- ],
|
|
|
- "feeling_elements": [
|
|
|
- {"原子点": "measured surprise", "维度": "tone"}
|
|
|
- ],
|
|
|
- }
|
|
|
- ],
|
|
|
- "expected_state_revision": role_context["state_revision"],
|
|
|
- },
|
|
|
- )
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- if kind == "paragraph":
|
|
|
- if isinstance(last, dict) and last.get("status") == "saved":
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- paragraphs = cast(Sequence[Mapping[str, Any]], role_context["paragraph_targets"])
|
|
|
- replacement = "replace the placeholder" in str(spec["objective"])
|
|
|
- paragraph: dict[str, Any] = {
|
|
|
- "theme": (
|
|
|
- "The retrieved observable reversal changes the opening claim."
|
|
|
- if replacement
|
|
|
- else "A familiar expectation changes after a visible detail."
|
|
|
- ),
|
|
|
- "form": "A concrete setup followed by one concise reversal.",
|
|
|
- "function": "Give the audience an immediate reason to continue.",
|
|
|
- "feeling": "Measured surprise grounded in observation.",
|
|
|
- "description": "The opening overturns one assumption with evidence.",
|
|
|
- "full_description": (
|
|
|
- "Use the accepted retrieval evidence to replace the unrealized opening and "
|
|
|
- "show exactly how the observed detail changes the interpretation."
|
|
|
- if replacement
|
|
|
- else "Start from the familiar expectation, name the visible detail, and show "
|
|
|
- "exactly how that detail changes the interpretation."
|
|
|
- ),
|
|
|
- }
|
|
|
- if paragraphs:
|
|
|
- paragraph["paragraph_target_key"] = paragraphs[0]["paragraph_target_key"]
|
|
|
- else:
|
|
|
- paragraph.update(
|
|
|
- {
|
|
|
- "client_key": "opening",
|
|
|
- "paragraph_index": 1,
|
|
|
- "name": "Opening",
|
|
|
- "content_range": {"scope": "opening", "beats": ["setup", "reversal"]},
|
|
|
- "level": 1,
|
|
|
- "theme_elements": [
|
|
|
- {"原子点": "visible reversal", "维度": "claim", "维度类型": "主维度"}
|
|
|
- ],
|
|
|
- "form_elements": [
|
|
|
- {"原子点": "setup and reversal", "维度": "shape", "维度类型": "主维度"}
|
|
|
- ],
|
|
|
- "function_elements": [
|
|
|
- {"原子点": "create curiosity", "维度": "job", "维度类型": "主维度"}
|
|
|
- ],
|
|
|
- "feeling_elements": [{"原子点": "measured surprise", "维度": "tone"}],
|
|
|
- }
|
|
|
- )
|
|
|
- return self._call(
|
|
|
- "save_script_paragraphs",
|
|
|
- {
|
|
|
- "paragraphs": [paragraph],
|
|
|
- "expected_state_revision": role_context["state_revision"],
|
|
|
- },
|
|
|
- )
|
|
|
- if kind == "element-set":
|
|
|
- if isinstance(last, dict) and last.get("created") == 1 and last.get("linked") == 1:
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- paragraphs = cast(Sequence[Mapping[str, Any]], role_context["paragraph_targets"])
|
|
|
- assert len(paragraphs) == 1
|
|
|
- return self._call(
|
|
|
- "save_script_elements",
|
|
|
- {
|
|
|
- "elements": [
|
|
|
- {
|
|
|
- "client_key": "reversal",
|
|
|
- "name": "Observable reversal",
|
|
|
- "dimension_primary": "实质",
|
|
|
- "dimension_secondary": "opening-evidence",
|
|
|
- "commonality_analysis": {
|
|
|
- "summary": "a visible detail changes the claim"
|
|
|
- },
|
|
|
- "topic_support": {"strength": "high"},
|
|
|
- "weight_score": {"value": 0.9},
|
|
|
- "support_elements": [{"source_handle": "accepted-opening"}],
|
|
|
- }
|
|
|
- ],
|
|
|
- "links": [
|
|
|
- {
|
|
|
- "paragraph_target_key": paragraphs[0]["paragraph_target_key"],
|
|
|
- "element_client_keys": ["reversal"],
|
|
|
- }
|
|
|
- ],
|
|
|
- "expected_state_revision": role_context["state_revision"],
|
|
|
- },
|
|
|
- )
|
|
|
- if kind == "compare":
|
|
|
- if isinstance(last, dict) and "artifact_ref" in last:
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- candidates = list(role_context["task"]["comparison_decision_ids"])
|
|
|
- assert len(candidates) == 2
|
|
|
- return self._call(
|
|
|
- "save_comparison_candidate",
|
|
|
- {
|
|
|
- "criterion_results": [
|
|
|
- {
|
|
|
- "criterion_id": "compare-closed",
|
|
|
- "candidate_results": [
|
|
|
- {
|
|
|
- "decision_id": candidate,
|
|
|
- "reason": "reviewed against the same frozen criterion",
|
|
|
- }
|
|
|
- for candidate in candidates
|
|
|
- ],
|
|
|
- }
|
|
|
- ],
|
|
|
- "conflicts": [
|
|
|
- "candidate B realizes the retrieved evidence while candidate A does not"
|
|
|
- ],
|
|
|
- "recommended_decision_id": candidates[-1],
|
|
|
- },
|
|
|
- )
|
|
|
- if kind in {"compose", "candidate-portfolio"}:
|
|
|
- raise AssertionError(f"{kind} must be executed by DeterministicWorker")
|
|
|
- raise AssertionError(f"unexpected Worker kind {kind}; tools={sorted(names)}")
|
|
|
-
|
|
|
- def _validator(self, messages) -> dict[str, Any]:
|
|
|
- prompt = _role_prompt(messages)
|
|
|
- spec = cast(Mapping[str, Any], prompt["task_spec"])
|
|
|
- refs = cast(Sequence[str], spec["context_refs"])
|
|
|
- kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
|
- if kind not in self.validator_kinds:
|
|
|
- self.validator_kinds.append(kind)
|
|
|
- last = _last_tool_value(messages)
|
|
|
- if not isinstance(last, dict) or "rule_results" not in last:
|
|
|
- return self._call("deterministic_precheck", {})
|
|
|
- criteria = cast(Sequence[Mapping[str, Any]], spec["acceptance_criteria"])
|
|
|
- role_context = cast(Mapping[str, Any], prompt["role_context"])
|
|
|
- evidence_handle = role_context["evidence_handles"][0]["evidence_handle"]
|
|
|
- failed_rules = [item for item in last["rule_results"] if item["verdict"] != "passed"]
|
|
|
- if failed_rules:
|
|
|
- return self._call(
|
|
|
- "submit_validation",
|
|
|
- {
|
|
|
- "verdict": "failed",
|
|
|
- "criterion_results": [
|
|
|
- {
|
|
|
- "criterion_id": item["criterion_id"],
|
|
|
- "verdict": "failed",
|
|
|
- "reason": "the rendered candidate still contains a placeholder",
|
|
|
- }
|
|
|
- for item in criteria
|
|
|
- ],
|
|
|
- "defects": [
|
|
|
- {
|
|
|
- "defect_code": "REALIZATION_PLACEHOLDER",
|
|
|
- "criterion_id": criteria[0]["criterion_id"],
|
|
|
- "scope_selector": {
|
|
|
- "anchor": "mission",
|
|
|
- "path": ["full", "opening"],
|
|
|
- },
|
|
|
- "observed_excerpt": (
|
|
|
- "TODO replace this placeholder opening with retrieved evidence."
|
|
|
- ),
|
|
|
- "evidence_handle_ids": [evidence_handle],
|
|
|
- "severity": "hard",
|
|
|
- "invalidated_inputs": [],
|
|
|
- "recommended_action_class": "split",
|
|
|
- }
|
|
|
- ],
|
|
|
- "recommendation": "split",
|
|
|
- },
|
|
|
- )
|
|
|
- assert all(item["verdict"] == "passed" for item in last["rule_results"])
|
|
|
- return self._call(
|
|
|
- "submit_validation",
|
|
|
- {
|
|
|
- "verdict": "passed",
|
|
|
- "criterion_results": [
|
|
|
- {
|
|
|
- "criterion_id": item["criterion_id"],
|
|
|
- "verdict": "passed",
|
|
|
- "reason": "the immutable artifact passed deterministic and semantic review",
|
|
|
- "evidence_handle_ids": [evidence_handle],
|
|
|
- }
|
|
|
- for item in criteria
|
|
|
- ],
|
|
|
- "defects": [],
|
|
|
- "recommendation": "accept",
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-class _PhaseThreeScriptedLLM(_PhaseTwoScriptedLLM):
|
|
|
- """Continue the real Phase2 runner through the protected Root delivery."""
|
|
|
-
|
|
|
- def __init__(self, store: FileSystemTaskStore) -> None:
|
|
|
- super().__init__(store)
|
|
|
- self.phase_three_active = False
|
|
|
-
|
|
|
- async def __call__(self, messages, tools, **kwargs):
|
|
|
- self.phase_three_active = self.phase_three_active or any(
|
|
|
- "script-build-phase-three/v1" in str(item.get("content", "")) for item in messages
|
|
|
- )
|
|
|
- names = {item["function"]["name"] for item in tools or []}
|
|
|
- if self.phase_three_active and "plan_script_tasks" in names:
|
|
|
- return await self._phase_three_planner()
|
|
|
- return await super().__call__(messages, tools, **kwargs)
|
|
|
-
|
|
|
- async def _phase_three_planner(self) -> dict[str, Any]:
|
|
|
- ledger = await self.store.load(ROOT)
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- assert _task_kind(root) == "root-delivery"
|
|
|
- if root.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [root.task_id]})
|
|
|
- if root.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(root, ledger)
|
|
|
- if root.status is TaskStatus.COMPLETED:
|
|
|
- return {"content": "Root delivery accepted", "finish_reason": "stop"}
|
|
|
- raise AssertionError(f"unexpected Root delivery status {root.status}")
|
|
|
-
|
|
|
- def _worker(self, messages, names: set[str]) -> dict[str, Any]:
|
|
|
- prompt = _role_prompt(messages)
|
|
|
- refs = cast(Sequence[str], cast(Mapping[str, Any], prompt["task_spec"])["context_refs"])
|
|
|
- kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
|
- if kind != "root-delivery":
|
|
|
- return super()._worker(messages, names)
|
|
|
- if kind not in self.worker_kinds:
|
|
|
- self.worker_kinds.append(kind)
|
|
|
- last = _last_tool_value(messages)
|
|
|
- if isinstance(last, dict) and "artifact_ref" in last:
|
|
|
- return self._call("submit_attempt", {})
|
|
|
- if isinstance(last, dict) and "legacy_projection_digest" in last:
|
|
|
- return self._call(
|
|
|
- "save_root_delivery_manifest",
|
|
|
- {"build_summary": "final summary", "blocking_defects": []},
|
|
|
- )
|
|
|
- return self._call("legacy_projection_dry_run", {"build_summary": "final summary"})
|
|
|
-
|
|
|
-
|
|
|
-class _OrderedExplorationScriptedLLM(_PhaseTwoScriptedLLM):
|
|
|
- """Close Phase2 after exercising each creative preset in a chosen order."""
|
|
|
-
|
|
|
- def __init__(self, store: FileSystemTaskStore, order: tuple[str, str, str]) -> None:
|
|
|
- super().__init__(store)
|
|
|
- self.order = order
|
|
|
- # This scenario checks ordering, not placeholder repair. The parent Worker
|
|
|
- # increments this before saving, so its first Compose is fully realized.
|
|
|
- self.compose_candidates_saved = 1
|
|
|
-
|
|
|
- async def _planner(self) -> dict[str, Any]:
|
|
|
- ledger = await self.store.load(ROOT)
|
|
|
- tasks = [task for task in ledger.tasks.values() if task.task_id != ledger.root_task_id]
|
|
|
- direction = next((task for task in tasks if _task_kind(task) == "direction"), None)
|
|
|
- if (
|
|
|
- direction is None
|
|
|
- or direction.status is not TaskStatus.COMPLETED
|
|
|
- or not self.phase_two_active
|
|
|
- ):
|
|
|
- return await super()._planner()
|
|
|
-
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- assert self.direction_goal_id is not None
|
|
|
- goal_ids = (self.direction_goal_id,)
|
|
|
- direction_decision, direction_ref = _accepted_ref(ledger, direction)
|
|
|
- direction_pin = _decision_ref(
|
|
|
- direction_decision,
|
|
|
- direction_ref,
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- task_kind="direction",
|
|
|
- )
|
|
|
-
|
|
|
- def by_objective(value: str) -> Any | None:
|
|
|
- return next((task for task in tasks if task.current_spec.objective == value), None)
|
|
|
-
|
|
|
- portfolio = next(
|
|
|
- (task for task in tasks if _task_kind(task) == "candidate-portfolio"), None
|
|
|
- )
|
|
|
- if portfolio is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "candidate-portfolio",
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- write_scope=("script-build://writes",),
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ]
|
|
|
- },
|
|
|
- )
|
|
|
- compose = next((task for task in tasks if _task_kind(task) == "compose"), None)
|
|
|
- if compose is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": portfolio.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "compose",
|
|
|
- scope_ref=FULL_SCOPE,
|
|
|
- write_scope=("script-build://writes",),
|
|
|
- input_decision_refs=(direction_pin,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- exploratory: dict[str, Any] = {}
|
|
|
- scopes = {
|
|
|
- "structure": "script-build://scopes/full/opening",
|
|
|
- "paragraph": "script-build://scopes/full/opening",
|
|
|
- "element-set": "script-build://scopes/full/opening/elements",
|
|
|
- }
|
|
|
- write_scopes = {
|
|
|
- "structure": ("script-build://writes/paragraphs/opening",),
|
|
|
- "paragraph": ("script-build://writes/paragraphs/opening",),
|
|
|
- "element-set": ("script-build://writes/elements",),
|
|
|
- }
|
|
|
- for kind in self.order:
|
|
|
- objective = f"explore {kind} before formal adoption"
|
|
|
- task = by_objective(objective)
|
|
|
- if task is None:
|
|
|
- inputs: tuple[dict[str, Any], ...] = (direction_pin,)
|
|
|
- base: dict[str, Any] | None = None
|
|
|
- if kind == "paragraph":
|
|
|
- structure_decision, structure_ref = _accepted_ref(
|
|
|
- ledger, exploratory["structure"]
|
|
|
- )
|
|
|
- inputs = (
|
|
|
- direction_pin,
|
|
|
- _decision_ref(
|
|
|
- structure_decision,
|
|
|
- structure_ref,
|
|
|
- scope_ref=scopes["structure"],
|
|
|
- task_kind="structure",
|
|
|
- ),
|
|
|
- )
|
|
|
- base = _artifact_ref_payload(structure_ref)
|
|
|
- elif kind == "element-set":
|
|
|
- structure_decision, structure_ref = _accepted_ref(
|
|
|
- ledger, exploratory["structure"]
|
|
|
- )
|
|
|
- inputs = (
|
|
|
- direction_pin,
|
|
|
- _decision_ref(
|
|
|
- structure_decision,
|
|
|
- structure_ref,
|
|
|
- scope_ref=scopes["structure"],
|
|
|
- task_kind="structure",
|
|
|
- ),
|
|
|
- )
|
|
|
- base = _artifact_ref_payload(structure_ref)
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- kind,
|
|
|
- scope_ref=scopes[kind],
|
|
|
- write_scope=write_scopes[kind],
|
|
|
- objective=objective,
|
|
|
- input_decision_refs=inputs,
|
|
|
- base_artifact_ref=base,
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- exploratory[kind] = task
|
|
|
- if task.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [task.task_id]})
|
|
|
- if task.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(task, ledger)
|
|
|
-
|
|
|
- structure_decision, structure_ref = _accepted_ref(ledger, exploratory["structure"])
|
|
|
- paragraph_decision, paragraph_ref = _accepted_ref(ledger, exploratory["paragraph"])
|
|
|
- element_decision, element_ref = _accepted_ref(ledger, exploratory["element-set"])
|
|
|
-
|
|
|
- paragraph_objective = "adopt structure and paragraph as one logical opening"
|
|
|
- paragraph_replacement = by_objective(paragraph_objective)
|
|
|
- if paragraph_replacement is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "paragraph",
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
- objective=paragraph_objective,
|
|
|
- input_decision_refs=(
|
|
|
- direction_pin,
|
|
|
- _decision_ref(
|
|
|
- structure_decision,
|
|
|
- structure_ref,
|
|
|
- scope_ref=scopes["structure"],
|
|
|
- task_kind="structure",
|
|
|
- ),
|
|
|
- _decision_ref(
|
|
|
- paragraph_decision,
|
|
|
- paragraph_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- ),
|
|
|
- ),
|
|
|
- base_artifact_ref=_artifact_ref_payload(structure_ref),
|
|
|
- supersedes_decision_ids=(paragraph_decision,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if paragraph_replacement.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call(
|
|
|
- "dispatch_script_tasks", {"task_ids": [paragraph_replacement.task_id]}
|
|
|
- )
|
|
|
- if paragraph_replacement.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(paragraph_replacement, ledger)
|
|
|
- adopted_paragraph_decision, adopted_paragraph_ref = _accepted_ref(
|
|
|
- ledger, paragraph_replacement
|
|
|
- )
|
|
|
-
|
|
|
- element_objective = "place the explored element on the adopted opening"
|
|
|
- element_replacement = by_objective(element_objective)
|
|
|
- if element_replacement is None:
|
|
|
- return self._call(
|
|
|
- "plan_script_tasks",
|
|
|
- {
|
|
|
- "parent_task_id": compose.task_id,
|
|
|
- "contracts": [
|
|
|
- _contract(
|
|
|
- "element-set",
|
|
|
- scope_ref=scopes["element-set"],
|
|
|
- write_scope=("script-build://writes/elements",),
|
|
|
- objective=element_objective,
|
|
|
- input_decision_refs=(
|
|
|
- direction_pin,
|
|
|
- _decision_ref(
|
|
|
- element_decision,
|
|
|
- element_ref,
|
|
|
- scope_ref=scopes["element-set"],
|
|
|
- task_kind="element-set",
|
|
|
- ),
|
|
|
- _decision_ref(
|
|
|
- adopted_paragraph_decision,
|
|
|
- adopted_paragraph_ref,
|
|
|
- scope_ref="script-build://scopes/full/opening",
|
|
|
- task_kind="paragraph",
|
|
|
- ),
|
|
|
- ),
|
|
|
- base_artifact_ref=_artifact_ref_payload(adopted_paragraph_ref),
|
|
|
- supersedes_decision_ids=(element_decision,),
|
|
|
- goal_ids=goal_ids,
|
|
|
- )
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if element_replacement.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [element_replacement.task_id]})
|
|
|
- if element_replacement.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(element_replacement, ledger)
|
|
|
- adopted_element_decision, adopted_element_ref = _accepted_ref(ledger, element_replacement)
|
|
|
-
|
|
|
- del adopted_paragraph_ref, adopted_element_ref
|
|
|
- if compose.current_spec_version == 1:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": compose.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "formal adoption follows immutable lineage, not exploration order",
|
|
|
- "selected_decision_ids": [
|
|
|
- structure_decision,
|
|
|
- adopted_paragraph_decision,
|
|
|
- adopted_element_decision,
|
|
|
- ],
|
|
|
- },
|
|
|
- )
|
|
|
- if compose.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [compose.task_id]})
|
|
|
- if compose.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(compose, ledger)
|
|
|
-
|
|
|
- compose_decision, _compose_ref = _accepted_ref(ledger, compose)
|
|
|
- if portfolio.current_spec_version == 1:
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": portfolio.task_id,
|
|
|
- "action": "revise",
|
|
|
- "reason": "pin the unique accepted StructuredScript",
|
|
|
- "selected_decision_ids": [compose_decision],
|
|
|
- },
|
|
|
- )
|
|
|
- if portfolio.status in {TaskStatus.PENDING, TaskStatus.NEEDS_REPLAN}:
|
|
|
- return self._call("dispatch_script_tasks", {"task_ids": [portfolio.task_id]})
|
|
|
- if portfolio.status is TaskStatus.AWAITING_DECISION:
|
|
|
- return self._accept(portfolio, ledger)
|
|
|
- return self._call(
|
|
|
- "decide_script_task",
|
|
|
- {
|
|
|
- "task_id": root.task_id,
|
|
|
- "action": "block",
|
|
|
- "reason": PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-class _PrincipalProvider:
|
|
|
- async def current(self, request_context: Any = None) -> Principal:
|
|
|
- del request_context
|
|
|
- return Principal("phase-two-owner")
|
|
|
-
|
|
|
-
|
|
|
-class _Authorizer:
|
|
|
- async def require_source_access(self, _principal: Principal, **_source: Any) -> None:
|
|
|
- return None
|
|
|
-
|
|
|
- async def require_access(self, _principal: Principal, _script_build_id: int) -> None:
|
|
|
- return None
|
|
|
-
|
|
|
-
|
|
|
-class _DecodeAdapter:
|
|
|
- async def retrieve(self, *, query: Mapping[str, Any], snapshot: Any) -> RetrievalResult:
|
|
|
- del snapshot
|
|
|
- return RetrievalResult(
|
|
|
- source_refs=("decode-index://fixture/opening-reversal",),
|
|
|
- summary=f"A grounded opening reverses one expectation: {query['keyword']}",
|
|
|
- supports=("direction",),
|
|
|
- confidence="high",
|
|
|
- metadata={"rank": 1, "score": 0.93},
|
|
|
- )
|
|
|
-
|
|
|
- async def require_access(self, _principal: Principal, _script_build_id: int) -> None:
|
|
|
- return None
|
|
|
-
|
|
|
-
|
|
|
-class _PromptCatalog:
|
|
|
- def __init__(self) -> None:
|
|
|
- self._entries = {
|
|
|
- (str(item["preset"]), str(item["role"])): dict(item)
|
|
|
- for item in (*script_build_prompt_manifest(), *phase_three_prompt_manifest())
|
|
|
- }
|
|
|
-
|
|
|
- async def load(self, requests: Sequence[Any]) -> tuple[dict[str, Any], ...]:
|
|
|
- return tuple(dict(self._entries[(str(item.preset), str(item.role))]) for item in requests)
|
|
|
-
|
|
|
-
|
|
|
-class _BlockedAfterPolicyLLM(_PhaseTwoScriptedLLM):
|
|
|
- """Hold the first Phase2 model call after policy persistence and UNBLOCK."""
|
|
|
-
|
|
|
- def __init__(self, store: FileSystemTaskStore) -> None:
|
|
|
- super().__init__(store)
|
|
|
- self.phase_two_model_call = asyncio.Event()
|
|
|
- self._never_release = asyncio.Event()
|
|
|
-
|
|
|
- async def __call__(self, messages, tools, **kwargs):
|
|
|
- del kwargs
|
|
|
- if any("script-build-phase-two/v1" in str(item.get("content", "")) for item in messages):
|
|
|
- self.phase_two_model_call.set()
|
|
|
- await self._never_release.wait()
|
|
|
- return await super().__call__(messages, tools)
|
|
|
-
|
|
|
-
|
|
|
-def _persisted_host(
|
|
|
- *,
|
|
|
- sessions: Any,
|
|
|
- tmp_path: Path,
|
|
|
- llm: Any,
|
|
|
- enabled_phase: int,
|
|
|
- model_manifest: Mapping[str, object],
|
|
|
-) -> Any:
|
|
|
- snapshots = SqlAlchemyInputSnapshotRepository(sessions)
|
|
|
- input_service = ScriptInputSnapshotService(
|
|
|
- legacy_input=cast(Any, None),
|
|
|
- persona_source=cast(Any, None),
|
|
|
- strategy_source=cast(Any, None),
|
|
|
- prompt_source=_PromptCatalog(),
|
|
|
- snapshots=snapshots,
|
|
|
- )
|
|
|
- artifacts = SqlAlchemyScriptBusinessArtifactRepository(sessions)
|
|
|
- task_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- runner = AgentRunner(
|
|
|
- trace_store=FileSystemTraceStore(str(tmp_path / "trace")),
|
|
|
- llm_call=llm,
|
|
|
- )
|
|
|
- return compose_host(
|
|
|
- HostDependencies(
|
|
|
- runner=runner,
|
|
|
- task_store=task_store,
|
|
|
- framework_artifact_store=FileSystemArtifactStore(str(tmp_path / "framework-artifacts")),
|
|
|
- input_snapshot_service=input_service,
|
|
|
- input_snapshots=snapshots,
|
|
|
- bindings=SqlAlchemyMissionBindingRepository(sessions),
|
|
|
- business_artifacts=artifacts,
|
|
|
- publications=SqlAlchemyPublicationRepository(sessions),
|
|
|
- legacy_state=SqlAlchemyLegacyBuildStateRepository(sessions),
|
|
|
- principal_provider=_PrincipalProvider(),
|
|
|
- build_authorizer=_Authorizer(),
|
|
|
- retrieval_adapters={"decode": _DecodeAdapter()},
|
|
|
- enabled_phase=enabled_phase,
|
|
|
- agent_data_root=tmp_path / "agent-data",
|
|
|
- candidate_workspaces=SqlAlchemyCandidateWorkspaceRepository(sessions, artifacts),
|
|
|
- default_model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-@pytest.mark.parametrize(
|
|
|
- "exploration_order",
|
|
|
- [
|
|
|
- pytest.param(("structure", "element-set", "paragraph"), id="element-second"),
|
|
|
- pytest.param(("structure", "paragraph", "element-set"), id="paragraph-second"),
|
|
|
- ],
|
|
|
-)
|
|
|
-async def test_real_runner_creative_exploration_order_does_not_choose_adoption_order(
|
|
|
- database: Any,
|
|
|
- tmp_path: Path,
|
|
|
- exploration_order: tuple[str, str, str],
|
|
|
-) -> None:
|
|
|
- _, sessions = database
|
|
|
- build_states = SqlAlchemyLegacyBuildStateRepository(sessions)
|
|
|
- script_build_id = await build_states.create(
|
|
|
- execution_id=711,
|
|
|
- topic_build_id=712,
|
|
|
- topic_id=713,
|
|
|
- agent_type="script_planner",
|
|
|
- agent_config={},
|
|
|
- data_source_url=None,
|
|
|
- strategies_config={},
|
|
|
- )
|
|
|
- prompt_manifest = script_build_prompt_manifest()
|
|
|
- model_manifest = {
|
|
|
- "presets": {
|
|
|
- str(item["preset"]): {
|
|
|
- "model": "scripted-fake-model",
|
|
|
- "temperature": 0,
|
|
|
- "max_iterations": 80,
|
|
|
- }
|
|
|
- for item in prompt_manifest
|
|
|
- }
|
|
|
- }
|
|
|
- snapshot_repository = SqlAlchemyInputSnapshotRepository(sessions)
|
|
|
- snapshot = await snapshot_repository.freeze(
|
|
|
- ScriptBuildInput(
|
|
|
- script_build_id=script_build_id,
|
|
|
- execution_id=711,
|
|
|
- topic_build_id=712,
|
|
|
- topic_id=713,
|
|
|
- topic={"topic": {"id": 713, "result": "Order must not determine adoption"}},
|
|
|
- account={"account_name": "fixture-account"},
|
|
|
- prompt_manifest=tuple(dict(item) for item in prompt_manifest),
|
|
|
- model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
- bindings = SqlAlchemyMissionBindingRepository(sessions)
|
|
|
- await bindings.create(
|
|
|
- script_build_id=script_build_id,
|
|
|
- root_trace_id=ROOT,
|
|
|
- input_snapshot_id=int(snapshot.snapshot_id),
|
|
|
- engine_version="phase-two-ordered-real-runner-test",
|
|
|
- schema_version="v2",
|
|
|
- )
|
|
|
- task_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- trace_store = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- llm = _OrderedExplorationScriptedLLM(task_store, exploration_order)
|
|
|
- runner = AgentRunner(trace_store=trace_store, llm_call=llm)
|
|
|
- business_artifacts = SqlAlchemyScriptBusinessArtifactRepository(sessions)
|
|
|
- composition = compose_host(
|
|
|
- HostDependencies(
|
|
|
- runner=runner,
|
|
|
- task_store=task_store,
|
|
|
- framework_artifact_store=FileSystemArtifactStore(str(tmp_path / "framework-artifacts")),
|
|
|
- input_snapshot_service=cast(Any, snapshot_repository),
|
|
|
- input_snapshots=snapshot_repository,
|
|
|
- bindings=bindings,
|
|
|
- business_artifacts=business_artifacts,
|
|
|
- publications=SqlAlchemyPublicationRepository(sessions),
|
|
|
- legacy_state=build_states,
|
|
|
- principal_provider=_PrincipalProvider(),
|
|
|
- build_authorizer=_Authorizer(),
|
|
|
- retrieval_adapters={"decode": _DecodeAdapter()},
|
|
|
- enabled_phase=2,
|
|
|
- agent_data_root=tmp_path / "agent-data",
|
|
|
- candidate_workspaces=SqlAlchemyCandidateWorkspaceRepository(
|
|
|
- sessions, business_artifacts
|
|
|
- ),
|
|
|
- default_model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
-
|
|
|
- await build_states.set_status(script_build_id, BuildStatus.RUNNING)
|
|
|
- await composition.mission_service.run(script_build_id)
|
|
|
-
|
|
|
- ledger = await task_store.load(ROOT)
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- assert root.status is TaskStatus.BLOCKED
|
|
|
- assert root.blocked_reason == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
- assert await build_states.get_status(script_build_id) is BuildStatus.PARTIAL
|
|
|
- assert tuple(llm.worker_kinds[2:5]) == exploration_order
|
|
|
- assert tuple(llm.validator_kinds[2:5]) == exploration_order
|
|
|
-
|
|
|
- task_by_objective = {
|
|
|
- task.current_spec.objective: task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id
|
|
|
- }
|
|
|
- expected_presets = {
|
|
|
- "structure": "script_structure_worker",
|
|
|
- "paragraph": "script_paragraph_worker",
|
|
|
- "element-set": "script_element_set_worker",
|
|
|
- }
|
|
|
- reopened_traces = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- for kind in exploration_order:
|
|
|
- task = task_by_objective[f"explore {kind} before formal adoption"]
|
|
|
- attempt = ledger.attempts[task.attempt_ids[-1]]
|
|
|
- assert attempt.worker_preset == expected_presets[kind]
|
|
|
- worker_trace = await reopened_traces.get_trace(attempt.worker_trace_id)
|
|
|
- assert worker_trace is not None and worker_trace.status == "completed"
|
|
|
- messages = await reopened_traces.get_trace_messages(attempt.worker_trace_id)
|
|
|
- if worker_trace.context.get("deterministic_worker"):
|
|
|
- assert messages == []
|
|
|
- else:
|
|
|
- assert messages
|
|
|
- validation = next(
|
|
|
- ledger.validations[item]
|
|
|
- for item in task.validation_ids
|
|
|
- if ledger.validations[item].attempt_id == attempt.attempt_id
|
|
|
- )
|
|
|
- assert validation.verdict is ValidationVerdict.PASSED
|
|
|
- validator_trace = await reopened_traces.get_trace(validation.validator_trace_id)
|
|
|
- assert validator_trace is not None and validator_trace.status == "completed"
|
|
|
- assert await reopened_traces.get_trace_messages(validation.validator_trace_id)
|
|
|
-
|
|
|
- element_replacement = task_by_objective["place the explored element on the adopted opening"]
|
|
|
- element_decision, element_ref = _accepted_ref(ledger, element_replacement)
|
|
|
- element_version = await business_artifacts.read_by_ref(
|
|
|
- element_ref,
|
|
|
- script_build_id=script_build_id,
|
|
|
- task_id=element_replacement.task_id,
|
|
|
- attempt_id=cast(str, ledger.decisions[element_decision].attempt_id),
|
|
|
- )
|
|
|
- assert element_version.artifact_type is ArtifactKind.ELEMENT_SET
|
|
|
- assert len(cast(Any, element_version.artifact).paragraph_element_links) == 1
|
|
|
-
|
|
|
- compose = next(
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "compose"
|
|
|
- )
|
|
|
- assert compose.status is TaskStatus.COMPLETED
|
|
|
- contract_ref = next(
|
|
|
- item
|
|
|
- for item in compose.current_spec.context_refs
|
|
|
- if item.startswith("script-build://task-contracts/sha256/")
|
|
|
- )
|
|
|
- compose_contract = await FileScriptTaskContractStore(tmp_path / "agent-data").read(
|
|
|
- ROOT, contract_ref
|
|
|
- )
|
|
|
- adopted_kinds = tuple(
|
|
|
- _task_kind(ledger.tasks[ledger.decisions[decision_id].task_id])
|
|
|
- for decision_id in compose_contract.contract.compose_order
|
|
|
- )
|
|
|
- assert adopted_kinds == ("structure", "paragraph", "element-set")
|
|
|
- portfolio = next(
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "candidate-portfolio"
|
|
|
- )
|
|
|
- assert portfolio.status is TaskStatus.COMPLETED
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-async def test_mission_service_real_runner_auto_continues_phase_one_to_phase_two_boundary(
|
|
|
- database: Any, tmp_path: Path
|
|
|
-) -> None:
|
|
|
- _, sessions = database
|
|
|
- build_states = SqlAlchemyLegacyBuildStateRepository(sessions)
|
|
|
- script_build_id = await build_states.create(
|
|
|
- execution_id=701,
|
|
|
- topic_build_id=702,
|
|
|
- topic_id=703,
|
|
|
- agent_type="script_planner",
|
|
|
- agent_config={},
|
|
|
- data_source_url=None,
|
|
|
- strategies_config={},
|
|
|
- )
|
|
|
- prompt_manifest = script_build_prompt_manifest()
|
|
|
- model_manifest = {
|
|
|
- "presets": {
|
|
|
- str(item["preset"]): {
|
|
|
- "model": "scripted-fake-model",
|
|
|
- "temperature": 0,
|
|
|
- "max_iterations": 80,
|
|
|
- }
|
|
|
- for item in prompt_manifest
|
|
|
- }
|
|
|
- }
|
|
|
- snapshot_repository = SqlAlchemyInputSnapshotRepository(sessions)
|
|
|
- snapshot = await snapshot_repository.freeze(
|
|
|
- ScriptBuildInput(
|
|
|
- script_build_id=script_build_id,
|
|
|
- execution_id=701,
|
|
|
- topic_build_id=702,
|
|
|
- topic_id=703,
|
|
|
- topic={"topic": {"id": 703, "result": "A concrete reversal changes a belief"}},
|
|
|
- account={"account_name": "fixture-account"},
|
|
|
- prompt_manifest=tuple(dict(item) for item in prompt_manifest),
|
|
|
- model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
- bindings = SqlAlchemyMissionBindingRepository(sessions)
|
|
|
- await bindings.create(
|
|
|
- script_build_id=script_build_id,
|
|
|
- root_trace_id=ROOT,
|
|
|
- input_snapshot_id=int(snapshot.snapshot_id),
|
|
|
- engine_version="phase-two-real-runner-test",
|
|
|
- schema_version="v2",
|
|
|
- )
|
|
|
- task_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- trace_store = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- llm = _PhaseTwoScriptedLLM(task_store)
|
|
|
- runner = AgentRunner(trace_store=trace_store, llm_call=llm)
|
|
|
- framework_artifacts = FileSystemArtifactStore(str(tmp_path / "framework-artifacts"))
|
|
|
- business_artifacts = SqlAlchemyScriptBusinessArtifactRepository(sessions)
|
|
|
- workspaces = SqlAlchemyCandidateWorkspaceRepository(sessions, business_artifacts)
|
|
|
- composition = compose_host(
|
|
|
- HostDependencies(
|
|
|
- runner=runner,
|
|
|
- task_store=task_store,
|
|
|
- framework_artifact_store=framework_artifacts,
|
|
|
- input_snapshot_service=cast(Any, snapshot_repository),
|
|
|
- input_snapshots=snapshot_repository,
|
|
|
- bindings=bindings,
|
|
|
- business_artifacts=business_artifacts,
|
|
|
- publications=SqlAlchemyPublicationRepository(sessions),
|
|
|
- legacy_state=build_states,
|
|
|
- principal_provider=_PrincipalProvider(),
|
|
|
- build_authorizer=_Authorizer(),
|
|
|
- retrieval_adapters={"decode": _DecodeAdapter()},
|
|
|
- enabled_phase=2,
|
|
|
- agent_data_root=tmp_path / "agent-data",
|
|
|
- candidate_workspaces=workspaces,
|
|
|
- default_model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
-
|
|
|
- await build_states.set_status(script_build_id, BuildStatus.RUNNING)
|
|
|
- await composition.mission_service.run(script_build_id)
|
|
|
-
|
|
|
- ledger = await task_store.load(ROOT)
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- assert root.status is TaskStatus.BLOCKED
|
|
|
- assert root.blocked_reason == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
- phase_two_blocks = [
|
|
|
- item
|
|
|
- for item in ledger.decisions.values()
|
|
|
- if item.task_id == root.task_id
|
|
|
- and item.action is DecisionAction.BLOCK
|
|
|
- and item.reason == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
- ]
|
|
|
- phase_one_blocks = [
|
|
|
- item
|
|
|
- for item in ledger.decisions.values()
|
|
|
- if item.task_id == root.task_id
|
|
|
- and item.action is DecisionAction.BLOCK
|
|
|
- and item.reason == PHASE_ONE_CAPABILITY_BOUNDARY
|
|
|
- ]
|
|
|
- assert len(phase_one_blocks) == len(phase_two_blocks) == 1
|
|
|
- assert await build_states.get_status(script_build_id) is BuildStatus.PARTIAL
|
|
|
- assert llm.worker_kinds == [
|
|
|
- "decode-retrieval",
|
|
|
- "direction",
|
|
|
- "structure",
|
|
|
- "paragraph",
|
|
|
- "element-set",
|
|
|
- ]
|
|
|
- assert llm.validator_kinds == [
|
|
|
- "decode-retrieval",
|
|
|
- "direction",
|
|
|
- "structure",
|
|
|
- "paragraph",
|
|
|
- "element-set",
|
|
|
- "compose",
|
|
|
- "candidate-portfolio",
|
|
|
- ]
|
|
|
- assert llm.submit_attempt_arguments
|
|
|
- assert all(not item for item in llm.submit_attempt_arguments)
|
|
|
- assert llm.compose_candidates_saved == 0
|
|
|
- assert llm.tool_calls_by_name.get("create_script_paragraph", 0) == 0
|
|
|
-
|
|
|
- compose_task = next(
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "compose"
|
|
|
- )
|
|
|
- assert compose_task.current_spec_version == 2
|
|
|
- assert len(compose_task.attempt_ids) == 1
|
|
|
- assert [ledger.validations[item].verdict for item in compose_task.validation_ids] == [
|
|
|
- ValidationVerdict.PASSED,
|
|
|
- ]
|
|
|
- assert [ledger.decisions[item].action for item in compose_task.decision_ids] == [
|
|
|
- DecisionAction.REVISE,
|
|
|
- DecisionAction.ACCEPT,
|
|
|
- ]
|
|
|
-
|
|
|
- paragraph_tasks = [
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "paragraph"
|
|
|
- ]
|
|
|
- assert len(paragraph_tasks) == 1
|
|
|
- assert (
|
|
|
- len(
|
|
|
- [
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "compare"
|
|
|
- ]
|
|
|
- )
|
|
|
- == 0
|
|
|
- )
|
|
|
-
|
|
|
- expected_identities = {
|
|
|
- str(item["preset"]): str(item["content_sha256"]) for item in prompt_manifest
|
|
|
- }
|
|
|
- for attempt in ledger.attempts.values():
|
|
|
- worker_trace = await trace_store.get_trace(attempt.worker_trace_id)
|
|
|
- assert worker_trace is not None
|
|
|
- if worker_trace.context.get("deterministic_worker"):
|
|
|
- assert worker_trace.total_tokens == 0
|
|
|
- assert attempt.worker_preset in {
|
|
|
- "script_compose_worker",
|
|
|
- "script_candidate_portfolio_worker",
|
|
|
- }
|
|
|
- continue
|
|
|
- assert (
|
|
|
- worker_trace.context["role_prompt_identity"]
|
|
|
- == expected_identities[attempt.worker_preset]
|
|
|
- )
|
|
|
- validations = [
|
|
|
- ledger.validations[item]
|
|
|
- for item in ledger.tasks[attempt.task_id].validation_ids
|
|
|
- if ledger.validations[item].attempt_id == attempt.attempt_id
|
|
|
- ]
|
|
|
- assert len(validations) == 1
|
|
|
- validator_trace = await trace_store.get_trace(validations[0].validator_trace_id)
|
|
|
- if validations[0].summary.startswith("Deterministic preflight rejected"):
|
|
|
- assert validator_trace is None
|
|
|
- continue
|
|
|
- assert validator_trace is not None
|
|
|
- assert (
|
|
|
- validator_trace.context["role_prompt_identity"]
|
|
|
- == expected_identities[validations[0].validator_preset]
|
|
|
- )
|
|
|
-
|
|
|
- reopened_traces = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- planner_trace = await reopened_traces.get_trace(ROOT)
|
|
|
- assert planner_trace is not None
|
|
|
- assert planner_trace.status == "incomplete"
|
|
|
- assert planner_trace.error_message == "mission_blocked"
|
|
|
- planner_messages = await reopened_traces.get_trace_messages(ROOT)
|
|
|
- phase_two_policies = [
|
|
|
- item
|
|
|
- for item in planner_messages
|
|
|
- if item.role == "system" and "script-build-phase-two/v1" in str(item.content)
|
|
|
- ]
|
|
|
- phase_two_continuations = [
|
|
|
- item
|
|
|
- for item in planner_messages
|
|
|
- if item.role == "user"
|
|
|
- and '"mission": "continue_toward_root"' in str(item.content)
|
|
|
- and '"phase": 2' in str(item.content)
|
|
|
- ]
|
|
|
- assert len(phase_two_policies) == len(phase_two_continuations) == 1
|
|
|
-
|
|
|
- portfolio_task = next(
|
|
|
- task
|
|
|
- for task in ledger.tasks.values()
|
|
|
- if task.task_id != ledger.root_task_id and _task_kind(task) == "candidate-portfolio"
|
|
|
- )
|
|
|
-
|
|
|
- def belongs_to_phase_two(task: Any) -> bool:
|
|
|
- current = task
|
|
|
- while current.parent_task_id is not None:
|
|
|
- if current.task_id == portfolio_task.task_id:
|
|
|
- return True
|
|
|
- current = ledger.tasks[current.parent_task_id]
|
|
|
- return False
|
|
|
-
|
|
|
- phase_two_attempts = [
|
|
|
- attempt
|
|
|
- for attempt in ledger.attempts.values()
|
|
|
- if belongs_to_phase_two(ledger.tasks[attempt.task_id])
|
|
|
- ]
|
|
|
- assert phase_two_attempts
|
|
|
- for attempt in phase_two_attempts:
|
|
|
- worker_trace = await reopened_traces.get_trace(attempt.worker_trace_id)
|
|
|
- assert worker_trace is not None and worker_trace.status == "completed"
|
|
|
- messages = await reopened_traces.get_trace_messages(attempt.worker_trace_id)
|
|
|
- if worker_trace.context.get("deterministic_worker"):
|
|
|
- assert messages == []
|
|
|
- else:
|
|
|
- assert messages
|
|
|
- validation = next(
|
|
|
- ledger.validations[item]
|
|
|
- for item in ledger.tasks[attempt.task_id].validation_ids
|
|
|
- if ledger.validations[item].attempt_id == attempt.attempt_id
|
|
|
- )
|
|
|
- validator_trace = await reopened_traces.get_trace(validation.validator_trace_id)
|
|
|
- if validation.summary.startswith("Deterministic preflight rejected"):
|
|
|
- assert validator_trace is None
|
|
|
- continue
|
|
|
- assert validator_trace is not None and validator_trace.status == "completed"
|
|
|
- assert await reopened_traces.get_trace_messages(validation.validator_trace_id)
|
|
|
-
|
|
|
- async with sessions() as session:
|
|
|
- rows = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(artifact_version_table).where(
|
|
|
- artifact_version_table.c.script_build_id == script_build_id
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .all()
|
|
|
- )
|
|
|
- branch_ids = list(
|
|
|
- await session.scalars(
|
|
|
- select(script_build_paragraph.c.branch_id).where(
|
|
|
- script_build_paragraph.c.script_build_id == script_build_id
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- build = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(script_build_record).where(script_build_record.c.id == script_build_id)
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .one()
|
|
|
- )
|
|
|
- kinds = {str(row["artifact_type"]) for row in rows}
|
|
|
- assert {
|
|
|
- ArtifactKind.DIRECTION.value,
|
|
|
- ArtifactKind.PARAGRAPH.value,
|
|
|
- ArtifactKind.STRUCTURED_SCRIPT.value,
|
|
|
- ArtifactKind.CANDIDATE_PORTFOLIO.value,
|
|
|
- } <= kinds
|
|
|
- assert branch_ids and min(branch_ids) > 0 and 0 not in branch_ids
|
|
|
- assert BuildStatus(str(build["status"])) is BuildStatus.PARTIAL
|
|
|
- assert build["error_message"] == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
- assert build["reson_trace_id"] == ROOT
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-async def test_historical_partial_advances_via_http_and_reenters_after_filestore_reload(
|
|
|
- database: Any,
|
|
|
- tmp_path: Path,
|
|
|
-) -> None:
|
|
|
- """Exercise SQL v1->v2 CAS and the only pre-recovery crash-safe HTTP window."""
|
|
|
-
|
|
|
- _, sessions = database
|
|
|
- build_states = SqlAlchemyLegacyBuildStateRepository(sessions)
|
|
|
- script_build_id = await build_states.create(
|
|
|
- execution_id=801,
|
|
|
- topic_build_id=802,
|
|
|
- topic_id=803,
|
|
|
- agent_type="script_planner",
|
|
|
- agent_config={},
|
|
|
- data_source_url=None,
|
|
|
- strategies_config={},
|
|
|
- )
|
|
|
- phase_one_prompts = phase_one_prompt_manifest()
|
|
|
- full_prompts = script_build_prompt_manifest()
|
|
|
- phase_one_models = {
|
|
|
- "presets": {
|
|
|
- str(item["preset"]): {
|
|
|
- "model": "scripted-fake-model",
|
|
|
- "temperature": 0,
|
|
|
- "max_iterations": 80,
|
|
|
- }
|
|
|
- for item in phase_one_prompts
|
|
|
- }
|
|
|
- }
|
|
|
- full_models = {
|
|
|
- "presets": {
|
|
|
- str(item["preset"]): {
|
|
|
- "model": "scripted-fake-model",
|
|
|
- "temperature": 0,
|
|
|
- "max_iterations": 80,
|
|
|
- }
|
|
|
- for item in full_prompts
|
|
|
- }
|
|
|
- }
|
|
|
- snapshots = SqlAlchemyInputSnapshotRepository(sessions)
|
|
|
- phase_one_snapshot = await snapshots.freeze(
|
|
|
- ScriptBuildInput(
|
|
|
- script_build_id=script_build_id,
|
|
|
- execution_id=801,
|
|
|
- topic_build_id=802,
|
|
|
- topic_id=803,
|
|
|
- topic={"topic": {"id": 803, "result": "A historical Phase1 checkpoint"}},
|
|
|
- account={"account_name": "historical-fixture"},
|
|
|
- prompt_manifest=tuple(dict(item) for item in phase_one_prompts),
|
|
|
- model_manifest=phase_one_models,
|
|
|
- )
|
|
|
- )
|
|
|
- bindings = SqlAlchemyMissionBindingRepository(sessions)
|
|
|
- await bindings.create(
|
|
|
- script_build_id=script_build_id,
|
|
|
- root_trace_id=ROOT,
|
|
|
- input_snapshot_id=int(phase_one_snapshot.snapshot_id),
|
|
|
- engine_version="phase-two-http-reentry-test",
|
|
|
- schema_version="v1",
|
|
|
- )
|
|
|
-
|
|
|
- phase_one_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- phase_one_host = _persisted_host(
|
|
|
- sessions=sessions,
|
|
|
- tmp_path=tmp_path,
|
|
|
- llm=_PhaseTwoScriptedLLM(phase_one_store),
|
|
|
- enabled_phase=1,
|
|
|
- model_manifest=full_models,
|
|
|
- )
|
|
|
- await build_states.set_status(script_build_id, BuildStatus.RUNNING)
|
|
|
- await phase_one_host.mission_service.run(script_build_id)
|
|
|
-
|
|
|
- phase_one_ledger = await phase_one_store.load(ROOT)
|
|
|
- phase_one_root = phase_one_ledger.tasks[phase_one_ledger.root_task_id]
|
|
|
- assert phase_one_root.status is TaskStatus.BLOCKED
|
|
|
- assert phase_one_root.blocked_reason == PHASE_ONE_CAPABILITY_BOUNDARY
|
|
|
- assert await build_states.get_status(script_build_id) is BuildStatus.PARTIAL
|
|
|
- phase_one_traces = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- assert await phase_one_traces.get_goal_tree(ROOT) is None
|
|
|
-
|
|
|
- # Reopen every file-backed component to represent the first Phase2 process.
|
|
|
- transition_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- blocked_llm = _BlockedAfterPolicyLLM(transition_store)
|
|
|
- transition_host = _persisted_host(
|
|
|
- sessions=sessions,
|
|
|
- tmp_path=tmp_path,
|
|
|
- llm=blocked_llm,
|
|
|
- enabled_phase=2,
|
|
|
- model_manifest=full_models,
|
|
|
- )
|
|
|
- async with httpx.AsyncClient(
|
|
|
- transport=httpx.ASGITransport(app=transition_host.app),
|
|
|
- base_url="http://test",
|
|
|
- ) as client:
|
|
|
- response = await client.post(
|
|
|
- f"/api/pattern/script_builds/{script_build_id}/phase-two/advance"
|
|
|
- )
|
|
|
- assert response.status_code == 200, response.text
|
|
|
- first_advance = response.json()
|
|
|
- assert first_advance["root_trace_id"] == ROOT
|
|
|
- assert first_advance["input_snapshot_id"] != phase_one_snapshot.snapshot_id
|
|
|
- phase_two_snapshot_id = str(first_advance["input_snapshot_id"])
|
|
|
-
|
|
|
- await asyncio.wait_for(blocked_llm.phase_two_model_call.wait(), timeout=10)
|
|
|
- transition_ledger = await transition_store.load(ROOT)
|
|
|
- assert transition_ledger.tasks[transition_ledger.root_task_id].status is TaskStatus.NEEDS_REPLAN
|
|
|
- assert await build_states.get_status(script_build_id) is BuildStatus.RUNNING
|
|
|
- crashed_task = transition_host.mission_service._runs[script_build_id]
|
|
|
- crashed_task.cancel()
|
|
|
- with pytest.raises(asyncio.CancelledError):
|
|
|
- await crashed_task
|
|
|
-
|
|
|
- reopened_after_crash = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- persisted_messages = await reopened_after_crash.get_trace_messages(ROOT)
|
|
|
- persisted_events = await reopened_after_crash.get_events(ROOT, 0)
|
|
|
- assert _phase_two_policy_count(persisted_messages) == 1
|
|
|
- assert _phase_two_continuation_count(persisted_messages) == 1
|
|
|
- assert _policy_migration_count(persisted_events) == 1
|
|
|
-
|
|
|
- # A fresh Host must take the NEEDS_REPLAN reentry path without rewriting policy.
|
|
|
- retry_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- retry_host = _persisted_host(
|
|
|
- sessions=sessions,
|
|
|
- tmp_path=tmp_path,
|
|
|
- llm=_PhaseTwoScriptedLLM(retry_store),
|
|
|
- enabled_phase=2,
|
|
|
- model_manifest=full_models,
|
|
|
- )
|
|
|
- async with httpx.AsyncClient(
|
|
|
- transport=httpx.ASGITransport(app=retry_host.app),
|
|
|
- base_url="http://test",
|
|
|
- ) as client:
|
|
|
- retry_response = await client.post(
|
|
|
- f"/api/pattern/script_builds/{script_build_id}/phase-two/advance"
|
|
|
- )
|
|
|
- assert retry_response.status_code == 200, retry_response.text
|
|
|
- assert retry_response.json()["root_trace_id"] == ROOT
|
|
|
- assert retry_response.json()["input_snapshot_id"] == phase_two_snapshot_id
|
|
|
- retry_task = retry_host.mission_service._runs[script_build_id]
|
|
|
- await asyncio.wait_for(asyncio.shield(retry_task), timeout=60)
|
|
|
-
|
|
|
- final_binding = await bindings.get_by_build(script_build_id)
|
|
|
- assert final_binding.root_trace_id == ROOT
|
|
|
- assert str(final_binding.input_snapshot_id) == phase_two_snapshot_id
|
|
|
- phase_two_snapshot = await snapshots.get(
|
|
|
- phase_two_snapshot_id,
|
|
|
- script_build_id=script_build_id,
|
|
|
- )
|
|
|
- assert phase_two_snapshot.parent_snapshot_id == phase_one_snapshot.snapshot_id
|
|
|
- assert phase_two_snapshot.parent_snapshot_sha256 == phase_one_snapshot.canonical_sha256
|
|
|
- assert (
|
|
|
- phase_two_snapshot.business_input_sha256
|
|
|
- == canonical_sha256(phase_one_snapshot.to_input().business_payload()).wire
|
|
|
- )
|
|
|
- assert {str(item["preset"]) for item in phase_two_snapshot.prompt_manifest} == {
|
|
|
- str(item["preset"]) for item in full_prompts
|
|
|
- }
|
|
|
-
|
|
|
- async with sessions() as session:
|
|
|
- snapshot_rows = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(input_snapshot_table)
|
|
|
- .where(input_snapshot_table.c.script_build_id == script_build_id)
|
|
|
- .order_by(input_snapshot_table.c.version)
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .all()
|
|
|
- )
|
|
|
- binding_row = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(mission_binding_table).where(
|
|
|
- mission_binding_table.c.script_build_id == script_build_id
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .one()
|
|
|
- )
|
|
|
- assert [int(item["version"]) for item in snapshot_rows] == [1, 2]
|
|
|
- assert int(binding_row["input_snapshot_id"]) == int(phase_two_snapshot_id)
|
|
|
- assert str(binding_row["root_trace_id"]) == ROOT
|
|
|
-
|
|
|
- final_ledger = await retry_store.load(ROOT)
|
|
|
- final_root = final_ledger.tasks[final_ledger.root_task_id]
|
|
|
- assert final_root.status is TaskStatus.BLOCKED
|
|
|
- assert final_root.blocked_reason == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
- assert await build_states.get_status(script_build_id) is BuildStatus.PARTIAL
|
|
|
- direction = next(
|
|
|
- item
|
|
|
- for item in final_ledger.tasks.values()
|
|
|
- if item.task_id != final_ledger.root_task_id and _task_kind(item) == "direction"
|
|
|
- )
|
|
|
- direction_attempt = final_ledger.attempts[direction.attempt_ids[-1]]
|
|
|
- direction_spec = next(
|
|
|
- item for item in direction.specs if item.version == direction_attempt.spec_version
|
|
|
- )
|
|
|
- assert f"script-build://inputs/{phase_one_snapshot.snapshot_id}" in direction_spec.context_refs
|
|
|
-
|
|
|
- final_traces = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- final_messages = await final_traces.get_trace_messages(ROOT)
|
|
|
- final_events = await final_traces.get_events(ROOT, 0)
|
|
|
- assert _phase_two_policy_count(final_messages) == 1
|
|
|
- assert _phase_two_continuation_count(final_messages) == 1
|
|
|
- assert _policy_migration_count(final_events) == 1
|
|
|
-
|
|
|
-
|
|
|
-@pytest.mark.asyncio
|
|
|
-async def test_real_runner_phase_one_to_three_final_publication_and_legacy_readback(
|
|
|
- database: Any, tmp_path: Path
|
|
|
-) -> None:
|
|
|
- _, sessions = database
|
|
|
- states = SqlAlchemyLegacyBuildStateRepository(sessions)
|
|
|
- script_build_id = await states.create(
|
|
|
- execution_id=801,
|
|
|
- topic_build_id=802,
|
|
|
- topic_id=803,
|
|
|
- agent_type="script_planner",
|
|
|
- agent_config={},
|
|
|
- data_source_url=None,
|
|
|
- strategies_config={},
|
|
|
- )
|
|
|
- phase_two_prompts = script_build_prompt_manifest()
|
|
|
- all_prompts = (*phase_two_prompts, *phase_three_prompt_manifest())
|
|
|
- model_manifest = {
|
|
|
- "presets": {
|
|
|
- str(item["preset"]): {
|
|
|
- "model": "scripted-fake-model",
|
|
|
- "temperature": 0,
|
|
|
- "max_iterations": 100,
|
|
|
- }
|
|
|
- for item in all_prompts
|
|
|
- }
|
|
|
- }
|
|
|
- snapshots = SqlAlchemyInputSnapshotRepository(sessions)
|
|
|
- snapshot = await snapshots.freeze(
|
|
|
- ScriptBuildInput(
|
|
|
- script_build_id=script_build_id,
|
|
|
- execution_id=801,
|
|
|
- topic_build_id=802,
|
|
|
- topic_id=803,
|
|
|
- topic={"topic": {"id": 803, "result": "A grounded reversal"}},
|
|
|
- account={"account_name": "fixture-account"},
|
|
|
- prompt_manifest=tuple(dict(item) for item in phase_two_prompts),
|
|
|
- model_manifest=model_manifest,
|
|
|
- )
|
|
|
- )
|
|
|
- bindings = SqlAlchemyMissionBindingRepository(sessions)
|
|
|
- await bindings.create(
|
|
|
- script_build_id=script_build_id,
|
|
|
- root_trace_id=ROOT,
|
|
|
- input_snapshot_id=int(snapshot.snapshot_id),
|
|
|
- engine_version="phase-three-real-runner-test",
|
|
|
- schema_version="v3",
|
|
|
- )
|
|
|
- task_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
- trace_store = FileSystemTraceStore(str(tmp_path / "trace"))
|
|
|
- llm = _PhaseThreeScriptedLLM(task_store)
|
|
|
- runner = AgentRunner(trace_store=trace_store, llm_call=llm)
|
|
|
- artifacts = SqlAlchemyScriptBusinessArtifactRepository(sessions)
|
|
|
- input_service = ScriptInputSnapshotService(
|
|
|
- legacy_input=cast(Any, None),
|
|
|
- persona_source=cast(Any, None),
|
|
|
- strategy_source=cast(Any, None),
|
|
|
- prompt_source=_PromptCatalog(),
|
|
|
- snapshots=snapshots,
|
|
|
- )
|
|
|
- projection = LegacyDetailProjectionService(sessions, _Authorizer())
|
|
|
- fencing = FencedCommandGate(sessions)
|
|
|
- final_uow = SqlAlchemyFinalPublicationUnitOfWork(sessions, projection, fencing)
|
|
|
- lease_root = tmp_path / "leases"
|
|
|
-
|
|
|
- def lease_factory(_script_build_id: int) -> OwnerLease:
|
|
|
- return OwnerLease(sessions, lease_root)
|
|
|
-
|
|
|
- composition = compose_host(
|
|
|
- HostDependencies(
|
|
|
- runner=runner,
|
|
|
- task_store=task_store,
|
|
|
- framework_artifact_store=FileSystemArtifactStore(str(tmp_path / "framework-artifacts")),
|
|
|
- input_snapshot_service=input_service,
|
|
|
- input_snapshots=snapshots,
|
|
|
- bindings=bindings,
|
|
|
- business_artifacts=artifacts,
|
|
|
- publications=SqlAlchemyPublicationRepository(sessions),
|
|
|
- legacy_state=states,
|
|
|
- principal_provider=_PrincipalProvider(),
|
|
|
- build_authorizer=_Authorizer(),
|
|
|
- retrieval_adapters={"decode": _DecodeAdapter()},
|
|
|
- enabled_phase=2,
|
|
|
- agent_data_root=tmp_path / "agent-data",
|
|
|
- candidate_workspaces=SqlAlchemyCandidateWorkspaceRepository(sessions, artifacts),
|
|
|
- default_model_manifest=model_manifest,
|
|
|
- owner_lease_factory=lease_factory,
|
|
|
- fenced_command_gate=fencing,
|
|
|
- final_publication_uow=final_uow,
|
|
|
- legacy_projection=projection,
|
|
|
- )
|
|
|
- )
|
|
|
- await states.set_status(script_build_id, BuildStatus.RUNNING)
|
|
|
- await composition.mission_service.run(script_build_id)
|
|
|
- async with sessions() as session:
|
|
|
- candidate_ids_before = tuple(
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(script_build_paragraph.c.id).where(
|
|
|
- script_build_paragraph.c.script_build_id == script_build_id,
|
|
|
- script_build_paragraph.c.branch_id > 0,
|
|
|
- )
|
|
|
- )
|
|
|
- ).scalars()
|
|
|
- )
|
|
|
-
|
|
|
- assert composition.phase_three is not None
|
|
|
- await composition.phase_three.advance(script_build_id, Principal("phase-two-owner"))
|
|
|
- phase_three_run = composition.phase_three._runs[script_build_id]
|
|
|
- await phase_three_run
|
|
|
- ledger = await task_store.load(ROOT)
|
|
|
- root = ledger.tasks[ledger.root_task_id]
|
|
|
- assert root.status is TaskStatus.COMPLETED
|
|
|
- assert _task_kind(root) == "root-delivery"
|
|
|
- assert llm.worker_kinds[-1] == "root-delivery"
|
|
|
- assert llm.validator_kinds[-1] == "root-delivery"
|
|
|
-
|
|
|
- assert composition.finalization is not None
|
|
|
- result = await composition.finalization.finalize(script_build_id, Principal("phase-two-owner"))
|
|
|
- assert result.committed is True
|
|
|
- detail = await projection.project_authorized(
|
|
|
- script_build_id, principal=Principal("phase-two-owner")
|
|
|
- )
|
|
|
- assert detail["build"]["status"] == BuildStatus.SUCCESS.value
|
|
|
- assert projection.to_canonical(detail).canonical_sha256 == result.legacy_projection_digest
|
|
|
- assert detail["paragraphs"]
|
|
|
-
|
|
|
- async with sessions() as session:
|
|
|
- publication = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(publication_table).where(
|
|
|
- publication_table.c.script_build_id == script_build_id,
|
|
|
- publication_table.c.publication_type == "final",
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .one()
|
|
|
- )
|
|
|
- binding = (
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(mission_binding_table).where(
|
|
|
- mission_binding_table.c.script_build_id == script_build_id
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- .mappings()
|
|
|
- .one()
|
|
|
- )
|
|
|
- candidate_ids_after = tuple(
|
|
|
- (
|
|
|
- await session.execute(
|
|
|
- select(script_build_paragraph.c.id).where(
|
|
|
- script_build_paragraph.c.script_build_id == script_build_id,
|
|
|
- script_build_paragraph.c.branch_id > 0,
|
|
|
- )
|
|
|
- )
|
|
|
- ).scalars()
|
|
|
- )
|
|
|
- assert publication["state"] == "published"
|
|
|
- assert binding["accepted_root_artifact_version_id"] == publication["artifact_version_id"]
|
|
|
- assert candidate_ids_after == candidate_ids_before
|
|
|
-
|
|
|
- await trace_store.record_model_usage(
|
|
|
- ROOT,
|
|
|
- sequence=10_000,
|
|
|
- role="assistant",
|
|
|
- model="scripted-fake-model",
|
|
|
- prompt_tokens=123,
|
|
|
- completion_tokens=7,
|
|
|
- )
|
|
|
- planner_trace = await trace_store.get_trace(ROOT)
|
|
|
- assert planner_trace is not None
|
|
|
- await trace_store.add_message(
|
|
|
- Message.create(
|
|
|
- trace_id=ROOT,
|
|
|
- role="tool",
|
|
|
- sequence=planner_trace.last_sequence + 1,
|
|
|
- parent_sequence=planner_trace.head_sequence,
|
|
|
- tool_call_id="diagnostic-planner-failure",
|
|
|
- content={
|
|
|
- "tool_name": "plan_script_tasks",
|
|
|
- "result": json.dumps(
|
|
|
- {
|
|
|
- "failure": {
|
|
|
- "code": "INPUT_SCOPE_MISMATCH",
|
|
|
- "message": "scope is outside the active direction",
|
|
|
- "disposition": "replan_task",
|
|
|
- "source_tool": "plan_script_tasks",
|
|
|
- "details": {"task_id": root.task_id},
|
|
|
- }
|
|
|
- }
|
|
|
- ),
|
|
|
- },
|
|
|
- )
|
|
|
- )
|
|
|
- planner_started_at = datetime(2026, 7, 22, tzinfo=UTC)
|
|
|
- await trace_store.update_trace(
|
|
|
- ROOT,
|
|
|
- created_at=planner_started_at,
|
|
|
- completed_at=planner_started_at + timedelta(seconds=12.5),
|
|
|
- last_activity_at=planner_started_at + timedelta(seconds=12.5),
|
|
|
- )
|
|
|
- diagnostics = await _collect_diagnostics(
|
|
|
- SimpleNamespace(composition=composition), script_build_id
|
|
|
- )
|
|
|
- assert diagnostics["current_domain_error"] is None
|
|
|
- assert "last_domain_error" not in diagnostics
|
|
|
- assert diagnostics["preset_usage"]["script_planner"]["calls"] >= 1
|
|
|
- assert diagnostics["preset_usage"]["script_planner"]["tokens"] >= 130
|
|
|
- assert diagnostics["preset_usage"]["script_planner"]["seconds"] == pytest.approx(12.5)
|
|
|
- assert diagnostics["error_history_by_code"]["INPUT_SCOPE_MISMATCH"] == 1
|
|
|
-
|
|
|
-
|
|
|
-def _phase_two_policy_count(messages: Sequence[Any]) -> int:
|
|
|
- return len(
|
|
|
- [
|
|
|
- item
|
|
|
- for item in messages
|
|
|
- if item.role == "system" and "script-build-phase-two/v1" in str(item.content)
|
|
|
- ]
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def _phase_two_continuation_count(messages: Sequence[Any]) -> int:
|
|
|
- return len(
|
|
|
- [
|
|
|
- item
|
|
|
- for item in messages
|
|
|
- if item.role == "user" and '"mission": "continue_toward_root"' in str(item.content)
|
|
|
- ]
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def _policy_migration_count(events: Sequence[Mapping[str, Any]]) -> int:
|
|
|
- return len(
|
|
|
- [
|
|
|
- item
|
|
|
- for item in events
|
|
|
- if item.get("event") == "planner_policy_migrated"
|
|
|
- or item.get("event_type") == "planner_policy_migrated"
|
|
|
- ]
|
|
|
- )
|