Skip to content

Resume a codegen thread from its checkpoint instead of re-entering it - #192

Open
shellygr wants to merge 1 commit into
masterfrom
shelly/codegen-resume-checkpoint
Open

Resume a codegen thread from its checkpoint instead of re-entering it#192
shellygr wants to merge 1 commit into
masterfrom
shelly/codegen-resume-checkpoint

Conversation

@shellygr

Copy link
Copy Markdown
Contributor

The bug

console-codegen / tui-codegen take --thread-id, so the way to pick up a run
that crashed partway is to invoke the same command again against the same thread.
That did not work. The second invocation died at the first LLM call:

ValueError: Received multiple non-consecutive system messages.

langgraph treats a non-None input as an update from START. Passing the
original input again therefore re-entered the entry node on top of a message
history that already held an initial prompt, appending a second system message
mid-history. The provider rejects that, so the failure is loud rather than a
quiet duplicate.

The fix

Most of the machinery was already in place. run_graph drops the input when the
run config names a checkpoint (composer/io/graph_runner.py:105), which is why
floor retries resume correctly. What was missing is that nothing turned a bare
--thread-id into a checkpoint, so the manual path never took that branch.

  • latest_checkpoint_of(checkpointer, thread_id) in composer/io/context.py
    returns the thread's most recent checkpoint id, or None for a fresh thread.
  • The codegen entry point resolves it when --thread-id was given and
    --checkpoint-id was not, and passes it as the resume point.

Behaviour elsewhere is untouched: a fresh run mints a new thread id, finds no
checkpoint, and starts from START exactly as before. An explicit
--checkpoint-id still wins. No sub-agent is affected, since every other
run_to_completion caller either mints a unique thread per invocation or
already passes a checkpoint.

Resuming a thread that already ran to completion returns its final state instead
of authoring a second time.

Tests

tests/test_graph_resume.py — four tests over a two-node pregel loop with an
entry node that injects a system prompt the way the real one does, on an
in-memory checkpointer. No LLM, no Postgres, 0.8s.

The third test is the one that gives the others teeth: it runs the identical
second invocation without the resume point and asserts the entry node runs
twice and the system prompt is duplicated, so a regression in the fix fails a
test rather than silently passing.

Routine pass: 1122 passed, 10 skipped, 11 deselected, pyright 0 errors.

Note for review

If a caller ever passed both an already-checkpointed --thread-id and a
resume_work_key, the crash snapshot merged into flow_input would go unused,
since a checkpoint resume discards the input. That combination does not occur
today (the snapshot path mints a fresh thread), and the snapshot is salvaged from
the very checkpoint being resumed, so I left it alone rather than guard a case
that has no caller.

🤖 Generated with Claude Code

Codegen takes --thread-id, so the way to pick up a run that crashed is to
invoke the same command again against the same thread. That did not work:
the second invocation handed langgraph the original input, which langgraph
treats as an update from START, so the graph re-entered the entry node on
top of a message history that already held an initial prompt. The provider
rejected the request outright:

    ValueError: Received multiple non-consecutive system messages.

The machinery for resuming was already there -- run_graph drops the input
when the run config names a checkpoint, which is why floor retries resume
correctly -- but nothing resolved a bare --thread-id into a checkpoint.
So the codegen entry point now looks the thread's latest checkpoint up and
passes it as the resume point, unless the caller named a checkpoint itself.

A fresh thread still finds no checkpoint and starts from START as before.
Resuming a thread that already finished returns its final state rather than
authoring a second time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant