Implement LangGraph suspend-and-resume orchestration (2.4.2) - #77
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughPersist planner state before side effects, return a SuspendedWorkflowResult, provide in-memory and SQLAlchemy checkpoint adapters with idempotent save_or_reuse/mark_resumed semantics, wire suspend-before-execute and resume entrypoints into LangGraph, add Alembic migration and unit-of-work wiring, and cover behaviour with unit, property, snapshot, BDD and SQLAlchemy tests plus ADR/ExecPlan and guide updates. ChangesDurable Suspend-and-Resume Orchestration
Possibly related issues
Poem
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (2 errors, 6 warnings)
✅ Passed checks (10 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. tests/test_orchestration_properties.py Comment on lines +121 to +144 def test_step_idempotency_keys_are_deterministic(
workflow_id: str,
workflow_type: str,
step_name: str,
action_id: str,
attempt: int,
) -> None:
"""Property test: identical workflow step inputs produce identical keys."""
first = build_workflow_step_idempotency_key(
workflow_id=workflow_id,
workflow_type=workflow_type,
step_name=step_name,
action_id=action_id,
attempt=attempt,
)
second = build_workflow_step_idempotency_key(
workflow_id=workflow_id,
workflow_type=workflow_type,
step_name=step_name,
action_id=action_id,
attempt=attempt,
)
assert second == first
assert first.endswith(f":{attempt}")❌ New issue: Excess Number of Function Arguments |
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. episodic/orchestration/_dto.py Comment on lines +474 to +493 def build_workflow_step_idempotency_key( # noqa: PLR0913
*,
workflow_id: str,
workflow_type: str,
step_name: str,
action_id: str,
attempt: int = 0,
) -> str:
"""Build the deterministic idempotency key for a suspendable workflow step."""
if attempt < 0:
msg = "attempt must be greater than or equal to zero."
raise ValueError(msg)
parts = (
_normalize_non_empty_text(workflow_id, "workflow_id"),
_normalize_non_empty_text(workflow_type, "workflow_type"),
_normalize_non_empty_text(step_name, "step_name"),
_normalize_non_empty_text(action_id, "action_id"),
str(attempt),
)
return ":".join(parts)❌ New issue: Excess Number of Function Arguments |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
f8c1ad3 to
2c1e867
Compare
Reviewer's GuideImplements durable suspend-and-resume orchestration for structured generation by introducing checkpoint DTOs and ports, a LangGraph path that suspends before execution and resumes from persisted checkpoints, plus in-memory and SQLAlchemy-backed checkpoint stores, tests, and documentation/roadmap updates. Sequence diagram for suspend-before-execute generation orchestrationsequenceDiagram
actor Client
participant Orchestrator as StructuredPlanningOrchestrator
participant LangGraph as GenerationGraph
participant Planner as PlannerPort
participant Checkpoints as CheckpointPort
Client->>Orchestrator: start_generation(request)
Orchestrator->>LangGraph: run(request, checkpoint_port)
LangGraph->>Planner: plan(request)
Planner-->>LangGraph: PlannerResult
LangGraph->>Checkpoints: get_by_idempotency_key(step_idempotency_key)
Checkpoints-->>LangGraph: WorkflowCheckpoint | None
alt checkpoint does not exist
LangGraph->>Checkpoints: save(WorkflowCheckpoint)
Checkpoints-->>LangGraph: WorkflowCheckpoint
end
LangGraph-->>Orchestrator: SuspendedWorkflowResult
Orchestrator-->>Client: SuspendedWorkflowResult
Sequence diagram for resuming a suspended generation workflowsequenceDiagram
actor Client
participant Orchestrator as GenerationOrchestrationLayer
participant Checkpoints as CheckpointPort
participant ResumePort as TaskResumePort
Client->>Orchestrator: resume_generation(ResumeWorkflowCommand)
Orchestrator->>Checkpoints: get(checkpoint_id)
Checkpoints-->>Orchestrator: WorkflowCheckpoint | None
alt checkpoint unknown
Orchestrator-->>Client: ValueError
else checkpoint found
Orchestrator->>ResumePort: resume(ResumeWorkflowCommand)
ResumePort-->>Orchestrator: ActionExecutionResult
Orchestrator->>Orchestrator: build_generation_result(PlannerResult, ActionExecutionResult)
Orchestrator-->>Client: GenerationOrchestrationResult
end
ER diagram for workflow_checkpoints tableerDiagram
WORKFLOW_CHECKPOINTS {
uuid id PK
string workflow_id
string workflow_type
string step_name
string idempotency_key
jsonb payload
string status
datetime created_at
datetime updated_at
}
Class diagram for orchestration checkpoint DTOs, ports, and adaptersclassDiagram
class WorkflowCheckpoint {
+str checkpoint_id
+str workflow_id
+str workflow_type
+str step_name
+str idempotency_key
+dict~str, object~ payload
+str status
+datetime created_at
+datetime updated_at
+__post_init__() void
}
class SuspendedWorkflowResult {
+str checkpoint_id
+str workflow_id
+str step_name
+str idempotency_key
+__post_init__() void
}
class ResumeWorkflowCommand {
+str checkpoint_id
+ActionExecutionResult result
+__post_init__() void
}
class WorkflowStepIdentity {
+str workflow_id
+str workflow_type
+str step_name
+str action_id
+__post_init__() void
}
class GenerationGraphState {
+GenerationOrchestrationRequest request
+PlannerResult planner_result
+tuple~ActionExecutionResult~ action_results
+GenerationOrchestrationResult orchestration_result
+SuspendedWorkflowResult suspended_result
}
class CheckpointPort {
<<protocol>>
+get(checkpoint_id str) WorkflowCheckpoint
+get_by_idempotency_key(idempotency_key str) WorkflowCheckpoint
+save(checkpoint WorkflowCheckpoint) WorkflowCheckpoint
}
class TaskResumePort {
<<protocol>>
+resume(command ResumeWorkflowCommand) ActionExecutionResult
}
class InMemoryCheckpointStore {
+dict~str, WorkflowCheckpoint~ _by_id
+dict~str, str~ _by_key
+get(checkpoint_id str) WorkflowCheckpoint
+get_by_idempotency_key(idempotency_key str) WorkflowCheckpoint
+save(checkpoint WorkflowCheckpoint) WorkflowCheckpoint
}
class SqlAlchemyWorkflowCheckpointStore {
-AsyncSession _session
+get(checkpoint_id str) WorkflowCheckpoint
+get_by_idempotency_key(idempotency_key str) WorkflowCheckpoint
+save(checkpoint WorkflowCheckpoint) WorkflowCheckpoint
}
class WorkflowCheckpointRecord {
+uuid id
+str workflow_id
+str workflow_type
+str step_name
+str idempotency_key
+dict~str, object~ payload
+str status
+datetime created_at
+datetime updated_at
}
class Functions {
+build_workflow_step_idempotency_key(step WorkflowStepIdentity, attempt int) str
+resume_generation_orchestration(checkpoint_port CheckpointPort, resume_port TaskResumePort, command ResumeWorkflowCommand) GenerationOrchestrationResult
}
CheckpointPort <|.. InMemoryCheckpointStore
CheckpointPort <|.. SqlAlchemyWorkflowCheckpointStore
InMemoryCheckpointStore o--> WorkflowCheckpoint
SqlAlchemyWorkflowCheckpointStore o--> WorkflowCheckpointRecord
GenerationGraphState o--> SuspendedWorkflowResult
GenerationGraphState o--> PlannerResult
GenerationGraphState o--> ActionExecutionResult
GenerationGraphState o--> GenerationOrchestrationResult
Functions ..> WorkflowStepIdentity
Functions ..> CheckpointPort
Functions ..> TaskResumePort
WorkflowCheckpointRecord --> WorkflowCheckpoint
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce9b04d236
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/developers-guide.md`:
- Around line 650-651: Update the documentation for
build_workflow_step_idempotency_key to state that it accepts a
WorkflowStepIdentity and an attempt count rather than five scalar arguments;
specifically, reword the sentence to explain that callers should construct a
WorkflowStepIdentity (containing workflow id, workflow type, step name, and
action id) and pass a separate attempt parameter for the retry count when
invoking build_workflow_step_idempotency_key.
In `@docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md`:
- Around line 525-530: The focused pytest command references the wrong steps
module name; change the test file in the command from
tests/steps/test_generation_suspend_resume_steps.py to
tests/steps/test_generation_orchestration_steps.py so the documented run loads
the implemented suspend/resume BDD steps; update the command string shown (the
PYO3... uv run pytest ... line) to use the correct test module name.
In `@episodic/orchestration/_dto.py`:
- Line 3: Remove the redundant future-import at the top of the module: delete
the line "from __future__ import annotations" from the top of episodic
orchestration DTO module (the module-level import in _dto.py) so the file no
longer contains that forbidden future import; no other changes are required.
In `@episodic/orchestration/langgraph.py`:
- Around line 346-360: The resume_generation_orchestration function currently
assumes resume_port.resume(command) returns a single ActionExecutionResult and
builds the final result with build_generation_result(planner_result,
(action_result,)); update the function docstring to explicitly state this
single-action-per-suspend assumption (and that future changes to allow multiple
actions must update this code path), referencing
resume_generation_orchestration, resume_port.resume, planner_result, and
build_generation_result so maintainers can find and adjust the logic if the
model changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 503c444e-13bd-4525-8551-2aa4d810828e
📒 Files selected for processing (22)
alembic/versions/20260508_000008_add_workflow_checkpoints.pydocs/adr/adr-006-durable-generation-checkpoints.mddocs/developers-guide.mddocs/episodic-podcast-generation-system-design.mddocs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.mddocs/roadmap.mddocs/users-guide.mdepisodic/canonical/storage/__init__.pyepisodic/canonical/storage/models.pyepisodic/canonical/storage/uow.pyepisodic/canonical/storage/workflow_checkpoints.pyepisodic/orchestration/__init__.pyepisodic/orchestration/_dto.pyepisodic/orchestration/_protocols.pyepisodic/orchestration/checkpoints.pyepisodic/orchestration/generation.pyepisodic/orchestration/langgraph.pytests/canonical_storage/test_workflow_checkpoints.pytests/features/generation_orchestration.featuretests/steps/test_generation_orchestration_steps.pytests/test_generation_orchestration_langgraph.pytests/test_orchestration_properties.py
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/adr/adr-006-durable-generation-checkpoints.md`:
- Line 37: Update the British spelling "serialises" to the Oxford -ize form
"serializes" in the sentence that reads "The in-memory adapter serialises
`save(...)` mutations with an `asyncio.Lock`" so the documentation matches the
project's en-GB-oxendict convention; search for the phrase containing
`save(...)` and `asyncio.Lock` and replace "serialises" with "serializes".
In `@docs/developers-guide.md`:
- Around line 625-626: Replace the phrase "independent from" with the correct
collocation "independent of" in the sentence containing "checkpoint, and resume
ports that keep graph policy independent from storage, queue, and provider
adapters." Locate that exact phrase ("independent from") in the
docs/developers-guide.md content and update it to "independent of" so the
sentence reads "...keep graph policy independent of storage, queue, and provider
adapters."
In `@docs/episodic-podcast-generation-system-design.md`:
- Around line 471-525: Add short screen-reader descriptions immediately before
each Mermaid sequenceDiagram block (the "Suspend flow" diagram that begins with
"Client->>Orchestrator: start_generation(request)" and the "Resume flow" diagram
that begins with "Client->>Orchestrator:
resume_generation(ResumeWorkflowCommand)"); insert a one- or two-sentence
plain-text line labeled like "Screen reader description:" summarizing the
diagram intent (e.g., checkpoint persistence before side-effecting execution for
the first, and checkpoint lookup/reject-or-resume for the second) so assistive
readers get context before the mermaid code blocks.
In `@docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md`:
- Line 681: The document uses the non-Oxford spelling "serialised" in the phrase
"serialised in-memory"; update every occurrence of "serialised" to the Oxford
spelling "serialized" (e.g., change "serialised in-memory checkpoint clock" to
"serialized in-memory checkpoint clock") to match the en-GB-oxendict `-ize`
convention; search for the token "serialised" in this file (and nearby lines)
and replace with "serialized" and run a quick spellcheck to ensure consistency.
In `@episodic/orchestration/langgraph.py`:
- Around line 401-402: The code reads payload = checkpoint.payload and then uses
payload["planner_result"] which can raise KeyError for malformed checkpoints;
update the block that calls _planner_result_from_payload to guard for a missing
"planner_result" key: check for "planner_result" in payload (or use
payload.get("planner_result")) and if missing raise a TypeError with a clear
message (or convert the KeyError to TypeError) so the function's exception
behavior matches its docstring; reference the checkpoint.payload access and the
call to _planner_result_from_payload when making the change.
- Around line 297-370: The _suspend_execute_node function is too long; extract
the checkpoint construction and persistence (the block that creates
dto.WorkflowCheckpoint and calls checkpoint_port.save when existing is None)
into a new helper like _create_and_save_checkpoint that accepts
(checkpoint_port, request, planner_result, workflow_id, workflow_type, action,
idempotency_key) and returns the saved checkpoint object; inside the helper
build the payload (using _planner_result_to_payload and request fields),
generate checkpoint_id via uuid.uuid4(), call
checkpoint_port.save(dto.WorkflowCheckpoint(...)) and return the result; then
replace the original inline block in _suspend_execute_node with a call to this
helper and preserve the existing/reused_checkpoint logic and logging; reference
dto.WorkflowCheckpoint, dto.build_workflow_step_idempotency_key,
checkpoint_port.save, and _planner_result_to_payload when implementing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 27990eef-a6fd-42fa-8c21-11a84816b9c3
📒 Files selected for processing (13)
docs/adr/adr-006-durable-generation-checkpoints.mddocs/developers-guide.mddocs/episodic-podcast-generation-system-design.mddocs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.mdepisodic/canonical/storage/workflow_checkpoints.pyepisodic/orchestration/_dto.pyepisodic/orchestration/_protocols.pyepisodic/orchestration/checkpoints.pyepisodic/orchestration/langgraph.pytests/__snapshots__/test_generation_orchestration_snapshots.ambrtests/test_generation_orchestration_langgraph.pytests/test_generation_orchestration_snapshots.pytests/test_orchestration_properties.py
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
episodic/orchestration/langgraph.py (1)
479-480:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winGuard against missing
planner_resultkey in checkpoint payload.Line 480 accesses
payload["planner_result"]directly. If the checkpoint payload is malformed, this raisesKeyError, but the docstring documents onlyTypeError. Either catchKeyErrorand raiseTypeError, or check for the key's presence first.🛡️ Proposed fix
payload = checkpoint.payload + if "planner_result" not in payload: + msg = "checkpoint payload missing required key: planner_result" + raise TypeError(msg) planner_result = _planner_result_from_payload(payload["planner_result"])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@episodic/orchestration/langgraph.py` around lines 479 - 480, The code accesses payload["planner_result"] without guarding for a missing key which can raise KeyError; update the checkpoint handling in the function that sets payload = checkpoint.payload to validate the key before use: either check 'planner_result' in payload and raise a TypeError with the documented message if absent, or wrap the call to _planner_result_from_payload(payload["planner_result"]) in a try/except catching KeyError and re-raising a TypeError; ensure you reference the existing symbols payload, planner_result, _planner_result_from_payload and checkpoint in your change so behavior and docstring remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@episodic/canonical/storage/workflow_checkpoints.py`:
- Line 11: Remove the unnecessary future import in the new module: delete the
line "from __future__ import annotations" from
episodic.canonical.storage.workflow_checkpoints (the module-level import at top
of file) so the file conforms to the Python ≥3.14 baseline and project coding
guidelines.
In `@tests/canonical_storage/test_workflow_checkpoints.py`:
- Line 3: Remove the unnecessary future import in the test module by deleting
the line "from __future__ import annotations" in
tests/canonical_storage/test_workflow_checkpoints.py; since the project targets
Python ≥3.14 and PEP 563 behavior is the default, simply remove that import
statement so the module no longer contains it.
---
Duplicate comments:
In `@episodic/orchestration/langgraph.py`:
- Around line 479-480: The code accesses payload["planner_result"] without
guarding for a missing key which can raise KeyError; update the checkpoint
handling in the function that sets payload = checkpoint.payload to validate the
key before use: either check 'planner_result' in payload and raise a TypeError
with the documented message if absent, or wrap the call to
_planner_result_from_payload(payload["planner_result"]) in a try/except catching
KeyError and re-raising a TypeError; ensure you reference the existing symbols
payload, planner_result, _planner_result_from_payload and checkpoint in your
change so behavior and docstring remain consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3cd9fbe2-81ee-4aba-9d81-e0e7e4e99eba
📒 Files selected for processing (12)
docs/adr/adr-006-durable-generation-checkpoints.mddocs/episodic-podcast-generation-system-design.mddocs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.mdepisodic/canonical/storage/workflow_checkpoints.pyepisodic/orchestration/_dto.pyepisodic/orchestration/_protocols.pyepisodic/orchestration/checkpoints.pyepisodic/orchestration/langgraph.pytests/canonical_storage/test_workflow_checkpoints.pytests/test_generation_orchestration_langgraph.pytests/test_orchestration_planner.pytests/test_orchestration_properties.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md (1)
689-718:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse Oxford spelling for -ize/-ization/-izing forms.
The en-GB-oxendict convention requires the -ize suffix. Update the following:
- Line 691: "serialised" → "serialized"
- Line 698: "deserialisation" → "deserialization"
- Line 712: "finalising" → "finalizing"
Proposed fix
Revision note 2026-05-10: Follow-up review fixes added richer suspend/resume module documentation, documented `resume_generation_orchestration` error paths, -injected the in-memory checkpoint clock, serialised in-memory checkpoint saves +injected the in-memory checkpoint clock, serialized in-memory checkpoint saves with an `asyncio.Lock`, and changed the SQLAlchemy checkpoint adapter to insert first and query only after duplicate-key conflicts. Added missing unknown-checkpoint, concurrent in-memory save, checkpoint payload property, and checkpoint payload snapshot coverage. Revision note 2026-05-10: Code review follow-up made checkpoint planner payload -deserialisation symmetric for optional planner usage, added the explicit +deserialization symmetric for optional planner usage, added the explicit `CheckpointPort.mark_resumed()` status transition after successful resume, fixed the SQLAlchemy checkpoint store runtime annotation, and covered negative idempotency attempts plus SQL not-found checkpoint lookups. ... Revision note 2026-05-10: Review follow-up verified that SQL checkpoint saves already use a nested transaction savepoint for duplicate idempotency keys, so no whole-unit-of-work rollback fix was needed. Multi-step suspended checkpoints now fail explicitly during `resume_generation_orchestration` instead of -silently finalising only the first externally supplied action result. +silently finalizing only the first externally supplied action result.Triage:
[type:spelling]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md` around lines 689 - 718, Replace the three British spellings in the revision notes to use Oxford -ize forms: change "serialised" to "serialized", "deserialisation" to "deserialization", and "finalising" to "finalizing" in the revision text (search for those exact words in the docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md revision notes and update them).
♻️ Duplicate comments (2)
docs/developers-guide.md (1)
625-626:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace "independent from" with "independent of".
The correct en-GB collocation is "independent of", not "independent from".
Proposed fix
- checkpoint, and resume ports that keep graph policy independent from storage, + checkpoint, and resume ports that keep graph policy independent of storage,Triage:
[type:grammar]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/developers-guide.md` around lines 625 - 626, Replace the phrase "independent from" with "independent of" in the sentence containing "checkpoint, and resume ports that keep graph policy independent from storage, queue, and provider adapters" so the collocation uses en-GB "independent of" (i.e., change "...independent from storage, queue, and provider adapters" to "...independent of storage, queue, and provider adapters").episodic/orchestration/langgraph.py (1)
485-486:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winGuard against missing
planner_resultkey in checkpoint payload.Line 486 accesses
payload["planner_result"]directly. A malformed checkpoint payload raisesKeyError, but the docstring documents onlyTypeErrorandValueError. Either catchKeyErrorand raiseTypeError, or documentKeyErrorin the docstring.Proposed fix
payload = checkpoint.payload + if "planner_result" not in payload: + msg = "checkpoint payload missing required key: planner_result" + raise TypeError(msg) planner_result = _planner_result_from_payload(payload["planner_result"])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@episodic/orchestration/langgraph.py` around lines 485 - 486, The code directly indexes payload["planner_result"] which can raise KeyError for malformed checkpoint payloads; in the function handling checkpoint.payload (referencing the local variable payload and the call to _planner_result_from_payload), wrap the access in a try/except KeyError and raise a TypeError (or convert to the documented exception) with a clear message about the missing "planner_result" key so callers see a consistent error type, or alternatively update the docstring to include KeyError if you prefer not to change runtime behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md`:
- Around line 689-718: Replace the three British spellings in the revision notes
to use Oxford -ize forms: change "serialised" to "serialized", "deserialisation"
to "deserialization", and "finalising" to "finalizing" in the revision text
(search for those exact words in the
docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md revision
notes and update them).
---
Duplicate comments:
In `@docs/developers-guide.md`:
- Around line 625-626: Replace the phrase "independent from" with "independent
of" in the sentence containing "checkpoint, and resume ports that keep graph
policy independent from storage, queue, and provider adapters" so the
collocation uses en-GB "independent of" (i.e., change "...independent from
storage, queue, and provider adapters" to "...independent of storage, queue, and
provider adapters").
In `@episodic/orchestration/langgraph.py`:
- Around line 485-486: The code directly indexes payload["planner_result"] which
can raise KeyError for malformed checkpoint payloads; in the function handling
checkpoint.payload (referencing the local variable payload and the call to
_planner_result_from_payload), wrap the access in a try/except KeyError and
raise a TypeError (or convert to the documented exception) with a clear message
about the missing "planner_result" key so callers see a consistent error type,
or alternatively update the docstring to include KeyError if you prefer not to
change runtime behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0e89e48e-0fd8-4268-96a6-edbd8a34b779
📒 Files selected for processing (4)
docs/developers-guide.mddocs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.mdepisodic/orchestration/langgraph.pytests/test_generation_orchestration_langgraph.py
This comment was marked as resolved.
This comment was marked as resolved.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 776 The 🏁 Script executed: Length of output: 2933
The supporting changes are also all in place:
No further work is required for these checks. |
Raise `TypeError` for missing required checkpoint payload fields so malformed resume payloads follow the documented deserialization contract.
Keep `main` ADR 006 for hexagonal enforcement and preserve the durable checkpoint decision as ADR 007 after rebasing onto `origin/main`.
6c15c9d to
57d5761
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (3)
episodic/canonical/storage/workflow_checkpoints.py (1)
11-11:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove the redundant future-annotations import in this new module.
Delete the import at Line 11 to align with the Python 3.14 baseline.
♻️ Suggested patch
-from __future__ import annotations - import typing as typAs per coding guidelines, "Do not add
from __future__ import annotationsin new or modified files; the project baseline is Python >=3.14 where annotation evaluation is deferred by default".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@episodic/canonical/storage/workflow_checkpoints.py` at line 11, Remove the redundant future import statement "from __future__ import annotations" from the top of the module (the import shown in the diff) so the file relies on the project Python >=3.14 baseline; simply delete that import line and ensure no other references depend on it (no other changes required).docs/episodic-podcast-generation-system-design.md (1)
553-600:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd screen-reader descriptions before both new Mermaid sequence diagrams.
Insert a short
Screen reader description:paragraph before the diagram at
Line 553 and before the diagram at Line 577 so assistive technologies receive
context before parsing the diagram blocks.♿ Suggested patch
+Screen reader description: Suspend path for generation where planning completes, +the checkpoint is persisted or reused, and the workflow returns a suspended +result before side-effecting execution. + ```mermaid sequenceDiagram @@@@
+Screen reader description: Resume path where checkpoint lookup rejects unknown
+IDs, known checkpoints resume through TaskResumePort, and the final generation
+result is built before marking the checkpoint resumed.
+ LoadingsequenceDiagramTriage:
[type:docstyle]As per coding guidelines, "Add screen reader descriptions before complex diagrams or code blocks in documentation".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/episodic-podcast-generation-system-design.md` around lines 553 - 600, Add a short "Screen reader description:" paragraph immediately before each mermaid sequenceDiagram block shown (the one labeled StructuredPlanningOrchestrator/GenerationGraph and the one labeled GenerationOrchestrationLayer/CheckpointPort/TaskResumePort) so assistive tech gets context; the paragraph should be one or two concise sentences describing the flow (e.g., "Screen reader description: Suspend flow that persists or reuses a checkpoint before side-effecting execution." for the first diagram and "Screen reader description: Resume path where checkpoint lookup rejects unknown IDs, known checkpoints resume through TaskResumePort and the final generation result is built before marking the checkpoint resumed." for the second), inserted directly above the opening ```mermaid line for each diagram.tests/canonical_storage/test_workflow_checkpoints.py (1)
3-3:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove the redundant future-annotations import in this new test module.
Delete Line 3 to match the repository Python 3.14 baseline.
♻️ Suggested patch
-from __future__ import annotations - import typing as typAs per coding guidelines, "Do not add
from __future__ import annotationsin new or modified files; the project baseline is Python >=3.14 where annotation evaluation is deferred by default".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/canonical_storage/test_workflow_checkpoints.py` at line 3, Delete the redundant top-level line "from __future__ import annotations" in the new test module (the import shown in the diff); remove that import statement so the file no longer includes it and save the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@alembic/versions/20260508_000008_add_workflow_checkpoints.py`:
- Around line 36-41: The migration defines updated_at with
server_default=sa.func.now() but lacks a DB-side update on row modification; if
rows may be updated outside the ORM add a PostgreSQL trigger in this migration:
create a plpgsql function (e.g. update_updated_at_column) that sets
NEW.updated_at = NOW() and create a BEFORE UPDATE trigger (e.g.
update_workflow_checkpoints_updated_at) on the workflow_checkpoints table via
op.execute in the upgrade, and in the downgrade drop the trigger and the
function via op.execute; reference the updated_at column and the table name
workflow_checkpoints and ensure the function/trigger names match between upgrade
and downgrade.
In `@docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md`:
- Around line 465-467: The ExecPlan currently references reserve_or_get(...);
update the document to use the final API name save_or_reuse(...) everywhere in
the ExecPlan so it matches the implemented port contract and avoids
confusion—specifically replace occurrences of reserve_or_get with save_or_reuse
and ensure the surrounding text still describes the method as atomic via an
idempotency_key uniqueness constraint (preserve mentions of atomicity and
idempotency_key).
In `@episodic/canonical/storage/models.py`:
- Around line 104-108: The status column on the model is a plain String and
needs DB-level validation; define a domain enum (e.g., WorkflowCheckpointStatus
with values "suspended" and "resumed") in episodic/canonical/domain.py and
change the model's status mapped_column to use sa.Enum(WorkflowCheckpointStatus,
name="workflow_checkpoint_status") (or an equivalent CHECK-backed Enum) so only
allowed values are persisted; update any imports and adjust the mapped attribute
(status: orm.Mapped[str]) to reference the enum type to ensure SQLAlchemy and
the DB enforce the valid statuses.
- Around line 79-80: Add a NumPy-style docstring to the WorkflowCheckpointRecord
class that mirrors the structure used by other models (e.g.,
SeriesProfileRecord) and includes an "Attributes" section documenting each
SQLAlchemy column on the model: list each attribute name (e.g., id, workflow_id,
checkpoint_data, created_at, updated_at or the actual column names in
WorkflowCheckpointRecord), its type (e.g., Integer, String, JSON, DateTime), and
any constraints (primary key, nullable, unique, foreign key, default) plus a
one-line purpose for each; ensure the docstring is placed immediately under the
class definition and follows the same formatting and phrasing conventions used
in other model docstrings in the file.
In `@episodic/orchestration/langgraph.py`:
- Around line 167-173: The code currently accesses step["required_inputs"]
directly which raises KeyError; instead call _require_field(step,
"required_inputs") to preserve the TypeError contract—update the required_inputs
construction (the tuple(...) comprehension that uses typ.cast("list[object]",
...)) to use _require_field(step, "required_inputs") in place of
step["required_inputs"], leaving the cast and tuple generation intact so missing
fields raise TypeError as expected.
In `@tests/test_generation_orchestration_snapshots.py`:
- Around line 20-23: The import line currently brings in unused symbols
(_action_result_from_payload, _plan_from_payload, _plan_to_payload,
_planner_result_from_payload); update the import from
episodic.orchestration.langgraph to only import the two used functions
_action_result_to_payload and _planner_result_to_payload so the module no longer
imports unused names.
---
Duplicate comments:
In `@docs/episodic-podcast-generation-system-design.md`:
- Around line 553-600: Add a short "Screen reader description:" paragraph
immediately before each mermaid sequenceDiagram block shown (the one labeled
StructuredPlanningOrchestrator/GenerationGraph and the one labeled
GenerationOrchestrationLayer/CheckpointPort/TaskResumePort) so assistive tech
gets context; the paragraph should be one or two concise sentences describing
the flow (e.g., "Screen reader description: Suspend flow that persists or reuses
a checkpoint before side-effecting execution." for the first diagram and "Screen
reader description: Resume path where checkpoint lookup rejects unknown IDs,
known checkpoints resume through TaskResumePort and the final generation result
is built before marking the checkpoint resumed." for the second), inserted
directly above the opening ```mermaid line for each diagram.
In `@episodic/canonical/storage/workflow_checkpoints.py`:
- Line 11: Remove the redundant future import statement "from __future__ import
annotations" from the top of the module (the import shown in the diff) so the
file relies on the project Python >=3.14 baseline; simply delete that import
line and ensure no other references depend on it (no other changes required).
In `@tests/canonical_storage/test_workflow_checkpoints.py`:
- Line 3: Delete the redundant top-level line "from __future__ import
annotations" in the new test module (the import shown in the diff); remove that
import statement so the file no longer includes it and save the file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 98506b4c-dbc7-4765-8472-f18918d30498
📒 Files selected for processing (25)
alembic/versions/20260508_000008_add_workflow_checkpoints.pydocs/adr/adr-007-durable-generation-checkpoints.mddocs/developers-guide.mddocs/episodic-podcast-generation-system-design.mddocs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.mddocs/roadmap.mddocs/users-guide.mdepisodic/canonical/storage/__init__.pyepisodic/canonical/storage/models.pyepisodic/canonical/storage/uow.pyepisodic/canonical/storage/workflow_checkpoints.pyepisodic/orchestration/__init__.pyepisodic/orchestration/_dto.pyepisodic/orchestration/_protocols.pyepisodic/orchestration/checkpoints.pyepisodic/orchestration/generation.pyepisodic/orchestration/langgraph.pytests/__snapshots__/test_generation_orchestration_snapshots.ambrtests/canonical_storage/test_workflow_checkpoints.pytests/features/generation_orchestration.featuretests/steps/test_generation_orchestration_steps.pytests/test_generation_orchestration_langgraph.pytests/test_generation_orchestration_snapshots.pytests/test_orchestration_planner.pytests/test_orchestration_properties.py
|
@coderabbitai Have the following now been resolved? Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity. |
This comment was marked as resolved.
This comment was marked as resolved.
Add database-side timestamp and status enforcement for workflow checkpoints, including the PostgreSQL update trigger and persisted `WorkflowCheckpointStatus` enum. Normalize required plan-step payload validation through the shared `TypeError` deserialization contract and update the suspend/resume documentation to match the implemented `save_or_reuse` API.
Reject multi-step plans before persisting a suspended checkpoint so the resume path cannot later drop planned actions. Trim checkpoint request payloads to orchestration metadata and keep the ExecPlan revision chronology aligned with the implemented update.
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. episodic/orchestration/langgraph.py Comment on lines +388 to +460 async def _suspend_execute_node(
state: GenerationGraphState,
*,
checkpoint_port: protocols.CheckpointPort,
workflow_type: str = "generation_orchestration",
) -> dict[str, dto.SuspendedWorkflowResult]:
"""Persist or reuse a checkpoint before executing the first action."""
request = state.request
if request is None:
msg = "missing required state value: request"
raise ValueError(msg)
planner_result = state.planner_result
if planner_result is None:
msg = "missing required state value: planner_result"
raise ValueError(msg)
if len(planner_result.plan.steps) != 1:
msg = "cannot suspend a workflow with no planned steps"
if planner_result.plan.steps:
msg = (
"suspend_generation_orchestration currently supports exactly "
"one planned step per suspended checkpoint."
)
raise ValueError(msg)
action = planner_result.plan.steps[0]
identity = _build_execute_step_identity(
request=request,
action=action,
workflow_type=workflow_type,
)
idempotency_key = dto.build_workflow_step_idempotency_key(
identity,
)
_log_event(
"debug",
"generation_graph.suspend_execute_node.start",
correlation_id=request.correlation_id,
workflow_id=identity.workflow_id,
workflow_type=identity.workflow_type,
step_name=identity.step_name,
action_id=identity.action_id,
idempotency_key=idempotency_key,
)
fresh_id = str(uuid.uuid4())
existing = await checkpoint_port.save_or_reuse(
dto.WorkflowCheckpoint(
checkpoint_id=fresh_id,
workflow_id=identity.workflow_id,
workflow_type=identity.workflow_type,
step_name=identity.step_name,
idempotency_key=idempotency_key,
payload=_build_checkpoint_payload(
request=request,
planner_result=planner_result,
),
)
)
reused_checkpoint = existing.checkpoint_id != fresh_id
_log_event(
"debug",
"generation_graph.suspend_execute_node.finish",
correlation_id=request.correlation_id,
checkpoint_id=existing.checkpoint_id,
idempotency_key=existing.idempotency_key,
reused_checkpoint=reused_checkpoint,
)
suspended_result = dto.SuspendedWorkflowResult(
checkpoint_id=existing.checkpoint_id,
workflow_id=existing.workflow_id,
step_name=existing.step_name,
idempotency_key=existing.idempotency_key,
)
return {"suspended_result": suspended_result}❌ New issue: Large Method |
This comment was marked as resolved.
This comment was marked as resolved.
Move suspend state validation into a small helper so the checkpoint node stays under the method-size threshold without changing orchestration behaviour.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4813
Check-by-check verdict
No further work is required on this PR for the in-scope items. Extend Issue |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/adr/adr-007-durable-generation-checkpoints.md`:
- Line 1: Update the document title to expand the ADR acronym on first use:
replace the heading "# ADR-007: Durable generation checkpoints" with
"Architecture Decision Record (ADR)-007: Durable generation checkpoints" (or
equivalent) so the acronym ADR is expanded at first occurrence in the title.
In `@episodic/orchestration/langgraph.py`:
- Around line 157-166: Enum construction for PlannedAction fields
(dto.ActionKind and dto.ModelTier) can raise ValueError on invalid members which
bypasses the intended deserialisation TypeError path; update the deserialisation
logic around PlannedAction creation in langgraph.py (where _required_string is
used to build dto.ActionKind and dto.ModelTier) to catch ValueError from the
enum constructors and re-raise a TypeError with the same context, and apply the
same change to the other enum parsing site that constructs
dto.ActionKind/dto.ModelTier later in the file so all malformed-enum payloads
consistently produce TypeError.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fc2ab477-3adf-4ac0-95f6-3d55d4c18ac9
📒 Files selected for processing (2)
docs/adr/adr-007-durable-generation-checkpoints.mdepisodic/orchestration/langgraph.py
Convert invalid checkpoint enum payload values to the documented `TypeError` deserialization path for both planned actions and action results. Expand the checkpoint ADR title acronym on first use.
Summary
This branch implements roadmap task (2.4.2) by adding durable suspend-and-resume orchestration for structured generation runs. It persists workflow checkpoints before the side-effecting execution step, reuses the first checkpoint for repeated idempotency keys, and resumes from a checkpoint through
TaskResumePortwith an externally supplied action result.Roadmap task: (2.4.2)
Execplan: docs/execplans/2-4-2-add-lang-graph-suspend-and-resume-orchestration.md
Review walkthrough
Validation
make check-fmt: passedmake typecheck: passedmake lint: passedmake test: passed,446 passed, 3 skippedmake markdownlint: passed,0 error(s)make nixie: passedNotes
make fmtwas also run after documentation edits. It left Python files unchanged but still reports the repository-wide legacy Markdown MD013 formatter issue in unrelated documents. The explicit required gates above pass on the final branch state.Summary by Sourcery
Add durable suspend-and-resume support to the LangGraph-based generation orchestration, including checkpoint persistence, idempotent workflow step keys, and resume handling, with tests, storage adapters, migration, and documentation updates.
New Features:
Enhancements:
Build:
Deployment:
Tests: