Skip to content

feat: ImplementAndJudge chain, per-tool error reporting, and minimum-diff judge rubric - #178

Merged
elkaix merged 11 commits into
mainfrom
feat/implement-judge-chain
Jun 23, 2026
Merged

feat: ImplementAndJudge chain, per-tool error reporting, and minimum-diff judge rubric#178
elkaix merged 11 commits into
mainfrom
feat/implement-judge-chain

Conversation

@elkaix

@elkaix elkaix commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Adds a one-shot implementer → judge chain tool, surfaces the failing tool's reason in InvalidToolError, and adopts the minimum-diff rubric as a uniform judge dimension.

ImplementAndJudge chain tool (pythinker_code.tools.agent)

  • Wraps AgentTool twice (implementer first, then judge with the implementer's output baked into the packet).
  • On NEEDS_WORK and max_revisions >= 1, re-invokes the implementer once with the judge's REQUIRED FIXES section isolated under a ## Revision brief heading, then re-judges.
  • Two implementer invocations is the hard cap; higher values clamp.
  • Refuses to launch from a non-root role, validates both child subagent types + their execution policy / required MCP servers up front, and reuses the orchestration-approval pattern from RunAgents.
  • Verdict parsing anchors on the judge's SUMMARY heading so preamble tokens ("PASS for the brief but…", "BLOCKED would be overkill…") cannot outrank the real verdict. Missing / SUMMARY-without-token fails closed to BLOCKED.
  • The implementer's <coding_artifact> block is extracted as data (never as instructions) and passed to the judge. Required fixes are framed as untrusted feedback in the revision prompt.

InvalidToolError per-tool aggregation (soul/toolset.py)

Bad tool paths in agent.yaml used to surface as Invalid tools: [...] with the actual reason buried in the log file. The aggregated error now lists each failing tool with its per-tool reason and a "Did you mean ?" hint. A constructor exception on one tool is caught per-tool so the user gets one clear error instead of a bare traceback. Whole load still aborts on any failure.

Judge minimum-diff rubric dimension (agents/default/judge.yaml)

Every non-trivial diff the judge reviews is now checked against the reduction ladder (skip-need → reuse-stdlib → use-native → use-installed-dep → one-line → minimum). The ladder applies uniformly across review modes — there is no mode switch on the judge.

Bundled skills (src/pythinker_code/skills/judge-{minimum-diff,overengineering-review})

Two static default skills replace ad-hoc prose in the system prompt with explicit, versionable content, registered in the PyInstaller datas manifest so the one-file build picks them up.

Commits

  • feat(tools): add ImplementAndJudge chain tool
  • feat(toolset): surface per-tool reason in aggregated InvalidToolError
  • feat(agents): register ImplementAndJudge, adopt minimum-diff judge rubric
  • feat(skills): bundle judge-minimum-diff and judge-overengineering-review
  • test: cover ImplementAndJudge chain, judge branding, and toolset error reporting
  • chore(release): add Unreleased changelog entries

The branch also carries fd6070d feat: editor bug fixes, Draft and auto save fixes which was already committed locally on main ahead of origin/main when this branch was cut. That commit is unrelated to the chain-tool scope but is included so the branch contains every local commit the author asked to publish.

Test plan

  • make check-pythinker-code — ruff format/check + pyright + ty
  • make test-pythinker-code — unit + e2e
  • uv run pytest tests/core/test_implement_judge_chain.py -v — chain primitives (verdict parsing, artifact extraction, fingerprint, REQUIRED FIXES isolation, param validation)
  • uv run pytest tests/tools/test_implement_judge_load.py -v — loader regression
  • uv run pytest tests/core/test_load_agent.py::test_load_tools_invalid tests/core/test_load_agent.py::test_load_tools_aggregates_constructor_errors -v — new aggregated-error tests
  • uv run pytest tests/test_judge_branding.py -v — minimum-diff rubric drift guard
  • uv run pytest tests/utils/test_pyinstaller_utils.py::test_pyinstaller_datas -v — datas manifest

Risk

  • The chain is opt-in (parent agents must call ImplementAndJudge rather than Agent with subagent_type=implementer); default-agent registration just makes the tool available.
  • Tool registration touches agent.yaml; the loader-regression test test_implement_judge_loads_via_toolset guards against the missing-class startup failure mode.
  • InvalidToolError format change: any caller that string-matches the old Invalid tools: [...] shape will see the new multi-line message. Internal-only; no public API contract.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ImplementAndJudge auto-chaining for scoped, non-trivial code edits (single-tool workflow with revision feedback, capped to two runs).
    • Shipped judge-branded default skills, including minimum-diff and overengineering-review.
  • Bug Fixes

    • Improved tool-loading errors with per-tool reasons and actionable “Did you mean …?” hints.
    • Fixed OpenAI Responses prompt conversion to always send system as developer, including during model switches.
    • Refined background-task and update UI behavior (spinner/label fixes, fewer repaints, no stale “Update available” after /update).
  • Documentation

    • Added/updated rubric docs for minimum-diff and overengineering review.

elkaix added 7 commits June 23, 2026 13:11
The new chain tool wraps AgentTool twice (implementer first, then judge with
the implementer's output baked into the packet) and optionally re-invokes the
implementer once on NEEDS_WORK with the judge's REQUIRED FIXES section
isolated under a '## Revision brief' heading. Two implementer invocations is
the hard cap; higher max_revisions values clamp to one.

Verdict parsing anchors on the judge's SUMMARY heading so preamble tokens
('PASS for the brief but...', 'BLOCKED would be overkill...') cannot outrank
the real verdict, and missing/SUMMARY-without-token fails closed to BLOCKED
rather than silently passing. The implementer's <coding_artifact> block is
extracted as data (never as instructions) and passed to the judge. Required
fixes are framed as untrusted feedback in the revision prompt so an embedded
directive in the judge text cannot steer the write-privileged implementer.

The chain refuses to launch from a non-root role, validates both child
subagent types and their execution policy / required MCP servers up front, and
reuses the orchestration-approval pattern from RunAgents so a session-approved
chain does not re-prompt per child.
Bad tool paths in agent.yaml used to surface as a bare
'Invalid tools: [...]' with the actual reason (module missing, class
missing, or constructor exception) buried in the log file. The
aggregated error now lists each failing tool with its per-tool reason,
and a class-name miss logs a 'Did you mean <closest>?' hint.

A constructor exception on one tool is now caught per-tool so the user
gets one clear error naming the offending tool and the exception type
instead of a bare traceback out of agent load. The whole load still
aborts on any failure - agent.yaml tool references are hard
requirements - but the message is now diagnosable from the traceback
alone. This unblocks the stale-binary failure mode that hits users who
build pythinker before a new tool lands.
…bric

- default/agent.yaml: register pythinker_code.tools.agent:ImplementAndJudge
  alongside Agent and RunAgents so the chain is available out of the box
  without a custom agent spec.
- default/judge.yaml: add the coding_artifact BLOCKED gate to the Context
  Gate (the judge must see the implementer's <coding_artifact> block when
  judging a code-change summary) and a Minimum-diff rubric dimension
  applying the reduction ladder (skip-need, reuse-stdlib, use-native,
  use-installed-dep, one-line, minimum) to every non-trivial diff the
  judge reviews. The ladder applies uniformly across review modes - no
  mode switch on the judge.
- default/system.md: document the ImplementAndJudge default in the
  implementation playbook and the judge-gate guidance so parent agents
  prefer the chain over ad-hoc implementer+judge fan-out.
- tests/core/test_agent_spec.py + test_default_agent.py: assert the new
  tool appears in the default agent's tool list across the spec
  snapshot tests so the registration does not drift.
Two static default skills replace ad-hoc prose in the system prompt with
explicit, versionable content:

- judge-minimum-diff: the reduction-ladder rubric the judge applies as a
  dimension on every non-trivial diff (skip-need, reuse-stdlib,
  use-native, use-installed-dep, one-line, minimum).
- judge-overengineering-review: the parent-facing review checklist that
  walks through the same ladder before declaring a non-trivial change
  done.

Both ship as skills/ directory entries so ReadSkill can load them on
demand rather than bloating the always-on system prompt. PyInstaller
datas entries are added in tests/utils/test_pyinstaller_utils.py so the
PyInstaller one-file build picks them up alongside the other bundled
skills.
…r reporting

- tests/core/test_implement_judge_chain.py (new): unit tests for the
  chain's load-bearing primitives - verdict parsing (PASS / NEEDS_WORK /
  BLOCKED, preamble-token-resistance, missing SUMMARY fails closed to
  BLOCKED), artifact extraction, REQUIRED FIXES section isolation,
  fingerprint stability across revisions, and pydantic param validation.
- tests/tools/test_implement_judge_load.py (new): regression test for
  the loader path - confirms ImplementAndJudgeTool instantiates through
  PythinkerToolset._load_tool with the same dependency-injection as
  AgentTool so a default-agent startup cannot regress.
- tests/test_judge_branding.py (new): asserts the judge system prompt
  mentions the minimum-diff rubric / reduction ladder so the always-on
  policy does not drift away from the bundled skills.
- tests/core/test_load_agent.py: extend test_load_tools_invalid to
  assert the aggregated error names the reason ('class or module not
  found'), and add test_load_tools_aggregates_constructor_errors to
  cover the new per-tool exception path with a monkeypatched _load_tool
  that raises - the same per-tool-catch handles class-miss, module-miss,
  and constructor-exception cases.
…set error reporting, and judge rubric

User-facing bullets under ## Unreleased:
- InvalidToolError now names the failing tool and the reason (per-tool
  aggregation + 'Did you mean' hint + per-tool exception catch).
- Auto-chain implementer -> judge via ImplementAndJudge tool for
  non-trivial scoped edits; two implementer invocations is the hard cap.
- Judge adopts the minimum-diff rubric dimension applied uniformly
  across review modes.
- Bundled judge-minimum-diff and judge-overengineering-review default
  skills replace ad-hoc system prompt prose.

The 'changelog-entry-required' CI check requires this for any change to
shipped paths; without it the PR fails before review.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e14c058f-86a8-4342-9392-543f4f442a39

📥 Commits

Reviewing files that changed from the base of the PR and between 2b66c09 and 1737974.

📒 Files selected for processing (4)
  • src/pythinker_code/tools/agent/__init__.py
  • tests/core/test_implement_judge_chain.py
  • tests/core/test_plan_mode_injection_provider.py
  • tests_e2e/test_wire_protocol.py

📝 Walkthrough

Walkthrough

Introduces ImplementAndJudgeTool, a sequential implementer→judge chain with one optional revision cycle, wired into the default agent. Ships two new judge skill rubrics (minimum-diff and overengineering-review) and updates judge.yaml/system.md orchestration guidance. Fixes OpenAI Responses to unconditionally normalize role=systemrole=developer. Improves InvalidToolError with per-tool failure reasons and "Did you mean?" suggestions. Refines background-task UI refresh gating via activity timestamps and replaces the stale "Update available" banner chip with a restart-to-apply message after successful in-session updates.

Changes

ImplementAndJudge chain, judge rubrics, agent wiring, and comprehensive tests

Layer / File(s) Summary
Judge skill rubrics and agent prompt updates
src/pythinker_code/skills/judge-minimum-diff/SKILL.md, src/pythinker_code/skills/judge-overengineering-review/SKILL.md, src/pythinker_code/agents/default/judge.yaml, src/pythinker_code/agents/default/system.md
Adds two SKILL.md rubrics defining the minimum-diff reduction ladder and overengineering-review checklist; adds a context-gate artifact requirement and minimum-diff workflow rule to judge.yaml; updates system.md to default to ImplementAndJudge for scoped edits while reserving bare judge for non-implementation reviews.
ImplementAndJudgeTool orchestration implementation
src/pythinker_code/tools/agent/__init__.py
Adds 492 lines implementing ImplementAndJudgeTool: regex-anchored verdict/artifact/required-fixes parsers (fail-closed to BLOCKED), fingerprinting independent of revision index, implementer/judge prompt builders, approval-gated sequential execution with one-revision loop on NEEDS_WORK, and the ImplementAndJudge module export.
Default agent wiring and toolset error reporting
src/pythinker_code/agents/default/agent.yaml, src/pythinker_code/soul/toolset.py
Registers ImplementAndJudge in agent.yaml tools list; upgrades load_tools to aggregate (tool_path, reason) tuples and emit a structured InvalidToolError; adds per-tool exception logging and one-line reason summarization; improves _load_tool to suggest close class-name matches when tool classes are not found.
ImplementAndJudge unit and orchestration tests
tests/core/test_implement_judge_chain.py
Covers verdict parsing (fail-closed, case-insensitive, anchored to SUMMARY), artifact/required-fixes extraction with boundary isolation, fingerprint stability, prompt assembly, parameter cap validation, and end-to-end async orchestration including revision looping, cap enforcement, and all error/precondition paths.
Agent spec snapshots, toolset load, and test coverage updates
tests/core/test_agent_spec.py, tests/core/test_default_agent.py, tests/tools/test_implement_judge_load.py, tests/core/test_load_agent.py, tests/utils/test_pyinstaller_utils.py, tests_e2e/test_wire_protocol.py
Updates all agent spec snapshots to include ImplementAndJudge in subagent tool lists; adds toolset DI load regression; enhances per-tool failure reason assertions and constructor-error aggregation; updates PyInstaller data list for judge skill SKILL.md files and e2e wire-protocol snapshots for judge skill slash commands.
Branding guard test to prevent upstream identifier leakage
tests/test_judge_branding.py
Adds regex patterns for upstream brand identifiers, an authoritative target-file list covering judge YAML/MD/Python/tests, and a module-scoped fixture that aggregates hits; includes tests to fail on any upstream identifier appearance in owned content and to detect stale target entries.

OpenAI Responses system-role normalization

Layer / File(s) Summary
Unconditional system-to-developer role conversion
packages/pythinker-core/src/pythinker_core/contrib/chat_provider/openai_responses.py, packages/pythinker-core/tests/api_snapshot_tests/test_openai_responses.py
Removes is_openai_model guard from both generate and _convert_message; always converts role=system to role=developer regardless of model name. Adds parametrized tests across gpt-4/gpt-3.5-turbo/other variants and a mid-session history carry-over regression test for model switches.

Background-task UI refresh gating and update banner logic

Layer / File(s) Summary
Background task activity tracking and refresh rate gating
src/pythinker_code/ui/shell/prompt.py, tests/ui_and_conv/test_visualize_running_prompt.py
Adds _BG_QUIET_THRESHOLD_S constant, _bg_last_active_at stamping on background work spawn and token flow, _bg_refresh_active() helper to gate fast refresh within activity window, and updates _refresh() loop to require both tasks and recent activity. Changes pure-bash-only background to static "Running in background…" label instead of agent verb spinner; preserves spinner for mixed bash+agent. Covers all state transitions and label variants in tests.
Welcome banner restart-to-apply logic after successful update
src/pythinker_code/ui/shell/__init__.py, tests/ui_and_conv/test_shell_welcome_info.py
Extends _welcome_banner_chip() to detect in-session update completion via UpdateJobState.UPDATED and matching target version (excluding smoke-check failures); renders restart-to-apply message instead of stale "Update available" chip. Adds two banner regression tests (successful update with restart message; smoke-check-failed retaining Update available) and patches existing test to mock read_update_status.
Changelog documentation of all product changes
CHANGELOG.md
Documents all unreleased changes: improved InvalidToolError messaging, implementer→judge auto-chaining with two-invocation cap, judge minimum-diff rubric, judge-branded default skills, OpenAI Responses normalization, bash background task UI, and update banner behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • Pythoughts-labs/pythinker-code#43: Introduced the judge subagent configuration in agent.yaml/judge.yaml that this PR extends with context-gate artifact requirements and minimum-diff workflow rules.
  • Pythoughts-labs/pythinker-code#81: Established the coder <coding_artifact> JSON block contract that this PR's ImplementAndJudgeTool directly parses and validates as its intermediary communication channel.

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.55% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (feat: description) and clearly describes the three main changes: ImplementAndJudge chain, per-tool error reporting, and minimum-diff judge rubric.
Description check ✅ Passed Description comprehensively covers all changes with clear summaries of ImplementAndJudge tool, InvalidToolError improvements, judge rubric adoption, bundled skills, and test plan. All template sections are filled with substantive content.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/implement-judge-chain

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.35897% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/soul/toolset.py 57.89% 8 Missing ⚠️
src/pythinker_code/ui/shell/prompt.py 88.88% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…lan doc

- src/pythinker_code/tools/agent/__init__.py, tests/core/test_implement_judge_chain.py:
  s/unparseable/unparsable/ in two docstrings. The crate-ci/typos CI check
  flags 'unparseable' as a typo; 'unparsable' is the standard form.
- tasks/implementer-judge-chain-plan.md: delete the tracked plan doc.
  It leaked the upstream 'ponytail' brand and is not part of the shipped
  artifact.
- tests/test_judge_branding.py: remove the now-deleted plan doc from
  _BRAND_GUARD_TARGETS so test_brand_guard_targets_exist does not flag a
  stale entry.
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py

@coderabbitai coderabbitai Bot 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.

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 `@src/pythinker_code/agents/default/judge.yaml`:
- Line 43: The Minimum-diff rule in the judge.yaml file currently permits new
dependencies with only a one-line justification, which is inconsistent with the
repository's enforced dependency policy that requires maintainer approval plus
explicit security/approval justification. Update the Minimum-diff dependency
rule text (in the section starting with "Minimum-diff:") to require maintainer
approval and explicit inline justification/security/approval fields instead of
just a one-line justification, ensuring it aligns with the zero-new-bundled-deps
workflow that CI actually enforces. Also apply the same correction to any
mirrored skill text that references this rule.

In `@src/pythinker_code/tools/agent/__init__.py`:
- Around line 1138-1144: The documentation for the max_revisions field claims
higher values are "clamped" to MAX_IMPLEMENT_JUDGE_REVISIONS, but the schema
constraint le=MAX_IMPLEMENT_JUDGE_REVISIONS will reject values above the cap
instead of clamping them. Either remove the word "clamped" from the field
description to accurately reflect the rejection behavior, or implement actual
clamping logic in the parsing mechanism (using a validator or field_validator)
that accepts values above the cap and silently clamps them to
MAX_IMPLEMENT_JUDGE_REVISIONS while keeping the description as-is.
- Around line 1160-1161: The orchestration fingerprint dictionary includes
revision_index which causes the fingerprint to change for each revision attempt,
triggering re-approval requests on NEEDS_WORK retries. Remove revision_index
from the fingerprint dictionary (around line 1160 where the fingerprint is
constructed) so that all revisions of the same orchestration share the same
fingerprint and approval, preventing redundant approval prompts and maintaining
consistent orchestration behavior across retries.
- Around line 1172-1176: The verdict parsing in the code searches for the
verdict token starting from the end of the SUMMARY match to the end of output,
which allows verdicts from sections after SUMMARY to be accepted as valid. To
fix this, change the _IMPLEMENT_JUDGE_VERDICT_RE.search() call to search only
within the SUMMARY section itself by either searching within the summary.group()
text or limiting the search range to the SUMMARY match's boundaries (from
summary.start() to summary.end()). This ensures the verdict token must appear
within the SUMMARY section to be accepted, otherwise the function will correctly
return BLOCKED as the fail-closed default.

In `@tests/core/test_implement_judge_chain.py`:
- Line 53: The spell-check CI validation is failing on the word "unparseable"
used in the comment describing the judge chain behavior. Change the word
"unparseable" to "unparsable" in the comment that states "The chain must never
silently treat an unparseable judge reply as a pass" to use the correct spelling
and unblock the pipeline.

In `@tests/ui_and_conv/test_visualize_running_prompt.py`:
- Around line 2347-2360: Remove the monkeypatch of the private
_background_status_metadata helper method in the
test_background_status_truncates_after_dropping_metadata function, as it creates
unnecessary coupling to implementation details and triggers the Ruff ARG005
warning for the unused now parameter. Instead, configure the test to trigger the
truncation behavior through legitimate token-state inputs on the
CustomPromptSession object (such as _latest_todos or other public-facing state
properties), and verify the truncation behavior through the same rendered output
assertions without mocking internal methods.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 462fe7e6-b112-4c7c-9844-52392a9278b9

📥 Commits

Reviewing files that changed from the base of the PR and between bf2f703 and 92094df.

⛔ Files ignored due to path filters (1)
  • tasks/implementer-judge-chain-plan.md is excluded by !tasks/**
📒 Files selected for processing (21)
  • CHANGELOG.md
  • packages/pythinker-core/src/pythinker_core/contrib/chat_provider/openai_responses.py
  • packages/pythinker-core/tests/api_snapshot_tests/test_openai_responses.py
  • src/pythinker_code/agents/default/agent.yaml
  • src/pythinker_code/agents/default/judge.yaml
  • src/pythinker_code/agents/default/system.md
  • src/pythinker_code/skills/judge-minimum-diff/SKILL.md
  • src/pythinker_code/skills/judge-overengineering-review/SKILL.md
  • src/pythinker_code/soul/toolset.py
  • src/pythinker_code/tools/agent/__init__.py
  • src/pythinker_code/ui/shell/__init__.py
  • src/pythinker_code/ui/shell/prompt.py
  • tests/core/test_agent_spec.py
  • tests/core/test_default_agent.py
  • tests/core/test_implement_judge_chain.py
  • tests/core/test_load_agent.py
  • tests/test_judge_branding.py
  • tests/tools/test_implement_judge_load.py
  • tests/ui_and_conv/test_shell_welcome_info.py
  • tests/ui_and_conv/test_visualize_running_prompt.py
  • tests/utils/test_pyinstaller_utils.py

Comment thread src/pythinker_code/agents/default/judge.yaml
Comment thread src/pythinker_code/tools/agent/__init__.py
Comment thread src/pythinker_code/tools/agent/__init__.py Outdated
Comment thread src/pythinker_code/tools/agent/__init__.py
Comment thread tests/core/test_implement_judge_chain.py Outdated
Comment thread tests/ui_and_conv/test_visualize_running_prompt.py
Address ImplementAndJudge review findings on PR #178:

- Bound _parse_judge_verdict to the SUMMARY section so a stray
  PASS/NEEDS_WORK/BLOCKED token in a later section (e.g. EVIDENCE) can no
  longer be read as the verdict — fail closed to BLOCKED instead of leaking
  a false PASS on a quality gate.
- Drop revision_index from the orchestration fingerprint so a NEEDS_WORK
  revision reuses the chain's single approval instead of re-prompting
  mid-chain after the implementer has already written, matching
  _run_agents_fingerprint.
- Correct the max_revisions field docs: values above the cap are rejected
  at validation, not clamped.

Adds regression tests for the later-section verdict leak and single-grant
revision reuse.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pythinker_code/tools/agent/__init__.py (1)

1451-1454: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset final state when a child fails after an earlier revision.

On revision-path failures, stale state from the previous judge/implementer can leak into the final result: an implementer error after NEEDS_WORK leaves last_verdict/last_artifact from the prior attempt, and a judge error can leave a stale verdict_match. Fail closed to BLOCKED and clear stale fields in both child-error branches.

As per coding guidelines, observable output must distinguish failure from partial success, and C06 flags returns that blur failure states.

Proposed fix
             last_implementer_output = self._child_result_output(impl_result)
             if impl_result.is_error:
-                last_implementer_error = impl_result.message
+                error_message = impl_result.message or "implementer subagent failed"
+                last_implementer_error = error_message
+                last_verdict = "BLOCKED"
+                last_verdict_raw = None
+                last_required_fixes = "judge not run because implementer subagent failed"
+                last_artifact = None
                 break
@@
             if judge_result.is_error:
                 # Treat a judge failure as BLOCKED for the current revision
                 # and surface it — fail closed rather than silently pass.
                 last_verdict = "BLOCKED"
-                last_required_fixes = f"judge subagent error: {judge_result.message}"
+                last_verdict_raw = None
+                error_message = judge_result.message or "judge subagent failed"
+                last_required_fixes = f"judge subagent error: {error_message}"
                 break

Also applies to: 1470-1475

🤖 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 `@src/pythinker_code/tools/agent/__init__.py` around lines 1451 - 1454, When an
implementer error occurs in the section around line 1451-1454 (in the if
impl_result.is_error block following the last_implementer_output assignment),
clear the stale state variables last_verdict and last_artifact to prevent them
from leaking into the final result. Similarly, in the judge error handling
section around lines 1470-1475, clear the stale verdict_match variable. In both
error branches, ensure the final state is set to BLOCKED to properly distinguish
failure from partial success, rather than allowing stale values from previous
revision attempts to persist in the final result.

Source: Coding guidelines

🤖 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 `@src/pythinker_code/tools/agent/__init__.py`:
- Around line 1451-1454: When an implementer error occurs in the section around
line 1451-1454 (in the if impl_result.is_error block following the
last_implementer_output assignment), clear the stale state variables
last_verdict and last_artifact to prevent them from leaking into the final
result. Similarly, in the judge error handling section around lines 1470-1475,
clear the stale verdict_match variable. In both error branches, ensure the final
state is set to BLOCKED to properly distinguish failure from partial success,
rather than allowing stale values from previous revision attempts to persist in
the final result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0eb16344-8e26-4a3e-89c0-2e101dbbd107

📥 Commits

Reviewing files that changed from the base of the PR and between 18e9f5c and 2b66c09.

📒 Files selected for processing (2)
  • src/pythinker_code/tools/agent/__init__.py
  • tests/core/test_implement_judge_chain.py

elkaix added 2 commits June 23, 2026 15:41
…napshot

- The wire handshake snapshot (tests_e2e/test_wire_protocol.py) was stale:
  this PR added the judge-minimum-diff and judge-overengineering-review
  bundled skills but never regenerated the handshake skills list, leaving
  CI red on test_initialize_handshake / test_initialize_external_tool_conflict.
  Regenerated to include both new skills.

- ImplementAndJudge: an implementer error on the revision now fails closed to
  BLOCKED, clearing the prior revision's stale NEEDS_WORK verdict and artifact
  so they can't leak into the final result (mirrors the judge-error branch).
  Adds a regression test.
test_pending_activation_returns_full asserted the plan-absent (full reminder)
branch but pointed plan_path at a hardcoded /tmp/plan.md. The provider checks
plan_path.exists(), so on any machine where that file happens to exist the test
hit the reentry branch and failed (green only on a clean /tmp, e.g. CI). Use a
tmp_path file that is never created, mirroring the sibling reentry test.
Comment thread src/pythinker_code/tools/agent/__init__.py
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