Skip to content

An empty plan against an unmet goal gets one nudge, then is believed - #92

Merged
Shashankss1205 merged 1 commit into
mainfrom
fix/empty-proposal-nudge
Aug 5, 2026
Merged

An empty plan against an unmet goal gets one nudge, then is believed#92
Shashankss1205 merged 1 commit into
mainfrom
fix/empty-proposal-nudge

Conversation

@Shashankss1205

Copy link
Copy Markdown
Collaborator

The defect

An admitted empty proposal ends the run: the planner is saying there is no further work, and the loop believes it. That is the right default — but it held even when the operator's own goal_reached check said the run was NOT done, and the planner was never told about the contradiction. Observed with qwen3:8b against the stdlib catalog: asked for a security audit, the model returned a valid empty proposal on round 1 and the run stopped no_further_work having spent one planning call and done nothing. The goal check existed, was unsatisfied, and appeared nowhere in any prompt.

The empty-proposal branch consulted goal_reached only to choose the label (goal_met vs no_further_work), never to question the stop (loop.py, the if not proposal.nodes: branch).

The fix

One nudge, not a counter. When an admitted proposal is empty AND a goal check exists AND it is unsatisfied AND this run has not nudged before, the loop replans once with a note naming the contradiction:

Your previous proposal was empty, but the run's goal check is not yet satisfied. Propose the remaining work, or reply with an empty proposal again to confirm there is nothing more this catalog can do.

Then the planner's answer is final:

  • It proposes work → the round proceeds through the same gate as any other.
  • It repeats the empty proposalno_further_work, with the detail upgraded to say the planner confirmed it against an unsatisfied goal check.
  • Its follow-up is unusable (including a scripted planner whose reply list simply ran out) → no_further_work with "the follow-up produced nothing usable" — read as confirmation-by-silence, never counted toward max_consecutive_planning_failures. This is what keeps the stdlib scripted flow's deliberate honest-clean-stop intact (test_the_scripted_plan_runs_spend_free_and_stops_cleanly is unchanged and passes).

Runs with no goal check, or a satisfied one, behave byte-identically to before: one empty proposal, one immediate clean stop. The existing pins (test_an_admitted_empty_proposal_ends_the_run_with_no_further_work, stop-reason string set) are untouched and green.

Tests

Four new, in tests/test_planner_loop.py:

  • test_an_empty_proposal_against_an_unmet_goal_gets_one_nudge — empty then real work: run finishes goal_met, the nudge text appears in round 2's prompt, round 1 is recorded admitted-but-unexecuted.
  • test_a_second_empty_proposal_is_believed — empty twice: no_further_work after exactly 2 rounds, "confirmed no further work" in the detail, nothing executed.
  • test_an_unusable_reply_after_the_nudge_confirms_no_further_work — script exhausts after the nudge: no_further_work, not planning_failed.
  • test_an_empty_proposal_with_no_goal_check_stops_without_a_nudge — pins the unchanged default: one round, one call, clean stop.

Full suite: 1,991 passed, 12 deselected; ruff clean.

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Shashankss1205
Shashankss1205 merged commit 63ff75e into main Aug 5, 2026
6 checks passed
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