Skip to content

fix(todo): filter completed and cancelled items when hydrating todos - #25945

Open
doublerebel wants to merge 3 commits into
NousResearch:mainfrom
doublerebel:fix/todo-hydration-active-items
Open

fix(todo): filter completed and cancelled items when hydrating todos#25945
doublerebel wants to merge 3 commits into
NousResearch:mainfrom
doublerebel:fix/todo-hydration-active-items

Conversation

@doublerebel

@doublerebel doublerebel commented May 14, 2026

Copy link
Copy Markdown

What does this PR do?

Adopts the narrow fix from #7595 so _hydrate_todo_store() only restores active todo items from conversation history.

Previously, hydration replayed every item from the last todo tool response, including completed and cancelled items. That disagreed with TodoStore.format_for_injection(), which intentionally omits completed/cancelled items from context injection. After context compression or a fresh agent turn, cancelled work could therefore reappear as active state.

This PR preserves only pending and in_progress items during hydration and logs how many inactive items were filtered.

Credit/provenance:

Related Issue

Addresses #7597 and #7599.

Note: both issues are currently closed upstream, but current origin/main still restores inactive todo items from _hydrate_todo_store(). This PR applies the closed-but-unmerged fix path and adds regression coverage.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • run_agent.py
    • Filters hydrated todo items to pending / in_progress before writing them into the todo store.
    • Keeps hydration aligned with TodoStore.format_for_injection() so completed/cancelled work is not resurrected.
    • Reports the filtered count in verbose restore output.
  • tests/run_agent/test_run_agent.py
    • Adds coverage that completed and cancelled items are filtered during hydration.
    • Adds coverage for the restored/filtered count message.
  • scripts/release.py
    • Adds AUTHOR_MAP entries for the adopted upstream author and this branch author so the contributor attribution check can pass.

How to Test

Reproduction on current origin/main:

  1. Ensure _hydrate_todo_store() receives a todo tool response containing mixed statuses:
    • pending
    • in_progress
    • completed
    • cancelled
  2. Call _hydrate_todo_store(history).
  3. Observe current origin/main writes all four items back to the todo store.

Fixed behavior on this branch:

  1. Run:

    scripts/run_tests.sh tests/run_agent/test_run_agent.py::TestHydrateTodoStore -q
  2. Confirm only pending and in_progress items are restored.

  3. Confirm the restore message reports the filtered inactive count.

Security review:

  • Reviewed diff against origin/main.
  • No new shell, filesystem, network, credential, or path handling.
  • Existing history JSON parsing trust boundary is not expanded.
  • New verbose logging reports counts only, not todo contents.
  • Verdict: no security blockers.

Full-suite local note:

  • I ran the full test suite locally via the repository runner, equivalent to the PR-template pytest tests/ -q check:

    ulimit -n 4096 && scripts/run_tests.sh tests/
  • Result: 94 failed, 22771 passed, 90 skipped, 2 errors on macOS/darwin-arm64. The failures were broad and outside this PR's touched files, concentrated in existing LSP/client e2e, provider/auxiliary/compression, CLI setup/config/gateway/update/completion, process/terminal/live-system-guard/zombie cleanup, file-tool state/staleness guards, plugin/kanban discovery, browser/TTS/code-execution timeout, and TUI gateway goal-command tests. The two collection errors were in tests/hermes_cli/test_web_oauth_dispatch.py and tests/plugins/test_kanban_dashboard_plugin.py.

  • Focused tests for this change passed: scripts/run_tests.sh tests/run_agent/test_run_agent.py::TestHydrateTodoStore tests/tools/test_todo_tool.py -q -> 18 passed.

  • E2E suite passed separately: python -m pytest tests/e2e/ -v --tb=short -> 56 passed, 7 skipped.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS / darwin-arm64 local worktree

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — no OS-specific behavior changed
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

For New Skills

N/A — this PR does not add or modify a skill.

Screenshots / Logs

N/A — no UI screenshots or log excerpts are needed for this non-visual state-hydration fix.

johnhom1024 and others added 3 commits May 14, 2026 13:21
Cancelled (or completed) todo items could resurrect after subsequent
agent turns because _hydrate_todo_store restored every item from the
latest todo tool response in conversation history.

Filter restored todo items to pending/in_progress so hydration matches
TodoStore.format_for_injection(), which intentionally excludes completed
and cancelled items.

Closes NousResearch#7597.
Closes NousResearch#7599.

Upstream-PR: NousResearch#7595
Upstream-Commit: b12f1a9
(cherry picked from commit b12f1a9)
Add regression coverage for restoring only active todo items from
conversation history and for reporting the filtered count in the
hydration log message.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels May 14, 2026
@doublerebel
doublerebel marked this pull request as ready for review May 14, 2026 21:05
@johnhom1024

Copy link
Copy Markdown

Thanks for picking this up! I closed my original PR #7595 because I wasn't 100% confident in the fix at the time (it was my first dive into this part of the codebase), but seeing you independently validate it and add test coverage is really reassuring. The diff looks correct to me — the pending/in_progress filter in _hydrate_todo_store is exactly the right spot. Appreciate the credit and the regression tests!

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying a real hydration/injection mismatch. Current main still restores the unfiltered list in run_agent.py:3594-3598, while tools/todo_tool.py:129-136 injects only active items after compression.

Problems

  • run_agent.py:5885 assumes every todos list member is a dict. Current TodoStore._validate() intentionally handles non-dict members at tools/todo_tool.py:166-167; guard or normalize before calling .get() so hydration does not regress to AttributeError.
  • The new histories at tests/run_agent/test_run_agent.py:956 and :975 omit the assistant todo call. Current main requires that pairing in run_agent.py:3602-3646, so these tests will not hydrate after salvage.

Suggested changes

  • Add a non-dict-entry regression case for the hydration path.
  • Add a matching assistant todo call before each new tool result, using the existing current-main test helper pattern.

This is an automated hermes-sweeper review.

Comment thread run_agent.py
# the next turn hydrates the store from conversation history.
active_items = [
item for item in last_todo_response
if item.get("status") in ("pending", "in_progress")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes every parsed todos member is a dict. Current TodoStore._validate() explicitly handles non-dict entries (tools/todo_tool.py:166-167), but this .get() would now raise before that normalization. Guard or normalize malformed entries here and add coverage.

history = [
{"role": "user", "content": "plan"},
{
"role": "tool",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new histories (including the one below) lack the preceding assistant todo tool call now required by _tool_response_matches_todo_call() on main (run_agent.py:3602-3646). Add the matching assistant call before this tool result so the tests exercise hydration after salvage.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants