langchain - ✅(Solved) Fix feat(middleware): TodoListMiddleware should re-inject current todos into system prompt for deep/long-running agent compatibility [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36624Fetched 2026-04-09 07:50:53
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
1
Participants
Timeline (top)
labeled ×3issue_type_added ×1

PR fix notes

PR #36681: feat(langchain): re-inject current todos in todo middleware system prompt

Description (problem / solution / changelog)

Fixes #36624

This PR updates TodoListMiddleware to re-inject the current todo state into the system prompt on every model call by default, so agents retain direct visibility into their live plan even after history summarization. It also refactors the duplicated sync and async system-message construction into a shared helper and adds unit coverage for the new behavior and opt-out path.

No breaking changes. The new inject_current_todos parameter is keyword-only and defaults to True, while setting it to False preserves the previous behavior.

AI assistance disclosure: I used an AI coding assistant to help implement, refine, and test this change, and I reviewed the final code and description before submitting.

How did you verify your code works?

  • Added unit tests covering todo block rendering, live todo injection, empty todo no-op behavior, and inject_current_todos=False behavior
  • Added matching async tests for the same scenarios
  • Ran: uv run --group test pytest tests/unit_tests/agents/middleware/implementations/test_todo.py -v
  • Result: 41 passed

Social handles (optional)

Web: www.garybadwal.com Twitter: @garybadwal_ LinkedIn: www.linkedin.com/in/garybadwal

Changed files

  • libs/langchain_v1/langchain/agents/middleware/todo.py (modified, +98/-19)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_todo.py (modified, +253/-0)
  • libs/langchain_v1/uv.lock (modified, +6/-1)
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a feature request, not a bug report or usage question.
  • I added a clear and descriptive title that summarizes the feature request.
  • I used the GitHub search to find a similar feature request and didn't find it.
  • I checked the LangChain documentation and API reference to see if this feature already exists.
  • This is not related to the langchain-community package.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Feature Description

TodoListMiddleware should re-inject the current todo list into the system prompt on every model call for immunity against summarization.

Currently wrap_model_call only appends static instructions, it never reads state["todos"]. This means the agent's live plan is invisible to the model unless it happens to still be in message history.

Proposed addition: inject_current_todos: bool = True parameter on TodoListMiddleware.init. When enabled, _build_system_content() reads state["todos"] and appends a <current_todos> block with status markers ([ ] pending, [~] in_progress, [x] completed) to the system prompt on every model call.

Use Case

This breaks silently when TodoListMiddleware is composed with SummarizationMiddleware in long-running / deep agent runs.

SummarizationMiddleware compacts message history when the context window fills up. The ToolMessage echo of the last write_todos call is eligible for summarization once trimmed, the model loses visibility of its current plan entirely, even though state["todos"] is still fully intact in checkpointed state.

The agent must then infer its plan from a lossy LLM-generated summary instead of reading structured state directly. This causes plan drift and repeated re-planning on long tasks exactly the failure mode the todo list was designed to prevent.

This is directly relevant to LangChain's own deep agents pattern where the TodoList tool is described as a cognitive no-op for context engineering. That guarantee breaks under summarization without this fix.

Proposed Solution

Add to TodoListMiddleware.init: inject_current_todos: bool = True

Extract a _build_system_content(request) helper that:

  1. Takes the existing system message content blocks as base
  2. Appends the static system_prompt instructions (existing behavior)
  3. If inject_current_todos=True and state["todos"] is non-empty, appends:

<current_todos> [x] Completed task [~] In-progress task
[ ] Pending task </current_todos>

Both wrap_model_call and awrap_model_call delegate to this shared helper, removing the duplicated if/else logic that currently exists in both methods. The change is fully non-breaking, inject_current_todos=False restores exactly the current behavior.

Alternatives Considered

Summarization-aware trimming configuring SummarizationMiddleware to never trim ToolMessages from write_todos. Rejected: brittle, requires users to know about the interaction, doesn't help if history is cleared for other reasons.

Additional Context

I have already implemented this locally:

  • _render_todos_block() helper function
  • inject_current_todos flag with opt-out default
  • _build_system_content() refactor eliminating duplicated logic in sync/async paths
  • 3 new unit tests: injection present, empty-list no-op, flag disabled

Branch is committed and tests pass. Happy to open the PR immediately if this is approved and I am assigned.

extent analysis

TL;DR

Add an inject_current_todos parameter to TodoListMiddleware.__init__ to re-inject the current todo list into the system prompt on every model call.

Guidance

  • Implement the proposed solution by adding inject_current_todos: bool = True to TodoListMiddleware.__init__ and creating a _build_system_content helper function to append the current todo list to the system prompt.
  • Update wrap_model_call and awrap_model_call to delegate to the _build_system_content helper to remove duplicated logic.
  • Test the implementation with the provided unit tests to ensure correct behavior.
  • Consider the alternative approach of configuring SummarizationMiddleware to never trim ToolMessages from write_todos, but note that this approach was rejected due to its brittleness.

Example

class TodoListMiddleware:
    def __init__(self, inject_current_todos: bool = True):
        self.inject_current_todos = inject_current_todos

    def _build_system_content(self, request):
        # Append static system prompt instructions
        content = "..."

        # Append current todo list if inject_current_todos is True
        if self.inject_current_todos and state["todos"]:
            content += "<current_todos>...\n"
            for todo in state["todos"]:
                content += f"[{'x' if todo['completed'] else '~' if todo['in_progress'] else ' '}] {todo['task']}\n"
            content += "</current_todos>"

        return content

Notes

The proposed solution is fully non-breaking, and setting inject_current_todos=False restores the current behavior. The implementation has already been tested with three new unit tests.

Recommendation

Apply the proposed workaround by adding the inject_current_todos parameter to TodoListMiddleware.__init__ and implementing the _build_system_content helper function. This solution addresses the issue of the model losing visibility of the current plan when SummarizationMiddleware compacts message history.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING