|
|
@@ -826,10 +826,13 @@ class AgentRunner:
|
|
|
"material_usage_recorded": total_material_chars == 0,
|
|
|
}
|
|
|
if candidate_ref is None:
|
|
|
- state["task_report_validation"] = validation_cache
|
|
|
- await self.trace_store.update_trace(
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+ await self.task_protocol_service.mutate_state(
|
|
|
evaluated_trace_id,
|
|
|
- context=evaluated.context,
|
|
|
+ lambda _trace, fresh_state: fresh_state.__setitem__(
|
|
|
+ "task_report_validation",
|
|
|
+ deepcopy(validation_cache),
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
candidate_checkpoint = None
|
|
|
@@ -925,20 +928,22 @@ class AgentRunner:
|
|
|
)
|
|
|
)
|
|
|
else:
|
|
|
- fresh = await self.trace_store.get_trace(evaluated_trace_id)
|
|
|
- if not fresh:
|
|
|
- raise ValueError(f"Trace not found: {evaluated_trace_id}")
|
|
|
- fresh_state = ensure_task_protocol(fresh.context)
|
|
|
- fresh_cache = fresh_state.get("task_report_validation")
|
|
|
- if (
|
|
|
- not isinstance(fresh_cache, dict)
|
|
|
- or fresh_cache.get("plan_hash") != plan.plan_hash
|
|
|
- ):
|
|
|
- raise ValueError("Validation cache changed while recording materials")
|
|
|
- fresh_cache["material_usage_recorded"] = True
|
|
|
- await self.trace_store.update_trace(
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def mark_material_usage(_trace, fresh_state):
|
|
|
+ fresh_cache = fresh_state.get("task_report_validation")
|
|
|
+ if (
|
|
|
+ not isinstance(fresh_cache, dict)
|
|
|
+ or fresh_cache.get("plan_hash") != plan.plan_hash
|
|
|
+ ):
|
|
|
+ raise ValueError(
|
|
|
+ "Validation cache changed while recording materials"
|
|
|
+ )
|
|
|
+ fresh_cache["material_usage_recorded"] = True
|
|
|
+
|
|
|
+ await self.task_protocol_service.mutate_state(
|
|
|
evaluated_trace_id,
|
|
|
- context=fresh.context,
|
|
|
+ mark_material_usage,
|
|
|
)
|
|
|
|
|
|
lineage_event = None
|
|
|
@@ -1037,27 +1042,32 @@ class AgentRunner:
|
|
|
scope_results=tuple(validation_cache["scope_results"]),
|
|
|
)
|
|
|
return
|
|
|
- fresh = await self.trace_store.get_trace(evaluated_trace_id)
|
|
|
- if not fresh:
|
|
|
- raise ValueError(f"Trace not found: {evaluated_trace_id}")
|
|
|
- fresh_state = ensure_task_protocol(fresh.context)
|
|
|
- cache = fresh_state.get("task_report_validation")
|
|
|
- if not isinstance(cache, dict) or cache.get("plan_hash") != plan.plan_hash:
|
|
|
- raise ValueError("Validation cache changed while scopes were running")
|
|
|
- by_scope = {
|
|
|
- item.get("scope"): item
|
|
|
- for item in cache.get("scope_results", [])
|
|
|
- if isinstance(item, dict)
|
|
|
- }
|
|
|
- by_scope[result.scope] = result.model_dump(mode="json")
|
|
|
- cache["scope_results"] = [
|
|
|
- by_scope[item]
|
|
|
- for item in plan.effective_scopes
|
|
|
- if item in by_scope
|
|
|
- ]
|
|
|
- await self.trace_store.update_trace(
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def persist_trace_scope(_trace, fresh_state):
|
|
|
+ cache = fresh_state.get("task_report_validation")
|
|
|
+ if (
|
|
|
+ not isinstance(cache, dict)
|
|
|
+ or cache.get("plan_hash") != plan.plan_hash
|
|
|
+ ):
|
|
|
+ raise ValueError(
|
|
|
+ "Validation cache changed while scopes were running"
|
|
|
+ )
|
|
|
+ by_scope = {
|
|
|
+ item.get("scope"): item
|
|
|
+ for item in cache.get("scope_results", [])
|
|
|
+ if isinstance(item, dict)
|
|
|
+ }
|
|
|
+ by_scope[result.scope] = result.model_dump(mode="json")
|
|
|
+ cache["scope_results"] = [
|
|
|
+ by_scope[item]
|
|
|
+ for item in plan.effective_scopes
|
|
|
+ if item in by_scope
|
|
|
+ ]
|
|
|
+
|
|
|
+ await self.task_protocol_service.mutate_state(
|
|
|
evaluated_trace_id,
|
|
|
- context=fresh.context,
|
|
|
+ persist_trace_scope,
|
|
|
)
|
|
|
|
|
|
run = await validator.validate_plan(
|
|
|
@@ -1091,20 +1101,24 @@ class AgentRunner:
|
|
|
),
|
|
|
)
|
|
|
return run
|
|
|
- fresh = await self.trace_store.get_trace(evaluated_trace_id)
|
|
|
- if not fresh:
|
|
|
- raise ValueError(f"Trace not found: {evaluated_trace_id}")
|
|
|
- fresh_state = ensure_task_protocol(fresh.context)
|
|
|
- cache = fresh_state.get("task_report_validation")
|
|
|
- if not isinstance(cache, dict) or cache.get("plan_hash") != plan.plan_hash:
|
|
|
- raise ValueError("Validation cache changed before aggregation")
|
|
|
- cache["aggregate_result"] = run.result.model_dump(mode="json")
|
|
|
- cache["scope_results"] = [
|
|
|
- item.model_dump(mode="json") for item in run.result.scope_results
|
|
|
- ]
|
|
|
- await self.trace_store.update_trace(
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def persist_aggregate(_trace, fresh_state):
|
|
|
+ cache = fresh_state.get("task_report_validation")
|
|
|
+ if (
|
|
|
+ not isinstance(cache, dict)
|
|
|
+ or cache.get("plan_hash") != plan.plan_hash
|
|
|
+ ):
|
|
|
+ raise ValueError("Validation cache changed before aggregation")
|
|
|
+ cache["aggregate_result"] = run.result.model_dump(mode="json")
|
|
|
+ cache["scope_results"] = [
|
|
|
+ item.model_dump(mode="json")
|
|
|
+ for item in run.result.scope_results
|
|
|
+ ]
|
|
|
+
|
|
|
+ await self.task_protocol_service.mutate_state(
|
|
|
evaluated_trace_id,
|
|
|
- context=fresh.context,
|
|
|
+ persist_aggregate,
|
|
|
)
|
|
|
if self.event_service is not None:
|
|
|
await self.event_service.emit_after_commit(
|
|
|
@@ -2044,30 +2058,43 @@ class AgentRunner:
|
|
|
"validation attempts; create a new trace"
|
|
|
)
|
|
|
if policy.requires_task_progress:
|
|
|
- state = ensure_task_protocol(trace_obj.context)
|
|
|
- had_report = state.get("task_report") is not None
|
|
|
- report_progress_revision = state.get(
|
|
|
- "task_report_progress_revision"
|
|
|
- )
|
|
|
- if had_report and report_progress_revision is None:
|
|
|
- raise ValueError(
|
|
|
- "TaskReport is missing its TaskProgress revision binding"
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+ async with self.task_protocol_service.locked_trace(
|
|
|
+ trace_obj.trace_id
|
|
|
+ ) as fresh_trace:
|
|
|
+ state = ensure_task_protocol(fresh_trace.context)
|
|
|
+ had_report = state.get("task_report") is not None
|
|
|
+ report_progress_revision = state.get(
|
|
|
+ "task_report_progress_revision"
|
|
|
)
|
|
|
- previous_head = state.get("task_progress_head_revision")
|
|
|
- rewind_task_progress(state, trace_obj.last_sequence)
|
|
|
- if (
|
|
|
- had_report
|
|
|
- and state.get("task_report_progress_revision") is None
|
|
|
- ):
|
|
|
- raise ValueError(
|
|
|
- "TaskReport references TaskProgress outside the "
|
|
|
- "current revision ancestry"
|
|
|
+ if had_report and report_progress_revision is None:
|
|
|
+ raise ValueError(
|
|
|
+ "TaskReport is missing its TaskProgress revision binding"
|
|
|
+ )
|
|
|
+ previous_head = state.get(
|
|
|
+ "task_progress_head_revision"
|
|
|
)
|
|
|
- if state.get("task_progress_head_revision") != previous_head:
|
|
|
- await self.trace_store.update_trace(
|
|
|
- trace_obj.trace_id,
|
|
|
- context=trace_obj.context,
|
|
|
+ rewind_task_progress(
|
|
|
+ state,
|
|
|
+ fresh_trace.last_sequence,
|
|
|
)
|
|
|
+ if (
|
|
|
+ had_report
|
|
|
+ and state.get("task_report_progress_revision") is None
|
|
|
+ ):
|
|
|
+ raise ValueError(
|
|
|
+ "TaskReport references TaskProgress outside the "
|
|
|
+ "current revision ancestry"
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ state.get("task_progress_head_revision")
|
|
|
+ != previous_head
|
|
|
+ ):
|
|
|
+ await self.trace_store.update_trace(
|
|
|
+ fresh_trace.trace_id,
|
|
|
+ context=fresh_trace.context,
|
|
|
+ )
|
|
|
+ trace_obj = fresh_trace
|
|
|
assert self.resource_budget is not None
|
|
|
await self.resource_budget.get_usage(root.trace_id)
|
|
|
|
|
|
@@ -2721,6 +2748,7 @@ class AgentRunner:
|
|
|
goal_id=current_goal_id,
|
|
|
goal_tree=goal_tree,
|
|
|
sequence=sequence,
|
|
|
+ tool_call_id=call.tool_call_id,
|
|
|
side_branch_ctx=side_branch_ctx,
|
|
|
trigger_event=trigger_event,
|
|
|
),
|
|
|
@@ -3748,6 +3776,7 @@ class AgentRunner:
|
|
|
goal_id=current_goal_id,
|
|
|
goal_tree=goal_tree,
|
|
|
sequence=sequence,
|
|
|
+ tool_call_id=tc["id"],
|
|
|
side_branch_ctx=side_branch_ctx,
|
|
|
trigger_event=trigger_event_for_tool,
|
|
|
),
|
|
|
@@ -3897,6 +3926,7 @@ class AgentRunner:
|
|
|
goal_id=current_goal_id,
|
|
|
goal_tree=goal_tree,
|
|
|
sequence=sequence,
|
|
|
+ tool_call_id=tc["id"],
|
|
|
side_branch_ctx=side_branch_ctx,
|
|
|
trigger_event=trigger_event_for_tool,
|
|
|
),
|
|
|
@@ -4069,31 +4099,62 @@ class AgentRunner:
|
|
|
trace = fresh_trace
|
|
|
policy = policy_from_context(trace.context)
|
|
|
if policy.requires_task_protocol:
|
|
|
- state = ensure_task_protocol(trace.context)
|
|
|
- missing_report = bool(
|
|
|
- trace.parent_trace_id and state.get("task_report") is None
|
|
|
- )
|
|
|
- pending_reviews = bool(state["pending_reviews"])
|
|
|
- pending_actions = bool(state["next_actions"])
|
|
|
- progress_error = (
|
|
|
- task_progress_readiness_error(state)
|
|
|
- if policy.requires_task_progress
|
|
|
- and not trace.parent_trace_id
|
|
|
- else None
|
|
|
- )
|
|
|
- if (
|
|
|
- missing_report
|
|
|
- or pending_reviews
|
|
|
- or pending_actions
|
|
|
- or progress_error
|
|
|
- ):
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def apply_completion_gate(fresh, state):
|
|
|
+ missing_report = bool(
|
|
|
+ fresh.parent_trace_id
|
|
|
+ and state.get("task_report") is None
|
|
|
+ )
|
|
|
+ pending_reviews = bool(state["pending_reviews"])
|
|
|
+ pending_actions = bool(state["next_actions"])
|
|
|
+ progress_error = (
|
|
|
+ task_progress_readiness_error(state)
|
|
|
+ if policy.requires_task_progress
|
|
|
+ and not fresh.parent_trace_id
|
|
|
+ else None
|
|
|
+ )
|
|
|
+ blocked = bool(
|
|
|
+ missing_report
|
|
|
+ or pending_reviews
|
|
|
+ or pending_actions
|
|
|
+ or progress_error
|
|
|
+ )
|
|
|
attempts = state["protocol_correction_attempts"]
|
|
|
- if attempts < 2:
|
|
|
+ if blocked and attempts < 2:
|
|
|
state["protocol_correction_attempts"] = attempts + 1
|
|
|
- await self.trace_store.update_trace(
|
|
|
+ elif blocked and missing_report:
|
|
|
+ report = protocol_error_report(
|
|
|
trace_id,
|
|
|
- context=trace.context,
|
|
|
+ "No valid TaskReport after two correction attempts",
|
|
|
+ )
|
|
|
+ state["task_report"] = report.model_dump()
|
|
|
+ state["task_report_submitted_at_sequence"] = sequence
|
|
|
+ state["task_report_progress_revision"] = state.get(
|
|
|
+ "task_progress_head_revision"
|
|
|
)
|
|
|
+ return {
|
|
|
+ "missing_report": missing_report,
|
|
|
+ "pending_reviews": pending_reviews,
|
|
|
+ "pending_actions": pending_actions,
|
|
|
+ "progress_error": progress_error,
|
|
|
+ "attempts": attempts,
|
|
|
+ "blocked": blocked,
|
|
|
+ }
|
|
|
+
|
|
|
+ gate = await self.task_protocol_service.mutate_state(
|
|
|
+ trace_id,
|
|
|
+ apply_completion_gate,
|
|
|
+ )
|
|
|
+ missing_report = gate["missing_report"]
|
|
|
+ pending_reviews = gate["pending_reviews"]
|
|
|
+ pending_actions = gate["pending_actions"]
|
|
|
+ progress_error = gate["progress_error"]
|
|
|
+ if (
|
|
|
+ gate["blocked"]
|
|
|
+ ):
|
|
|
+ attempts = gate["attempts"]
|
|
|
+ if attempts < 2:
|
|
|
if pending_reviews:
|
|
|
required_action = (
|
|
|
"review every pending child report with "
|
|
|
@@ -4121,20 +4182,9 @@ class AgentRunner:
|
|
|
})
|
|
|
continue
|
|
|
|
|
|
- if missing_report:
|
|
|
- report = protocol_error_report(
|
|
|
- trace_id,
|
|
|
- "No valid TaskReport after two correction attempts",
|
|
|
- )
|
|
|
- state["task_report"] = report.model_dump()
|
|
|
- state["task_report_submitted_at_sequence"] = sequence
|
|
|
- state["task_report_progress_revision"] = state.get(
|
|
|
- "task_progress_head_revision"
|
|
|
- )
|
|
|
completion_status = "failed"
|
|
|
await self.trace_store.update_trace(
|
|
|
trace_id,
|
|
|
- context=trace.context,
|
|
|
error_message="Recursive task protocol gate failed",
|
|
|
)
|
|
|
break
|
|
|
@@ -4187,28 +4237,34 @@ class AgentRunner:
|
|
|
candidate_output=response_content,
|
|
|
root_validator=True,
|
|
|
)
|
|
|
- refreshed_trace = await self.trace_store.get_trace(trace_id)
|
|
|
- if not refreshed_trace:
|
|
|
- raise ValueError(f"Trace not found after root validation: {trace_id}")
|
|
|
- trace = refreshed_trace
|
|
|
- state = ensure_task_protocol(trace.context)
|
|
|
- validation_cache = state.get("task_report_validation") or {}
|
|
|
- validation_record = {
|
|
|
- **validation_run.result.model_dump(),
|
|
|
- "validation_plan": validation_cache.get("validation_plan"),
|
|
|
- "validated_at_sequence": head_seq,
|
|
|
- }
|
|
|
- state["root_validation_history"].append(validation_record)
|
|
|
- state["root_validation_attempts"] += 1
|
|
|
- state["root_validation_passed"] = (
|
|
|
- validation_run.result.outcome == "passed"
|
|
|
- )
|
|
|
- await self.trace_store.update_trace(
|
|
|
- trace_id,
|
|
|
- context=trace.context,
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def record_root_validation(fresh, state):
|
|
|
+ validation_cache = state.get("task_report_validation") or {}
|
|
|
+ validation_record = {
|
|
|
+ **validation_run.result.model_dump(),
|
|
|
+ "validation_plan": validation_cache.get("validation_plan"),
|
|
|
+ "validated_at_sequence": head_seq,
|
|
|
+ }
|
|
|
+ state["root_validation_history"].append(validation_record)
|
|
|
+ state["root_validation_attempts"] += 1
|
|
|
+ state["root_validation_passed"] = (
|
|
|
+ validation_run.result.outcome == "passed"
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ state["root_validation_passed"],
|
|
|
+ state["root_validation_attempts"],
|
|
|
+ fresh,
|
|
|
+ )
|
|
|
+
|
|
|
+ root_passed, root_attempts, trace = (
|
|
|
+ await self.task_protocol_service.mutate_state(
|
|
|
+ trace_id,
|
|
|
+ record_root_validation,
|
|
|
+ )
|
|
|
)
|
|
|
- if not state["root_validation_passed"]:
|
|
|
- if state["root_validation_attempts"] < 2:
|
|
|
+ if not root_passed:
|
|
|
+ if root_attempts < 2:
|
|
|
validation_feedback = {
|
|
|
"role": "user",
|
|
|
"content": (
|
|
|
@@ -4269,30 +4325,50 @@ class AgentRunner:
|
|
|
trace = fresh_trace
|
|
|
policy = policy_from_context(trace.context)
|
|
|
if policy.requires_task_protocol:
|
|
|
- state = ensure_task_protocol(trace.context)
|
|
|
- if trace.parent_trace_id and state.get("task_report") is None:
|
|
|
- report = protocol_error_report(
|
|
|
- trace_id,
|
|
|
- "Agent loop ended without a valid TaskReport",
|
|
|
- )
|
|
|
- state["task_report"] = report.model_dump()
|
|
|
- state["task_report_submitted_at_sequence"] = sequence
|
|
|
- state["task_report_progress_revision"] = state.get(
|
|
|
- "task_progress_head_revision"
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+
|
|
|
+ def close_incomplete_protocol(fresh, state):
|
|
|
+ missing_report = bool(
|
|
|
+ fresh.parent_trace_id
|
|
|
+ and state.get("task_report") is None
|
|
|
)
|
|
|
+ if missing_report:
|
|
|
+ report = protocol_error_report(
|
|
|
+ trace_id,
|
|
|
+ "Agent loop ended without a valid TaskReport",
|
|
|
+ )
|
|
|
+ state["task_report"] = report.model_dump()
|
|
|
+ state["task_report_submitted_at_sequence"] = sequence
|
|
|
+ state["task_report_progress_revision"] = state.get(
|
|
|
+ "task_progress_head_revision"
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ "missing_report": missing_report,
|
|
|
+ "pending_reviews": bool(state["pending_reviews"]),
|
|
|
+ "pending_actions": bool(state["next_actions"]),
|
|
|
+ "root_validation_passed": bool(
|
|
|
+ state.get("root_validation_passed")
|
|
|
+ ),
|
|
|
+ "is_root": not fresh.parent_trace_id,
|
|
|
+ }
|
|
|
+
|
|
|
+ closed = await self.task_protocol_service.mutate_state(
|
|
|
+ trace_id,
|
|
|
+ close_incomplete_protocol,
|
|
|
+ )
|
|
|
+ if closed["missing_report"]:
|
|
|
completion_status = "failed"
|
|
|
await self.trace_store.update_trace(
|
|
|
trace_id,
|
|
|
- context=trace.context,
|
|
|
error_message="Recursive task protocol gate failed",
|
|
|
)
|
|
|
- if state["pending_reviews"]:
|
|
|
+ if closed["pending_reviews"]:
|
|
|
completion_status = "failed"
|
|
|
- if state["next_actions"]:
|
|
|
+ if closed["pending_actions"]:
|
|
|
completion_status = "failed"
|
|
|
if (
|
|
|
- not trace.parent_trace_id
|
|
|
- and not state.get("root_validation_passed")
|
|
|
+ closed["is_root"]
|
|
|
+ and not closed["root_validation_passed"]
|
|
|
):
|
|
|
completion_status = "failed"
|
|
|
|
|
|
@@ -4429,7 +4505,47 @@ class AgentRunner:
|
|
|
goal_tree.current_id = new_tree.current_id
|
|
|
|
|
|
trace = await self.trace_store.get_trace(trace_id)
|
|
|
+ projected_statuses: dict[str, str] = {}
|
|
|
if trace and policy_from_context(trace.context).requires_task_protocol:
|
|
|
+ projected_statuses = await self._rewind_protocol_state(
|
|
|
+ trace_id,
|
|
|
+ cutoff,
|
|
|
+ rewind_operation_id,
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ await self.trace_store.update_trace(trace_id, head_sequence=cutoff)
|
|
|
+ if goal_tree and projected_statuses:
|
|
|
+ for goal_id, status in projected_statuses.items():
|
|
|
+ goal = goal_tree.find(goal_id)
|
|
|
+ if goal:
|
|
|
+ goal.status = status
|
|
|
+ await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
+
|
|
|
+ # 4. 协议状态与 head_sequence 已在同一临界区落盘
|
|
|
+ await self.trace_store.append_event(trace_id, "rewind", rewind_payload)
|
|
|
+ if self.event_service is not None:
|
|
|
+ await self.event_service.emit_after_commit(
|
|
|
+ source_trace_id=trace_id,
|
|
|
+ event_type="run.rewound",
|
|
|
+ event_key=f"run.rewound:{rewind_operation_id}",
|
|
|
+ effective_at_sequence=cutoff,
|
|
|
+ payload={"after_sequence": cutoff},
|
|
|
+ )
|
|
|
+
|
|
|
+ # 5. 返回 next sequence(全局递增,不复用)
|
|
|
+ max_seq = max((m.sequence for m in all_messages), default=0)
|
|
|
+ return max_seq + 1
|
|
|
+
|
|
|
+ async def _rewind_protocol_state(
|
|
|
+ self,
|
|
|
+ trace_id: str,
|
|
|
+ cutoff: int,
|
|
|
+ rewind_operation_id: str,
|
|
|
+ ) -> dict[str, str]:
|
|
|
+ """Rebuild protocol state and move the Trace head in one lock domain."""
|
|
|
+ assert self.trace_store is not None
|
|
|
+ assert self.task_protocol_service is not None
|
|
|
+ async with self.task_protocol_service.locked_trace(trace_id) as trace:
|
|
|
state = ensure_task_protocol(trace.context)
|
|
|
submitted_at = state.get("task_report_submitted_at_sequence")
|
|
|
if isinstance(submitted_at, int) and submitted_at > cutoff:
|
|
|
@@ -4452,7 +4568,8 @@ class AgentRunner:
|
|
|
if entry.get("received_at_sequence", 0) <= cutoff
|
|
|
}
|
|
|
reverted_reviews = [
|
|
|
- review for review in state["reviews"]
|
|
|
+ review
|
|
|
+ for review in state["reviews"]
|
|
|
if review.get("reviewed_at_sequence", 0) > cutoff
|
|
|
]
|
|
|
for review in reverted_reviews:
|
|
|
@@ -4465,11 +4582,13 @@ class AgentRunner:
|
|
|
):
|
|
|
state["pending_reviews"][child_id] = entry
|
|
|
state["reviews"] = [
|
|
|
- review for review in state["reviews"]
|
|
|
+ review
|
|
|
+ for review in state["reviews"]
|
|
|
if review.get("reviewed_at_sequence", 0) <= cutoff
|
|
|
]
|
|
|
state["next_actions"] = [
|
|
|
- action for action in state["next_actions"]
|
|
|
+ action
|
|
|
+ for action in state["next_actions"]
|
|
|
if action.get("created_at_sequence", 0) <= cutoff
|
|
|
]
|
|
|
while (
|
|
|
@@ -4496,7 +4615,8 @@ class AgentRunner:
|
|
|
item.get("evaluated_at_sequence", 0),
|
|
|
)
|
|
|
or 0
|
|
|
- ) <= cutoff
|
|
|
+ )
|
|
|
+ <= cutoff
|
|
|
]
|
|
|
state["root_validation_history"] = root_history
|
|
|
state["root_validation_attempts"] = len(root_history)
|
|
|
@@ -4518,8 +4638,11 @@ class AgentRunner:
|
|
|
"payload": {"after_sequence": cutoff},
|
|
|
})
|
|
|
del operations[:-64]
|
|
|
- await self.trace_store.update_trace(trace_id, context=trace.context)
|
|
|
-
|
|
|
+ await self.trace_store.update_trace(
|
|
|
+ trace_id,
|
|
|
+ context=trace.context,
|
|
|
+ head_sequence=cutoff,
|
|
|
+ )
|
|
|
projected_statuses: dict[str, str] = {}
|
|
|
for entry in state["pending_reviews"].values():
|
|
|
if entry.get("goal_id"):
|
|
|
@@ -4528,28 +4651,7 @@ class AgentRunner:
|
|
|
goal_id = action.get("goal_id")
|
|
|
if goal_id and goal_id not in projected_statuses:
|
|
|
projected_statuses[goal_id] = "in_progress"
|
|
|
- if goal_tree and projected_statuses:
|
|
|
- for goal_id, status in projected_statuses.items():
|
|
|
- goal = goal_tree.find(goal_id)
|
|
|
- if goal:
|
|
|
- goal.status = status
|
|
|
- await self.trace_store.update_goal_tree(trace_id, goal_tree)
|
|
|
-
|
|
|
- # 4. 更新 head_sequence 到 rewind 点
|
|
|
- await self.trace_store.update_trace(trace_id, head_sequence=cutoff)
|
|
|
- await self.trace_store.append_event(trace_id, "rewind", rewind_payload)
|
|
|
- if self.event_service is not None:
|
|
|
- await self.event_service.emit_after_commit(
|
|
|
- source_trace_id=trace_id,
|
|
|
- event_type="run.rewound",
|
|
|
- event_key=f"run.rewound:{rewind_operation_id}",
|
|
|
- effective_at_sequence=cutoff,
|
|
|
- payload={"after_sequence": cutoff},
|
|
|
- )
|
|
|
-
|
|
|
- # 5. 返回 next sequence(全局递增,不复用)
|
|
|
- max_seq = max((m.sequence for m in all_messages), default=0)
|
|
|
- return max_seq + 1
|
|
|
+ return projected_statuses
|
|
|
|
|
|
def _find_safe_cutoff(self, messages: List[Message], after_sequence: int) -> int:
|
|
|
"""
|
|
|
@@ -5621,6 +5723,7 @@ class AgentRunner:
|
|
|
goal_id: Optional[str],
|
|
|
goal_tree: Optional[GoalTree],
|
|
|
sequence: int,
|
|
|
+ tool_call_id: str | None,
|
|
|
side_branch_ctx: Optional[SideBranchContext],
|
|
|
trigger_event: Optional[str],
|
|
|
) -> Dict[str, Any]:
|
|
|
@@ -5649,6 +5752,7 @@ class AgentRunner:
|
|
|
else None
|
|
|
),
|
|
|
"sequence": sequence,
|
|
|
+ "tool_call_id": tool_call_id,
|
|
|
"side_branch": {
|
|
|
"type": side_branch_ctx.type,
|
|
|
"branch_id": side_branch_ctx.branch_id,
|