test_task_planning_v2.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from __future__ import annotations
  2. from types import SimpleNamespace
  3. import pytest
  4. from agent.orchestration import TaskLedger, TaskRecord, TaskSpec
  5. from agent.orchestration.models import AcceptanceCriterion
  6. from script_build_host.application.task_planning_v2 import TaskPlanningServiceV2
  7. from script_build_host.tools.registry import _planner_task_input_schema
  8. class Store:
  9. def __init__(self, ledger: TaskLedger) -> None:
  10. self.ledger = ledger
  11. async def load(self, root_trace_id: str) -> TaskLedger:
  12. assert root_trace_id == self.ledger.root_trace_id
  13. return self.ledger
  14. class Coordinator:
  15. def __init__(self, ledger: TaskLedger) -> None:
  16. self.task_store = Store(ledger)
  17. self.call = None
  18. async def replay_command(self, *_args, **_kwargs):
  19. return None
  20. async def create_task_graph(self, root_trace_id: str, drafts, **kwargs):
  21. self.call = (root_trace_id, drafts, kwargs)
  22. for document in kwargs["context_documents"]:
  23. self.task_store.ledger.context_documents[document.uri] = document
  24. for draft in drafts:
  25. task_id = draft["_task_id"]
  26. self.task_store.ledger.tasks[task_id] = TaskRecord(
  27. task_id,
  28. draft["_parent_task_id"],
  29. "1.1",
  30. [
  31. TaskSpec(
  32. 1,
  33. draft["objective"],
  34. tuple(
  35. AcceptanceCriterion(
  36. item["criterion_id"],
  37. item["description"],
  38. item["hard"],
  39. )
  40. for item in draft["acceptance_criteria"]
  41. ),
  42. tuple(draft["context_refs"]),
  43. )
  44. ],
  45. )
  46. return {"tasks": [{"task_id": drafts[0]["_task_id"]}]}
  47. async def get_tasks(self, _root_trace_id: str, task_ids):
  48. return {"tasks": [{"task_id": item} for item in task_ids]}
  49. @pytest.mark.asyncio
  50. async def test_v2_planning_passes_contract_documents_in_same_task_mutation() -> None:
  51. root = TaskRecord(
  52. "root-task",
  53. None,
  54. "1",
  55. [
  56. TaskSpec(
  57. 1,
  58. "mission",
  59. (AcceptanceCriterion("done", "done"),),
  60. )
  61. ],
  62. )
  63. ledger = TaskLedger("root", "mission", "root-task", tasks={"root-task": root})
  64. coordinator = Coordinator(ledger)
  65. service = TaskPlanningServiceV2(
  66. coordinator=coordinator,
  67. bindings=SimpleNamespace(
  68. get_by_root=lambda _: None,
  69. ),
  70. artifacts=SimpleNamespace(),
  71. input_snapshots=SimpleNamespace(),
  72. )
  73. async def binding(_: str):
  74. return SimpleNamespace(
  75. script_build_id=1,
  76. input_snapshot_id="snapshot",
  77. active_direction_artifact_version_id=None,
  78. )
  79. service.bindings.get_by_root = binding
  80. async def input_snapshot(*_args, **_kwargs):
  81. return SimpleNamespace(
  82. snapshot_id="snapshot",
  83. account={},
  84. persona_points=(),
  85. section_patterns=({"pattern": "opening"},),
  86. )
  87. service.input_snapshots.get = input_snapshot
  88. result = await service.plan(
  89. intents=[{"task_kind": "direction", "objective": "choose direction"}],
  90. context={"root_trace_id": "root", "tool_call_id": "call-1"},
  91. )
  92. assert result["task_ids"]
  93. assert coordinator.call is not None
  94. _, drafts, kwargs = coordinator.call
  95. documents = kwargs["context_documents"]
  96. assert len(documents) == len(drafts) == 1
  97. assert documents[0].uri in drafts[0]["context_refs"]
  98. assert documents[0].payload["schema_version"] == "script-task-contract/v2"
  99. def test_planner_schema_exposes_business_intent_only() -> None:
  100. properties = _planner_task_input_schema()["properties"]
  101. assert "decision_ids" in properties
  102. assert "scope_selector" not in properties
  103. assert "criteria" not in properties
  104. assert "parent_task_id" not in properties