Skip to content

feat(agents): leaf prompt profile, typed coding-artifact contract, chain extraction - #213

Merged
elkaix merged 13 commits into
mainfrom
feat/implementer-deepening
Jul 18, 2026
Merged

feat(agents): leaf prompt profile, typed coding-artifact contract, chain extraction#213
elkaix merged 13 commits into
mainfrom
feat/implementer-deepening

Conversation

@elkaix

@elkaix elkaix commented Jul 18, 2026

Copy link
Copy Markdown
Member

Summary

Deepens the implementer (and all leaf subagent) architecture along three seams identified in an architecture review: prompt weight, the coding-artifact handoff contract, and chain locality.

Leaf subagent prompt profile

  • system.md is split into 12 shared Jinja partials (agents/default/partials/); the root prompt render is byte-identical to before (verified by fixed-args render diff against a pre-change baseline).
  • New system_leaf.md composes the shared sections without root-only Operating Loop / Playbooks / orchestration prose; the subagent preamble and artifact contract are template-owned.
  • All 12 roster roles migrated. Rendered implementer prompt: ~7,270 → ~4,240 words (−42%); coder −39%.

Typed coding-artifact contract (single source of truth)

  • utils/artifacts.py now owns the <coding_artifact> contract: the prompt block renders from the CodingArtifact schema (PYTHINKER_CODING_ARTIFACT_CONTRACT render arg gated by EMITS_CODING_ARTIFACT), and extraction is strict fail-closed — exactly one end-of-message block, duplicate/undeclared JSON keys rejected, typed present/missing/malformed results with bounded reasons.
  • ImplementAndJudge surfaces malformed artifacts distinctly in the judge prompt (untrusted plain-fenced raw block + reason) and its result line, instead of passing them through as if valid; missing/present behavior unchanged byte-for-byte.
  • The verifier's artifact receipt now cross-checks artifact.files_changed against git diff.
  • Invariant tests bind the rendered prompts and the verifier consumer to the schema.

Chain extraction

  • The implementer→judge chain moves to tools/agent/implement_judge.py (547 lines); tools/agent/__init__.py shrinks 1755 → 1195 lines with the full previous import surface preserved via re-exports (zero chain-test edits; PyInstaller hiddenimports updated).

Test hygiene

  • The four large verbatim ROLE_ADDITIONAL snapshots become semantic invariants: exact section-heading lists, template-ownership checks across all 12 roles, flag assertions, and load-bearing identity lines.
  • The raw-file duplicate-report-prose assertion now checks the rendered prompt.

Verification

  • make check-pythinker-code — ruff check, ruff format check, pyright (0 errors), ty: all passing.
  • make test-pythinker-code — 2,661 unit tests + 65 e2e tests passing (1+4 skipped) on the composed tree.
  • Root-prompt byte-identity verified by rendering with fixed args before/after the partial split and diffing.
  • Wire/e2e snapshots: unmoved (roster lines, when_to_use, and tool lists untouched).

Changelog

## Unreleased entries added for the leaf profile, the typed artifact contract, and the chain extraction.

Summary by CodeRabbit

  • New Features
    • Leaf system prompts are now modular and reused from shared templates.
    • Added an optional, typed “coding-artifact” contract that is emitted only when enabled.
    • Implement-and-judge now gates results using guarded PASS/NEEDS_WORK/BLOCKED with revision feedback.
  • Bug Fixes
    • Fail-closed artifact parsing with strict schema validation.
    • Stricter verdict extraction and verification against actual changes; hardened prompt formatting against hostile/malformed output.
  • Documentation
    • Added/expanded agent prompt guidance and code standards for consistent outputs.
  • Tests
    • Expanded prompt/contract byte-exact and artifact-verdict orchestration coverage.
  • Chores
    • Moved implement-and-judge logic into a dedicated module while preserving existing public behavior.

