Track workflow ledger through clarify and recovery
- SHA
383a8c16f07487d25cb080c363aa5a8a152dab77- Parents
-
e84c07c - Tree
ae0d16b
383a8c1
383a8c16f07487d25cb080c363aa5a8a152dab77e84c07c
ae0d16b| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/runtime/workflow_lanes.py
|
18 | 0 |
| M |
src/loader/runtime/workflow_recovery.py
|
9 | 0 |
| M |
tests/test_workflow_runtime.py
|
8 | 0 |
src/loader/runtime/workflow_lanes.pymodified@@ -38,6 +38,10 @@ from .workflow import ( | ||
| 38 | 38 | enrich_clarify_brief_with_grounding, |
| 39 | 39 | sync_todos_to_definition_of_done, |
| 40 | 40 | ) |
| 41 | +from .workflow_ledger import ( | |
| 42 | + seed_workflow_ledger_from_acceptance_criteria, | |
| 43 | + seed_workflow_ledger_from_brief, | |
| 44 | +) | |
| 41 | 45 | |
| 42 | 46 | EventSink = Callable[[AgentEvent], Awaitable[None]] |
| 43 | 47 | UserQuestionHandler = Callable[[str, list[str] | None], Awaitable[str]] | None |
@@ -132,6 +136,13 @@ class WorkflowLaneRunner: | ||
| 132 | 136 | dod.clarify_brief = str(brief_path) |
| 133 | 137 | dod.acceptance_criteria = list(dict.fromkeys(latest_brief.acceptance_criteria)) |
| 134 | 138 | self.dod_store.save(dod) |
| 139 | + self.agent.session.update_workflow_ledger( | |
| 140 | + seed_workflow_ledger_from_brief( | |
| 141 | + self.agent.session.workflow_ledger, | |
| 142 | + latest_brief, | |
| 143 | + phase="clarify", | |
| 144 | + ) | |
| 145 | + ) | |
| 135 | 146 | append_timeline( |
| 136 | 147 | ModeDecision.transition( |
| 137 | 148 | WorkflowMode.CLARIFY, |
@@ -202,6 +213,13 @@ class WorkflowLaneRunner: | ||
| 202 | 213 | if artifacts.verification_commands: |
| 203 | 214 | dod.verification_commands = artifacts.verification_commands |
| 204 | 215 | self.dod_store.save(dod) |
| 216 | + self.agent.session.update_workflow_ledger( | |
| 217 | + seed_workflow_ledger_from_acceptance_criteria( | |
| 218 | + self.agent.session.workflow_ledger, | |
| 219 | + list(dod.acceptance_criteria), | |
| 220 | + phase="plan", | |
| 221 | + ) | |
| 222 | + ) | |
| 205 | 223 | await self._emit_artifact( |
| 206 | 224 | emit=emit, |
| 207 | 225 | kind="implementation_plan", |
src/loader/runtime/workflow_recovery.pymodified@@ -21,6 +21,7 @@ from .workflow import ( | ||
| 21 | 21 | WorkflowSignalExtractor, |
| 22 | 22 | ) |
| 23 | 23 | from .workflow_lanes import WorkflowLaneRunner |
| 24 | +from .workflow_ledger import apply_freshness_to_workflow_ledger | |
| 24 | 25 | |
| 25 | 26 | EventSink = Callable[[AgentEvent], Awaitable[None]] |
| 26 | 27 | UserQuestionHandler = Callable[[str, list[str] | None], Awaitable[str]] | None |
@@ -77,6 +78,14 @@ class WorkflowRecoveryController: | ||
| 77 | 78 | if not freshness.requires_refresh: |
| 78 | 79 | return False |
| 79 | 80 | |
| 81 | + self.agent.session.update_workflow_ledger( | |
| 82 | + apply_freshness_to_workflow_ledger( | |
| 83 | + self.agent.session.workflow_ledger, | |
| 84 | + freshness, | |
| 85 | + phase="recovery", | |
| 86 | + ) | |
| 87 | + ) | |
| 88 | + | |
| 80 | 89 | strategy = WorkflowRecoveryStrategy(freshness.recovery_strategy) |
| 81 | 90 | if strategy == WorkflowRecoveryStrategy.PLAN_REFRESH: |
| 82 | 91 | return await self._run_plan_refresh_reentry( |
tests/test_workflow_runtime.pymodified@@ -1541,3 +1541,11 @@ async def test_full_replan_can_reenter_clarify_before_rebuilding_plan( | ||
| 1541 | 1541 | entry.reason_code == "full_replan_required" |
| 1542 | 1542 | for entry in run.agent.last_turn_summary.workflow_timeline |
| 1543 | 1543 | ) |
| 1544 | + assert any( | |
| 1545 | + item.status == "contradicted" | |
| 1546 | + for item in run.agent.session.workflow_ledger.assumptions | |
| 1547 | + ) | |
| 1548 | + assert any( | |
| 1549 | + item.status == "changed" | |
| 1550 | + for item in run.agent.session.workflow_ledger.acceptance_anchors | |
| 1551 | + ) | |