task_protocol_service.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """TaskProtocol 命令的单进程原子执行边界。"""
  2. from __future__ import annotations
  3. import asyncio
  4. from copy import deepcopy
  5. from weakref import WeakValueDictionary
  6. from cyber_agent.core.agent_mode import require_mutable_trace_policy
  7. from cyber_agent.core.context_policy import get_authorized_context_snapshot
  8. from cyber_agent.core.task_protocol import (
  9. TaskProgress,
  10. TaskProgressRevision,
  11. append_task_progress_revision,
  12. ensure_task_protocol,
  13. task_contract_ref,
  14. )
  15. from cyber_agent.trace.protocols import TraceStore
  16. _TRACE_LOCKS: WeakValueDictionary[
  17. tuple[asyncio.AbstractEventLoop, str], asyncio.Lock
  18. ] = WeakValueDictionary()
  19. class TaskProtocolStateError(RuntimeError):
  20. """TaskProtocol 命令不能在当前持久化状态执行。"""
  21. class TaskProtocolService:
  22. """将协议领域纯函数与 TraceStore 持久化组合起来。"""
  23. def __init__(self, trace_store: TraceStore) -> None:
  24. self.trace_store = trace_store
  25. def _lock(self, trace_id: str) -> asyncio.Lock:
  26. key = (asyncio.get_running_loop(), trace_id)
  27. lock = _TRACE_LOCKS.get(key)
  28. if lock is None:
  29. lock = asyncio.Lock()
  30. _TRACE_LOCKS[key] = lock
  31. return lock
  32. async def update_progress(
  33. self,
  34. trace_id: str,
  35. *,
  36. expected_revision: int,
  37. progress: TaskProgress | dict,
  38. effective_at_sequence: int,
  39. ) -> TaskProgressRevision:
  40. async with self._lock(trace_id):
  41. trace = await self.trace_store.get_trace(trace_id)
  42. if trace is None:
  43. raise TaskProtocolStateError(f"Trace not found: {trace_id}")
  44. policy = require_mutable_trace_policy(trace.context)
  45. if not policy.requires_task_progress:
  46. raise TaskProtocolStateError(
  47. "update_task_progress requires Recursive revision 3"
  48. )
  49. if (
  50. trace.agent_type == "validator"
  51. or trace.context.get("created_by_tool") == "validator"
  52. ):
  53. raise TaskProtocolStateError(
  54. "Validator traces cannot update TaskProgress"
  55. )
  56. parsed = TaskProgress.model_validate(progress)
  57. root_trace_id = trace.context.get("root_trace_id") or trace.trace_id
  58. for item in (
  59. *parsed.questions,
  60. *parsed.blockers,
  61. *parsed.findings,
  62. *parsed.hypotheses,
  63. *parsed.work_items,
  64. ):
  65. for ref in item.context_refs:
  66. get_authorized_context_snapshot(
  67. trace.context,
  68. ref_id=ref.ref_id,
  69. version=ref.version,
  70. root_trace_id=root_trace_id,
  71. uid=trace.uid,
  72. )
  73. next_context = deepcopy(trace.context)
  74. state = ensure_task_protocol(next_context)
  75. if state["pending_reviews"]:
  76. raise TaskProtocolStateError(
  77. "Review all child TaskReports before updating TaskProgress"
  78. )
  79. if state["next_actions"]:
  80. raise TaskProtocolStateError(
  81. "Execute every approved next action before updating TaskProgress"
  82. )
  83. if state["task_report"] is not None:
  84. raise TaskProtocolStateError(
  85. "TaskProgress cannot change after TaskReport submission"
  86. )
  87. revision = append_task_progress_revision(
  88. state,
  89. parsed,
  90. expected_revision=expected_revision,
  91. effective_at_sequence=effective_at_sequence,
  92. contract_ref=task_contract_ref(
  93. state,
  94. root_task_anchor_hash=next_context.get(
  95. "root_task_anchor_hash"
  96. ),
  97. ),
  98. )
  99. await self.trace_store.update_trace(trace_id, context=next_context)
  100. return revision