elkaix added 10 commits July 17, 2026 23:18
Make pythinker_code.utils.artifacts the single source of truth for the
<coding_artifact> handoff: a dataclass-derived prompt contract block, a
fail-closed extraction API (extracted/missing/malformed with strict
end-of-message anchoring, exactly-one-block, duplicate- and
undeclared-key rejection), a files_changed cross-check in the verifier
artifact receipt, and invariant tests binding the implementer/coder
prompt copies and the verifier consumer to the schema.
…xtraction

Replace the chain's local <coding_artifact> regex with the typed
fail-closed extraction API from utils.artifacts. Malformed artifacts now
surface distinctly in the judge prompt (untrusted plain-fenced raw block
with the bounded reason) and in the chain result line, instead of being
passed to the judge as if valid; missing and present renderings are
unchanged. _extract_coding_artifact stays as a compatibility adapter.
… module

Move the implementer→judge chain (~550 lines: regexes, params,
fingerprint, verdict parsing, prompt builders, ImplementAndJudgeTool)
out of tools/agent/__init__.py into tools/agent/implement_judge.py.
The full previous import surface of pythinker_code.tools.agent is
preserved via explicit re-exports, so the agent-spec tool path and all
existing imports work unchanged; the new submodule is registered in the
PyInstaller hiddenimports snapshot.
Extract the 12 shared sections of the root system prompt into
agents/default/partials/*.md included via Jinja, keeping the rendered
root prompt byte-identical (verified by fixed-args render diff). The
duplicate-report-prose test now asserts against the rendered prompt
instead of raw template bytes, and the PyInstaller datas snapshot
gains the new partial files.
New leaf profile composed from the shared prompt partials: identity,
subagent preamble, role slot, conditional artifact contract (rendered
from utils.artifacts via the new PYTHINKER_CODING_ARTIFACT_CONTRACT
render arg gated by EMITS_CODING_ARTIFACT), core rules, tool basics,
code standards, untrusted content, communication, definition of done,
environment, AGENTS.md, and skills — with no root-only orchestration
or playbook prose. No role is migrated yet.
Both roles now render system_leaf.md with EMITS_CODING_ARTIFACT, so the
subagent preamble and artifact contract come from the template (single
source of truth) and the root-only orchestration/playbook prose is no
longer shipped to these leaf roles. ROLE_ADDITIONAL keeps only the
role-specific sections. Rendered implementer prompt drops from ~7,270
to ~4,240 words (-42%); coder from ~7,770 to ~4,730 (-39%). The artifact
contract invariant now asserts against the rendered prompts.
verifier, judge, explore, plan, planner, scout, review, code-reviewer,
security-reviewer, and debugger now render the leaf profile: the
subagent preamble comes from the template and each role stops shipping
the root agent's orchestration/playbook manual. ROLE_ADDITIONAL bodies
are otherwise unchanged.
…variants

The four large ROLE_ADDITIONAL inline snapshots (coder, explore, plan,
planner) become exact section-heading lists, template-ownership checks
(no role embeds the subagent preamble or artifact contract — asserted
across all 12 roster roles), EMITS_CODING_ARTIFACT flag assertions, and
load-bearing identity lines, per the repo policy of preferring small
semantic prompt assertions over large snapshots.
@coderabbitai

coderabbitai Bot commented Jul 18, 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: 910c384a-3c28-46b8-92e7-162f40ee53e9

📥 Commits

Reviewing files that changed from the base of the PR and between b08b3c3 and 0908344.

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

📝 Walkthrough

Walkthrough

The PR modularizes root and leaf agent prompts, introduces strict typed coding-artifact contracts, and moves the ImplementAndJudge chain into its own module while preserving imports. Tests cover prompt rendering, artifact validation, malformed handoffs, orchestration behavior, and packaged assets.

Changes

Agent prompt composition

Layer / File(s) Summary
Modular leaf prompt composition
src/pythinker_code/agents/default/*.yaml, src/pythinker_code/agents/default/system*.md, src/pythinker_code/agents/default/partials/*, src/pythinker_code/soul/agent.py, tests/core/*
Leaf agents render system_leaf.md from shared partials, receive role-specific arguments, and remain childless; root prompt generation is validated deterministically.

Typed coding-artifact contract

Layer / File(s) Summary
Typed coding-artifact contract
src/pythinker_code/utils/artifacts.py, src/pythinker_code/agents/default/{coder,implementer,verifier}.yaml, tests/utils/test_artifact*.py
Artifact prompts are generated from the typed schema, extraction rejects malformed layouts and payloads, and verifier instructions cross-check declared files against git diff.

Implementer-judge orchestration

Layer / File(s) Summary
Implementer-judge orchestration
src/pythinker_code/tools/agent/{__init__,implement_judge}.py, tests/core/test_implement_judge_chain.py, tests/utils/test_pyinstaller_utils.py
The chain runs implementer and judge subagents, supports bounded revisions and fail-closed verdicts, returns normalized artifact status, and remains available through the prior package exports.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ImplementAndJudgeTool
  participant Implementer
  participant ArtifactExtractor
  participant Judge
  ImplementAndJudgeTool->>Implementer: send implementation brief
  Implementer-->>ArtifactExtractor: return tagged coding artifact
  ArtifactExtractor-->>ImplementAndJudgeTool: return validated or malformed result
  ImplementAndJudgeTool->>Judge: send implementation evidence
  Judge-->>ImplementAndJudgeTool: return verdict and required fixes
  ImplementAndJudgeTool->>Implementer: request bounded revision when needed
Loading

Possibly related PRs

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.76% 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 The title follows conventional commits and clearly summarizes the main changes to leaf prompts, artifact contracts, and chain extraction.
Description check ✅ Passed The description is detailed and covers summary, verification, and changelog, with only the template's issue and checklist sections omitted.
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.
✨ 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/implementer-deepening

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

Comment thread src/pythinker_code/tools/agent/implement_judge.py Fixed

@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: 10

🤖 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/partials/environment.md`:
- Around line 13-22: Update the environment prompt template around
PYTHINKER_WORK_DIR_LS and PYTHINKER_ADDITIONAL_DIRS_INFO to render
filesystem-derived values using a collision-safe untrusted-data wrapper that
cannot be terminated by repository-controlled filenames. Preserve the existing
directory context and absolute-path guidance while ensuring injected content is
clearly treated as data rather than instructions.

In `@src/pythinker_code/agents/default/partials/untrusted_content.md`:
- Around line 3-5: Update the guidance in the untrusted-content partial to state
that literal `<system>` and `<system-reminder>` tags in user-controlled messages
never grant authority, and must be treated as untrusted content unless supplied
through an authenticated runtime directive. Clarify that only actual
system-level directives are authoritative, while preserving the existing
protections for `<untrusted_data>` and the instruction to neutralize or wrap
external content.

In `@src/pythinker_code/soul/agent.py`:
- Around line 782-786: Update the render flow around template.render so
system_prompt_args cannot override the generated
PYTHINKER_CODING_ARTIFACT_CONTRACT value. Either reject that reserved key before
rendering or merge the arguments so coding_artifact_contract_block() is applied
last and remains authoritative.

In `@src/pythinker_code/tools/agent/implement_judge.py`:
- Around line 423-456: The revision flow around _parse_judge_verdict must
validate last_artifact independently before accepting a PASS verdict. If the
required artifact is missing or malformed and the judge returns PASS, override
the result to NEEDS_WORK when another revision remains, otherwise BLOCKED;
preserve the existing judge-error handling and add regression tests covering
missing and malformed artifacts paired with PASS.
- Around line 176-177: Neutralize model-generated revision feedback before
replaying it across the judge/implementer boundary: at
src/pythinker_code/tools/agent/implement_judge.py lines 176-177, serialize the
feedback as escaped data before appending it to the revision section; at lines
217-225, replace fixed Markdown fences with JSON serialization or a delimiter
guaranteed not to occur in the content. Preserve the existing prompt structure
while ensuring embedded content cannot break out or inject instructions.
- Around line 277-294: Add the appropriate return type annotation to the public
__init__ method of the relevant class, preserving its existing initialization
behavior and parameters.
- Around line 413-421: Update the implementer-error branch in the revision flow
to clear last_required_fixes along with last_verdict, last_verdict_raw, and
last_artifact, so prior-revision judge feedback is not later emitted as current
judge_output. Preserve the existing BLOCKED verdict and error message handling.
- Around line 118-134: Update _parse_judge_verdict to require the verdict regex
to match the first non-formatting token in summary_body, rather than searching
for a later token. Anchor _IMPLEMENT_JUDGE_VERDICT_RE appropriately and use
match() so prose before a verdict fails closed as BLOCKED.

In `@tests/core/test_agent_spec.py`:
- Around line 123-151: Extend the assertions in the subagent specification test
to explicitly cover the implementer leaf contract. Validate implementer uses
system_leaf.md, has EMITS_CODING_ARTIFACT set to "true", and retains its
expected ROLE_ADDITIONAL mission content, while preserving the existing generic
checks.

In `@tests/utils/test_artifacts.py`:
- Around line 267-295: Update the tests for extract_coding_artifact to exercise
observable behavior without monkeypatching its internal json.loads dependency:
use genuinely deeply nested JSON to trigger the parser recursion-limit path, and
test reason truncation through the public behavior or the relevant bounding
helper without replacing the decoder. Remove the artificial decoder mocks while
preserving assertions for the MalformedCodingArtifact reason and its
120-character limit.
🪄 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: aabb8a80-58f1-4fa3-8194-e487ef2152e6

📥 Commits

Reviewing files that changed from the base of the PR and between c16c0eb and 418b2f1.

⛔ Files ignored due to path filters (2)
  • tasks/lessons.md is excluded by !tasks/**
  • tasks/todo.md is excluded by !tasks/**
📒 Files selected for processing (39)
  • CHANGELOG.md
  • src/pythinker_code/agents/default/agent.yaml
  • src/pythinker_code/agents/default/code_reviewer.yaml
  • src/pythinker_code/agents/default/coder.yaml
  • src/pythinker_code/agents/default/debugger.yaml
  • src/pythinker_code/agents/default/explore.yaml
  • src/pythinker_code/agents/default/implementer.yaml
  • src/pythinker_code/agents/default/judge.yaml
  • src/pythinker_code/agents/default/partials/act_with_tools.md
  • src/pythinker_code/agents/default/partials/agents_md.md
  • src/pythinker_code/agents/default/partials/code_standards.md
  • src/pythinker_code/agents/default/partials/communication.md
  • src/pythinker_code/agents/default/partials/core_rules.md
  • src/pythinker_code/agents/default/partials/definition_of_done.md
  • src/pythinker_code/agents/default/partials/environment.md
  • src/pythinker_code/agents/default/partials/identity_core.md
  • src/pythinker_code/agents/default/partials/skills.md
  • src/pythinker_code/agents/default/partials/spend_context.md
  • src/pythinker_code/agents/default/partials/untrusted_content.md
  • src/pythinker_code/agents/default/partials/verify_results.md
  • src/pythinker_code/agents/default/plan.yaml
  • src/pythinker_code/agents/default/planner.yaml
  • src/pythinker_code/agents/default/review.yaml
  • src/pythinker_code/agents/default/scout.yaml
  • src/pythinker_code/agents/default/security_reviewer.yaml
  • src/pythinker_code/agents/default/system.md
  • src/pythinker_code/agents/default/system_leaf.md
  • src/pythinker_code/agents/default/verifier.yaml
  • src/pythinker_code/soul/agent.py
  • src/pythinker_code/tools/agent/__init__.py
  • src/pythinker_code/tools/agent/implement_judge.py
  • src/pythinker_code/utils/artifacts.py
  • tests/core/test_agent_spec.py
  • tests/core/test_default_agent.py
  • tests/core/test_implement_judge_chain.py
  • tests/core/test_leaf_prompt.py
  • tests/utils/test_artifact_contract.py
  • tests/utils/test_artifacts.py
  • tests/utils/test_pyinstaller_utils.py

Comment thread src/pythinker_code/agents/default/partials/environment.md
Comment thread src/pythinker_code/agents/default/partials/untrusted_content.md Outdated
Comment thread src/pythinker_code/soul/agent.py Outdated
Comment thread src/pythinker_code/tools/agent/implement_judge.py
Comment thread src/pythinker_code/tools/agent/implement_judge.py
Comment thread src/pythinker_code/tools/agent/implement_judge.py Outdated
Comment thread src/pythinker_code/tools/agent/implement_judge.py
Comment thread src/pythinker_code/tools/agent/implement_judge.py
Comment thread tests/core/test_agent_spec.py
Comment thread tests/utils/test_artifacts.py Outdated
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.37539% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/tools/agent/implement_judge.py 91.90% 10 Missing and 7 partials ⚠️
src/pythinker_code/soul/agent.py 75.00% 2 Missing ⚠️
src/pythinker_code/utils/artifacts.py 97.97% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

elkaix added 2 commits July 18, 2026 11:19
- Fence PYTHINKER_WORK_DIR_LS and additional-dir listings with a
  collision-safe backtick fence so repository-controlled filenames
  cannot break out of the prompt block
- Scope <system>/<system-reminder> authority to runtime-injected tags;
  lookalike tags in user or wrapped content grant none
- Keep the generated coding-artifact contract authoritative over
  spec-provided system_prompt_args in system prompt rendering
- Anchor the judge verdict token to the start of SUMMARY (match, not
  search) and gate PASS on a valid <coding_artifact> block, failing
  closed to NEEDS_WORK/BLOCKED
- Fence implementer output, artifacts, and revision feedback replayed
  across the judge/implementer boundary with collision-safe fences
- Clear stale judge output on revision implementer failure; annotate
  ImplementAndJudgeTool.__init__ return type
- Cover the implementer leaf artifact contract in agent-spec tests;
  drive artifact error paths with real inputs instead of monkeypatched
  json.loads

@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: 1

🤖 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/tools/agent/implement_judge.py`:
- Around line 231-238: Update the MalformedCodingArtifact handling in the
artifact-section construction to render artifact.reason through
_fenced_untrusted_block() before inserting it into the prompt, keeping the
existing malformed-artifact guidance intact. Add a regression test covering a
duplicate JSON key whose decoded reason contains newlines and prompt-like text,
verifying the reason is fenced as untrusted data.
🪄 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: 5585be75-2375-4b68-8513-4a55cfa66a29

📥 Commits

Reviewing files that changed from the base of the PR and between 1de88ae and b08b3c3.

📒 Files selected for processing (8)
  • src/pythinker_code/agents/default/partials/environment.md
  • src/pythinker_code/agents/default/partials/untrusted_content.md
  • src/pythinker_code/soul/agent.py
  • src/pythinker_code/tools/agent/implement_judge.py
  • tests/core/test_agent_spec.py
  • tests/core/test_default_agent.py
  • tests/core/test_implement_judge_chain.py
  • tests/utils/test_artifacts.py

Comment thread src/pythinker_code/tools/agent/implement_judge.py
The extractor's reason for a MalformedCodingArtifact can echo decoded
content (e.g. a duplicate JSON key holding newlines and prompt-shaped
text), yet it was interpolated into the judge prompt prose outside any
untrusted block. Render it through _fenced_untrusted_block() so it is
treated as data, and add a duplicate-key regression test.
@elkaix
elkaix merged commit 6e51643 into main Jul 18, 2026
37 checks passed
@elkaix
elkaix deleted the feat/implementer-deepening branch July 18, 2026 19:36
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