| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737 |
- """Recursive 任务报告与父级审核工具。
- 子 Agent 通过 ``submit_task_report`` 提交结构化结果;Validator 验收并汇合后,
- 直接父 Agent 必须通过 ``review_task_result`` 批准下一步,两者均不向 Legacy 暴露。
- """
- from __future__ import annotations
- from copy import deepcopy
- from datetime import datetime
- from hashlib import sha256
- from typing import Any
- from pydantic import ValidationError
- from cyber_agent.core.agent_mode import require_mutable_trace_policy
- from cyber_agent.core.context_policy import (
- ContextPolicyError,
- add_context_snapshot,
- build_child_context_access,
- create_context_snapshot,
- normalize_task_brief,
- replace_context_access,
- require_matching_root_task_anchor,
- require_root_task_anchor,
- )
- from cyber_agent.core.task_protocol import (
- TaskReport,
- TaskReportSubmission,
- TaskReview,
- ReviewDecision,
- TaskBrief,
- TaskProgress,
- task_progress_readiness_error,
- ensure_task_protocol,
- )
- from cyber_agent.core.task_protocol_service import (
- TaskProtocolService,
- TaskProtocolStateError,
- )
- from cyber_agent.core.validation import ValidationResult
- from cyber_agent.application.candidate import CandidateReviewAction
- from cyber_agent.application.models import canonical_json
- from cyber_agent.tools import tool
- _ALLOWED_REVIEW_DECISIONS_BY_VALIDATION: dict[str, set[ReviewDecision]] = {
- "passed": {
- "ACCEPT_REFINE",
- "REJECT_REFINE",
- "REVISE_CHILD",
- "DESCEND_AGAIN",
- "ASCEND",
- "FAIL",
- },
- "failed": {"REVISE_CHILD", "REPLAN_CURRENT", "FAIL"},
- "unknown": {"REVISE_CHILD", "REPLAN_CURRENT", "FAIL"},
- "error": {"REVISE_CHILD", "FAIL"},
- }
- def _error(message: str) -> dict[str, Any]:
- return {"status": "failed", "error": message}
- def _review_command_hash(
- *,
- child_trace_id: str,
- decision: ReviewDecision,
- reason: str,
- approved_next_task: TaskBrief | str | None,
- candidate_actions: list[CandidateReviewAction] | None,
- ) -> str:
- """Hash the persisted assistant command before protocol normalization."""
- def json_value(value: Any) -> Any:
- if hasattr(value, "model_dump"):
- return value.model_dump(mode="json")
- if isinstance(value, list):
- return [json_value(item) for item in value]
- if isinstance(value, tuple):
- return [json_value(item) for item in value]
- if isinstance(value, dict):
- return {key: json_value(item) for key, item in value.items()}
- return value
- payload = {
- "child_trace_id": child_trace_id,
- "decision": decision,
- "reason": reason,
- "approved_next_task": json_value(approved_next_task),
- "candidate_actions": json_value(candidate_actions or []),
- }
- return sha256(canonical_json(payload).encode("utf-8")).hexdigest()
- def _completed_review_response(
- review_record: dict[str, Any],
- state: dict[str, Any],
- ) -> dict[str, Any]:
- task_review = TaskReview.model_validate({
- field: review_record.get(field)
- for field in TaskReview.model_fields
- })
- return {
- "status": "completed",
- "task_review": task_review.model_dump(),
- "context_ref": review_record.get("context_ref"),
- "context_ref_warning": review_record.get("context_ref_warning"),
- "next_action_task_brief": review_record.get(
- "next_action_task_brief"
- ),
- "pending_review_count": len(state["pending_reviews"]),
- "next_action_count": len(state["next_actions"]),
- }
- async def _project_committed_review(
- *,
- store: Any,
- context: dict[str, Any],
- parent_trace_id: str,
- child_trace_id: str,
- state: dict[str, Any],
- review_record: dict[str, Any],
- ) -> None:
- """Idempotently finish projections after the parent protocol commit."""
- reviewed_at_sequence = int(review_record["reviewed_at_sequence"])
- if review_record["decision"] == "REVISE_CHILD":
- child = await store.get_trace(child_trace_id)
- if child is None:
- raise ValueError(
- "Committed REVISE_CHILD review references a missing child Trace"
- )
- child_state = ensure_task_protocol(child.context)
- old_report = child_state.get("task_report")
- if old_report and old_report not in child_state["report_history"]:
- child_state["report_history"].append(old_report)
- child_state["task_report"] = None
- child_state["task_report_submitted_at_sequence"] = None
- child_state["task_report_progress_revision"] = None
- child_state["task_report_validation"] = None
- child_state["protocol_correction_attempts"] = 0
- replace_context_access(
- child.context,
- [],
- root_task_anchor=require_root_task_anchor(child.context),
- task_brief=child_state.get("task_brief"),
- )
- await store.update_trace(child_trace_id, context=child.context)
- event_service = context.get("event_service")
- if event_service is not None:
- await event_service.emit_after_commit(
- source_trace_id=parent_trace_id,
- event_type="task.review.recorded",
- event_key=(
- f"task.review.recorded:{parent_trace_id}:"
- f"{child_trace_id}:{reviewed_at_sequence}"
- ),
- effective_at_sequence=reviewed_at_sequence,
- payload={"task_review": review_record},
- )
- pending_entry = review_record.get("pending_review") or {}
- goal_id = pending_entry.get("goal_id")
- if not goal_id:
- return
- decision = review_record["decision"]
- resolved_replan = review_record.get("resolved_replan")
- if state["pending_reviews"]:
- next_status = "pending_review"
- elif resolved_replan:
- next_status = "in_progress"
- goal_id = resolved_replan["goal_id"]
- elif decision == "ASCEND":
- next_status = "completed"
- elif decision == "FAIL":
- next_status = "failed"
- else:
- next_status = "in_progress"
- reason = review_record["reason"]
- await store.update_goal(
- parent_trace_id,
- goal_id,
- cascade_completion=False,
- status=next_status,
- summary=reason if next_status in {"completed", "failed"} else None,
- )
- tree = context.get("goal_tree")
- goal = tree.find(goal_id) if tree else None
- if goal:
- goal.status = next_status
- if next_status in {"completed", "failed"}:
- goal.summary = reason
- if tree.current_id == goal_id:
- tree.current_id = goal.parent_id
- elif resolved_replan:
- tree.current_id = goal_id
- if resolved_replan:
- persisted_tree = await store.get_goal_tree(parent_trace_id)
- persisted_goal = persisted_tree.find(goal_id) if persisted_tree else None
- if persisted_goal:
- persisted_goal.status = "in_progress"
- persisted_tree.current_id = goal_id
- await store.update_goal_tree(parent_trace_id, persisted_tree)
- @tool(
- description=(
- "Replace the current Recursive task's structured progress with one "
- "complete, version-checked snapshot."
- ),
- hidden_params=["context"],
- groups=["core"],
- )
- async def update_task_progress(
- expected_revision: int,
- progress: TaskProgress,
- context: dict | None = None,
- ) -> dict[str, Any]:
- """追加当前 Trace 的下一版 TaskProgress。"""
- if not context:
- return _error("context is required")
- if context.get("side_branch"):
- return _error("Side branches cannot update TaskProgress")
- service = context.get("task_protocol_service")
- trace_id = context.get("trace_id")
- if service is None or not trace_id:
- return _error("task_protocol_service and trace_id are required")
- try:
- revision = await service.update_progress(
- trace_id,
- expected_revision=expected_revision,
- progress=progress,
- effective_at_sequence=int(context.get("sequence", 0) or 0),
- )
- except (TaskProtocolStateError, ValueError) as exc:
- return _error(str(exc))
- return {
- "status": "completed",
- "task_progress": revision.model_dump(mode="json"),
- }
- @tool(
- description="Submit the current Recursive child Agent's structured task report before finishing.",
- hidden_params=["context"],
- groups=["core"],
- )
- async def submit_task_report(
- task_report: TaskReportSubmission,
- context: dict | None = None,
- ) -> dict[str, Any]:
- if not context:
- return _error("context is required")
- store = context.get("store")
- trace_id = context.get("trace_id")
- if not store or not trace_id:
- return _error("store and trace_id are required")
- service = context.get("task_protocol_service") or TaskProtocolService(store)
- async with service.transaction(trace_id):
- return await _submit_task_report_locked(task_report, context)
- async def _submit_task_report_locked(
- task_report: TaskReportSubmission,
- context: dict | None = None,
- ) -> dict[str, Any]:
- """提交当前 Recursive 非根 Agent 的结构化任务报告。
- 子 Agent 在结束前调用;Runner 会拒绝未提交报告或尚有待审核孩子的返回。
- """
- if not context:
- return _error("context is required")
- store = context.get("store")
- trace_id = context.get("trace_id")
- if not store or not trace_id:
- return _error("store and trace_id are required")
- trace = await store.get_trace(trace_id)
- if not trace:
- return _error(f"Trace not found: {trace_id}")
- try:
- policy = require_mutable_trace_policy(trace.context)
- except ValueError as exc:
- return _error(str(exc))
- if not policy.requires_task_protocol:
- return _error("submit_task_report requires a writable Recursive protocol")
- if not trace.parent_trace_id:
- return _error("Root Agent does not submit a TaskReport")
- root_trace_id = trace.context.get("root_trace_id") or trace.trace_id
- root = trace if root_trace_id == trace.trace_id else await store.get_trace(
- root_trace_id
- )
- if not root:
- return _error(f"Recursive root Trace not found: {root_trace_id}")
- try:
- require_matching_root_task_anchor(root.context, trace.context)
- except ContextPolicyError as exc:
- return _error(str(exc))
- state = ensure_task_protocol(trace.context)
- if state["pending_reviews"]:
- return _error("Review all child TaskReports before submitting this TaskReport")
- if state["next_actions"]:
- return _error("Execute every approved next action before submitting this TaskReport")
- if state["task_report"] is not None:
- return _error("A TaskReport has already been submitted")
- try:
- parsed_submission = (
- TaskReportSubmission.model_validate_json(task_report)
- if isinstance(task_report, str)
- else TaskReportSubmission.model_validate(task_report)
- )
- except ValidationError as exc:
- return _error(f"Invalid TaskReport: {exc}")
- if parsed_submission.candidate_refs:
- candidate_service = context.get("candidate_service")
- if candidate_service is None:
- return _error(
- "TaskReport CandidateRefs require the bound CandidateService"
- )
- try:
- await candidate_service.validate_report_refs(
- trace_id,
- parsed_submission.candidate_refs,
- active_head_sequence=context.get("head_sequence"),
- )
- except ValueError as exc:
- return _error(f"Invalid TaskReport CandidateRefs: {exc}")
- if policy.requires_task_progress and parsed_submission.outcome == "satisfied":
- readiness_error = task_progress_readiness_error(state)
- if readiness_error:
- return _error(readiness_error)
- try:
- report = TaskReport(
- child_trace_id=trace_id,
- **parsed_submission.model_dump(),
- )
- except ValidationError as exc:
- return _error(f"Invalid TaskReport: {exc}")
- state["task_report"] = report.model_dump()
- state["task_report_submitted_at_sequence"] = context.get("sequence", 0)
- state["task_report_progress_revision"] = state.get(
- "task_progress_head_revision"
- )
- state["task_report_validation"] = None
- state["protocol_correction_attempts"] = 0
- await store.update_trace(trace_id, context=trace.context)
- goal_id = context.get("goal_id")
- if goal_id:
- goal_status = (
- "failed"
- if report.outcome in {"failed", "protocol_error"}
- else "completed"
- )
- await store.update_goal(
- trace_id,
- goal_id,
- cascade_completion=False,
- status=goal_status,
- summary=report.summary,
- )
- tree = context.get("goal_tree")
- goal = tree.find(goal_id) if tree else None
- if goal:
- goal.status = goal_status
- goal.summary = report.summary
- return {"status": "completed", "task_report": report.model_dump()}
- @tool(
- description="Review one pending direct child's TaskReport and approve the parent's next action.",
- hidden_params=["context"],
- groups=["core"],
- )
- async def review_task_result(
- child_trace_id: str,
- decision: ReviewDecision,
- reason: str,
- approved_next_task: TaskBrief | None = None,
- candidate_actions: list[CandidateReviewAction] | None = None,
- context: dict | None = None,
- ) -> dict[str, Any]:
- if not context:
- return _error("context is required")
- store = context.get("store")
- parent_trace_id = context.get("trace_id")
- if not store or not parent_trace_id:
- return _error("store and trace_id are required")
- service = context.get("task_protocol_service") or TaskProtocolService(store)
- async with service.transaction_many(parent_trace_id, child_trace_id):
- return await _review_task_result_locked(
- child_trace_id=child_trace_id,
- decision=decision,
- reason=reason,
- approved_next_task=approved_next_task,
- candidate_actions=candidate_actions,
- context=context,
- )
- async def _review_task_result_locked(
- child_trace_id: str,
- decision: ReviewDecision,
- reason: str,
- approved_next_task: TaskBrief | None = None,
- candidate_actions: list[CandidateReviewAction] | None = None,
- context: dict | None = None,
- ) -> dict[str, Any]:
- """审核一份直属孩子的待处理 ``TaskReport``。
- 父 Agent 在 ``pending_review`` 阶段调用;决策受 Validator 结果约束并持久化下一动作。
- """
- if not context:
- return _error("context is required")
- store = context.get("store")
- parent_trace_id = context.get("trace_id")
- if not store or not parent_trace_id:
- return _error("store and trace_id are required")
- command_id = str(context.get("tool_call_id") or "") or None
- command_hash = _review_command_hash(
- child_trace_id=child_trace_id,
- decision=decision,
- reason=reason,
- approved_next_task=approved_next_task,
- candidate_actions=candidate_actions,
- )
- parent = await store.get_trace(parent_trace_id)
- child = await store.get_trace(child_trace_id)
- if not parent or not child:
- return _error("Parent or child Trace not found")
- try:
- policy = require_mutable_trace_policy(parent.context)
- except ValueError as exc:
- return _error(str(exc))
- if not policy.requires_task_protocol:
- return _error("review_task_result requires a writable Recursive protocol")
- if child.parent_trace_id != parent_trace_id or child.uid != parent.uid:
- return _error("TaskReport must belong to a direct child with the same owner")
- state = ensure_task_protocol(parent.context)
- committed = [
- item for item in state["reviews"]
- if command_id is not None
- and item.get("review_command_id") == command_id
- ]
- if len(committed) > 1:
- return _error("Persisted TaskReview command identity is duplicated")
- if committed:
- review_record = committed[0]
- if review_record.get("review_command_hash") != command_hash:
- return _error("TaskReview command payload changed during recovery")
- await _project_committed_review(
- store=store,
- context=context,
- parent_trace_id=parent_trace_id,
- child_trace_id=child_trace_id,
- state=state,
- review_record=review_record,
- )
- return _completed_review_response(review_record, state)
- pending = state["pending_reviews"]
- entry = pending.get(child_trace_id)
- if not entry:
- return _error("This direct child has no pending TaskReport")
- origin_goal_id = child.parent_goal_id
- if entry.get("goal_id") != origin_goal_id:
- return _error(
- "Pending TaskReport goal_id does not match the child's originating Goal"
- )
- revision_task_brief = None
- child_state = None
- try:
- root_trace_id = parent.context.get("root_trace_id") or parent.trace_id
- root = parent if root_trace_id == parent.trace_id else await store.get_trace(
- root_trace_id
- )
- if not root:
- return _error(f"Recursive root Trace not found: {root_trace_id}")
- root_task_anchor = require_matching_root_task_anchor(
- root.context,
- parent.context,
- )
- require_matching_root_task_anchor(root.context, child.context)
- parent_task_brief = state.get("task_brief")
- if isinstance(approved_next_task, str):
- approved_next_task = TaskBrief.model_validate_json(approved_next_task)
- if approved_next_task is not None:
- approved_next_task = normalize_task_brief(
- approved_next_task,
- parent_task_brief=parent_task_brief,
- root_task_anchor=root_task_anchor,
- )
- validation_result = ValidationResult.model_validate(
- entry.get("validation_result")
- )
- if validation_result.evaluated_trace_id != child_trace_id:
- return _error(
- "ValidationResult evaluated_trace_id does not match the direct child"
- )
- child_validation_state = ensure_task_protocol(child.context).get(
- "task_report_validation"
- )
- if not isinstance(child_validation_state, dict):
- return _error("Child has no persisted framework validation cache")
- if (
- child_validation_state.get("plan_hash") != validation_result.plan_hash
- or child_validation_state.get("aggregate_result")
- != validation_result.model_dump(mode="json")
- ):
- return _error(
- "Pending ValidationResult does not match the child's authoritative cache"
- )
- allowed_decisions = _ALLOWED_REVIEW_DECISIONS_BY_VALIDATION[
- validation_result.outcome
- ]
- if decision not in allowed_decisions:
- return _error(
- f"Validation {validation_result.outcome} only allows: "
- f"{', '.join(sorted(allowed_decisions))}"
- )
- review = TaskReview.model_validate({
- "parent_trace_id": parent_trace_id,
- "child_trace_id": child_trace_id,
- "decision": decision,
- "reason": reason,
- "approved_next_task": approved_next_task,
- "retry_from": (
- validation_result.retry_from
- if decision == "REPLAN_CURRENT"
- else None
- ),
- "candidate_actions": candidate_actions or [],
- })
- report = TaskReport.model_validate(entry["task_report"])
- if report.child_trace_id != child_trace_id:
- return _error(
- "Pending TaskReport child_trace_id does not match the direct child"
- )
- if review.decision == "REVISE_CHILD":
- child_state = ensure_task_protocol(child.context)
- revision_task_brief = normalize_task_brief(
- review.approved_next_task or child_state.get("task_brief"),
- parent_task_brief=parent_task_brief,
- root_task_anchor=root_task_anchor,
- )
- except (ContextPolicyError, ValidationError) as exc:
- return _error(f"Invalid TaskReview: {exc}")
- if report.outcome in {"failed", "protocol_error"} and review.decision not in {
- "REVISE_CHILD",
- "FAIL",
- }:
- return _error("Failed/protocol_error reports only allow REVISE_CHILD or FAIL")
- if review.decision == "REPLAN_CURRENT":
- goal_id = origin_goal_id
- persisted_tree = await store.get_goal_tree(parent_trace_id)
- if not goal_id or not persisted_tree or not persisted_tree.find(goal_id):
- return _error(
- "REPLAN_CURRENT requires the direct parent's originating Goal"
- )
- if review.decision in {"ASCEND", "FAIL"} and len(pending) != 1:
- return _error("ASCEND/FAIL is only allowed for the final pending report")
- if review.decision in {"ASCEND", "FAIL"} and state["next_actions"]:
- return _error("ASCEND/FAIL requires every approved next action to finish first")
- if review.decision in {"ACCEPT_REFINE", "DESCEND_AGAIN"}:
- child_limit = policy.max_children_per_parent
- if child_limit is not None:
- existing_children = await store.list_traces(
- parent_trace_id=parent_trace_id,
- created_by_tool="agent",
- limit=child_limit + 1,
- )
- reserved_children = sum(
- action.get("decision") in {"ACCEPT_REFINE", "DESCEND_AGAIN"}
- for action in state["next_actions"]
- )
- if len(existing_children) + reserved_children + 1 > child_limit:
- return _error(
- "Cannot approve another child: local child Agent limit "
- f"would be exceeded (existing={len(existing_children)}, "
- f"reserved={reserved_children}, max={child_limit})"
- )
- action_task_brief = revision_task_brief or review.approved_next_task
- if action_task_brief is not None:
- try:
- build_child_context_access(
- parent_context=parent.context,
- parent_trace_id=parent_trace_id,
- root_trace_id=root_trace_id,
- uid=parent.uid,
- parent_task_state=state,
- child_task_brief=action_task_brief,
- root_task_anchor=root_task_anchor,
- )
- except ContextPolicyError as exc:
- return _error(f"Approved TaskBrief context is invalid: {exc}")
- reviewed_at_sequence = int(context.get("sequence", 0) or 0)
- if report.candidate_refs or review.candidate_actions:
- candidate_service = context.get("candidate_service")
- if candidate_service is None:
- return _error("Candidate actions require the bound CandidateService")
- if not context.get("tool_call_id"):
- return _error(
- "Candidate actions require a persisted review tool_call_id"
- )
- try:
- await candidate_service.apply_review_actions(
- parent_trace_id,
- child_trace_id,
- report_refs=report.candidate_refs,
- actions=review.candidate_actions,
- effective_at_sequence=reviewed_at_sequence,
- command_id=str(context["tool_call_id"]),
- )
- except ValueError as exc:
- return _error(f"Invalid candidate action: {exc}")
- context_ref = None
- context_ref_warning = None
- try:
- snapshot = create_context_snapshot(
- kind="reviewed_task_result",
- summary=review.reason,
- source_trace_id=child_trace_id,
- root_trace_id=root_trace_id,
- uid=parent.uid,
- content={
- "task_report": deepcopy(entry["task_report"]),
- "validation_result": deepcopy(entry["validation_result"]),
- "task_review": review.model_dump(mode="json"),
- },
- granted_at_sequence=reviewed_at_sequence,
- )
- context_ref = add_context_snapshot(
- parent.context,
- snapshot,
- root_task_anchor=root_task_anchor,
- task_brief=state.get("task_brief"),
- )
- except ContextPolicyError as exc:
- context_ref_warning = str(exc)
- if context_ref is not None and action_task_brief is not None:
- try:
- action_task_brief = normalize_task_brief(
- action_task_brief.model_copy(update={
- "context_refs": [
- *action_task_brief.context_refs,
- context_ref,
- ],
- }),
- parent_task_brief=parent_task_brief,
- root_task_anchor=root_task_anchor,
- )
- build_child_context_access(
- parent_context=parent.context,
- parent_trace_id=parent_trace_id,
- root_trace_id=root_trace_id,
- uid=parent.uid,
- parent_task_state=state,
- child_task_brief=action_task_brief,
- root_task_anchor=root_task_anchor,
- )
- except ContextPolicyError as exc:
- action_task_brief = revision_task_brief or review.approved_next_task
- context_ref_warning = (
- f"{context_ref_warning}; {exc}"
- if context_ref_warning
- else str(exc)
- )
- pending.pop(child_trace_id)
- review_record = {
- **review.model_dump(),
- "review_command_id": command_id,
- "review_command_hash": command_hash,
- "pending_review": deepcopy(entry),
- "reviewed_at": datetime.now().isoformat(),
- "reviewed_at_sequence": reviewed_at_sequence,
- "context_ref": context_ref.model_dump() if context_ref else None,
- "context_ref_warning": context_ref_warning,
- "next_action_task_brief": (
- action_task_brief.model_dump(mode="json")
- if action_task_brief is not None
- else None
- ),
- }
- state["reviews"].append(review_record)
- state["protocol_correction_attempts"] = 0
- if review.decision in {"ACCEPT_REFINE", "DESCEND_AGAIN", "REVISE_CHILD"}:
- state["next_actions"].append({
- "decision": review.decision,
- "child_trace_id": child_trace_id,
- "goal_id": origin_goal_id,
- "task_brief": action_task_brief.model_dump() if action_task_brief else None,
- "created_at_sequence": reviewed_at_sequence,
- })
- resolved_replan = None
- if review.decision == "REPLAN_CURRENT":
- state["pending_replans"].append({
- "child_trace_id": child_trace_id,
- "goal_id": origin_goal_id,
- "retry_from": review.retry_from,
- "requested_at_sequence": context.get("sequence", 0),
- "received_at_sequence": entry.get("received_at_sequence"),
- })
- elif review.decision == "FAIL":
- state["pending_replans"].clear()
- if not pending and state["pending_replans"]:
- resolved_replan = state["pending_replans"][0]
- state["pending_replans"].clear()
- review_record["resolved_replan"] = deepcopy(resolved_replan)
- await store.update_trace(parent_trace_id, context=parent.context)
- await _project_committed_review(
- store=store,
- context=context,
- parent_trace_id=parent_trace_id,
- child_trace_id=child_trace_id,
- state=state,
- review_record=review_record,
- )
- return _completed_review_response(review_record, state)
|