feat(my-deepagent): v0.2 PR #2b — mydeepagent runs resume <id> real implementation
Closes the v0.1.0 KNOWN LIMIT where resume was an exit-2 stub. Builds on
v0.2 PR #2a's LangGraph wiring + the existing DB phase-state machine +
sweep_orphan_runs — no Temporal (per DR-3).
Highlights
- `WorkflowEngine.resume(run_id)` (new async method):
- Loads RunRow, rejects terminal states with
MyDeepAgentError("run_already_terminal").
- Reloads worktree_root from `RunRow.worktree_root`, template via
`_reload_template` (WorkflowTemplateRow JOIN + model_validate), and
bindings via `_reload_bindings` (run_bindings ⨝ agent_personas).
- **Does NOT call `bind_personas` again** — locks in the original
binding so consent / persona-pool changes since the original run
don't silently shift role assignment.
- `_execute_run` (extracted shared phase loop): `run()` and `resume()`
both dispatch through it. Skips already-completed phases (emits
`phase.skipped` event) and re-executes the rest.
- 4 new private helpers on WorkflowEngine: `_get_run_or_raise`,
`_reload_template`, `_reload_bindings`, `_get_completed_phase_keys`.
- `RunEventType.RUN_RESUMED` and `PHASE_SKIPPED` are now actually
emitted (the enum members existed already).
- `cli/runs.py _runs_resume_async`: stub → real impl. Validates the run
exists + non-terminal, loads seed personas + artifact schemas from
`docs/schemas/`, constructs WorkflowEngine with an
"abort-on-new-approval" callback (resume should not silently re-prompt
the user — original gates already passed; a new gate means the
workflow has changed). Calls engine.resume(UUID(id)), prints final
state + report. Catches MyDeepAgentError and exits 1 with red error.
Tests
- `tests/integration/test_resume.py` (new, 5 scenarios):
1. 2-phase mock workflow: phase 1 succeeds, phase 2 fails first time,
row flipped back to executing → resume → phase 2 completes.
Asserts `phase.skipped` event for phase 1, `run.resumed` event,
and exactly 1 mock invocation for phase 2 on resume.
2. Terminal run → `MyDeepAgentError(code="run_already_terminal")`.
3. Unknown run id → `MyDeepAgentError(code="run_not_found")`.
4. RunBindingRow rows missing → `MyDeepAgentError(code="run_metadata_missing")`.
5. Corrupt `workflow_templates.definition` →
`MyDeepAgentError(code="template_load_failed")`.
Mock pattern matches existing test_engine.py: patch
`my_deepagent.engine.build_agent` to return a fake agent that writes
the expected artifact and drives the watcher middleware.
Gates
- ruff check + ruff format --check + mypy --strict: PASS (103 source files)
- pytest non-E2E: 587 PASS (12.69 s) — +5 from new resume tests
- pytest E2E real OpenRouter on Postgres: PASS 78.52 s (baseline 71–122 s;
within DR-3 acceptance threshold ≤+20%)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,45 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **v0.2 PR #2b — `mydeepagent runs resume <id>` real implementation**.
|
||||
Closes the v0.1.0 KNOWN LIMIT where resume was an exit-2 stub. Reuses
|
||||
v0.2 PR #2a's LangGraph wiring + sweep_orphan_runs's DB state machine,
|
||||
no Temporal (DR-3).
|
||||
- `src/my_deepagent/engine.py`:
|
||||
- New `WorkflowEngine.resume(run_id)` async method. Loads `RunRow`,
|
||||
rejects terminal states with `MyDeepAgentError.human_required("run_already_terminal")`,
|
||||
reloads `worktree_root` + `WorkflowTemplate` (via `_reload_template`) +
|
||||
bindings (via `_reload_bindings`) from DB. Does **not** call
|
||||
`bind_personas` again — locks in the original binding so consent /
|
||||
pool changes don't silently shift roles.
|
||||
- New `_execute_run` helper (shared phase loop) extracted from `run()`.
|
||||
Skips already-`completed` phases (emits `phase.skipped` event) and
|
||||
re-executes the rest. Both `run` (new) and `resume` dispatch through
|
||||
it.
|
||||
- New helpers: `_get_run_or_raise`, `_reload_template`,
|
||||
`_reload_bindings` (rebuilds `{role_id: Binding}` from
|
||||
`run_bindings` ⨝ `agent_personas`; corrupt persona rows are logged
|
||||
and skipped, surfacing as `run_metadata_missing` if no bindings remain),
|
||||
`_get_completed_phase_keys`.
|
||||
- New `RunEventType.RUN_RESUMED` and `RunEventType.PHASE_SKIPPED` are
|
||||
now actually emitted (the enum members existed already from v0.1.0).
|
||||
- `src/my_deepagent/cli/runs.py` `_runs_resume_async`: stub → real impl.
|
||||
Validates run exists + non-terminal, loads seed personas + artifact
|
||||
schemas (`docs/schemas/`), constructs `WorkflowEngine` with a
|
||||
"abort-on-new-approval" callback, calls `engine.resume(UUID(id))`,
|
||||
prints final state + report path. Catches `MyDeepAgentError` and prints
|
||||
a red error with exit 1.
|
||||
- `tests/integration/test_resume.py` (new, 5 scenarios):
|
||||
1. 2-phase workflow: phase 1 succeeds, phase 2 fails → flip run row
|
||||
back to executing → resume → phase 2 completes; assert phase 1 was
|
||||
skipped (`phase.skipped` event present) and `run.resumed` event emitted.
|
||||
2. Terminal run → `resume()` raises `MyDeepAgentError(code="run_already_terminal")`.
|
||||
3. Unknown run id → raises `MyDeepAgentError(code="run_not_found")`.
|
||||
4. RunBindingRow rows missing → raises `MyDeepAgentError(code="run_metadata_missing")`.
|
||||
5. workflow_templates.definition is malformed → raises `MyDeepAgentError(code="template_load_failed")`.
|
||||
- E2E real OpenRouter regression PASS 78.52 s (baseline 71–122 s);
|
||||
within DR-3 acceptance threshold (+20%).
|
||||
|
||||
- **v0.2 PR #2a — LangGraph `AsyncPostgresSaver` engine wiring** (foundation
|
||||
for `runs resume`). v0.2 PR #1 added the dependency; this commit actually
|
||||
uses it.
|
||||
|
||||
Reference in New Issue
Block a user