From 38995c3d3f47e9508bae250979e9a3d6687113c3 Mon Sep 17 00:00:00 2001 From: David Hyrule Date: Mon, 15 Jun 2026 21:53:38 +0200 Subject: [PATCH] fix(backend): give the implementer the full issue body, not just the title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit assemble_backend_prompt built the "## Request" section as `intent or request`. The planner sets `intent` to the issue's truncated first line (often just the title), so the `or` short-circuited and the full request body — action items, constraints, acceptance — never reached the backend. The loop was effectively implementing against the issue title alone. task_spec_from_state already carries the full body in TaskSpec.request; prefer it over the one-line intent (fall back to intent, then a placeholder). Surfaced dogfooding the VPS launch-proof issue (hyrule-cloud#28): the loop produced a title-only plan because it never saw the detailed spec. Note: the planner's formal acceptance_criteria remain a generic template (graded by role judgment + human PR review); deriving criteria from the issue body is a separate, larger enhancement. Validation: ruff clean, mypy --strict clean, 176 passed (+1), evals 15/15. Co-Authored-By: Claude Opus 4.8 --- src/hyrule_engineering_loop/backend.py | 5 ++++- tests/test_phase20_agent_backend.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/hyrule_engineering_loop/backend.py b/src/hyrule_engineering_loop/backend.py index e3fe7cd..ba44409 100644 --- a/src/hyrule_engineering_loop/backend.py +++ b/src/hyrule_engineering_loop/backend.py @@ -249,7 +249,10 @@ def assemble_backend_prompt(task_spec: TaskSpec, constraints: BackendConstraints "", "## Request", "", - task_spec.intent.rstrip() or task_spec.request.rstrip() or "(no request text supplied)", + # Prefer the full request body (the issue text) over the planner's + # one-line intent summary, so the backend sees the complete spec — + # action items, constraints, and acceptance — not just the title. + task_spec.request.rstrip() or task_spec.intent.rstrip() or "(no request text supplied)", ] if task_spec.acceptance_criteria: lines.extend(["", "## Acceptance criteria (the definition of done)", ""]) diff --git a/tests/test_phase20_agent_backend.py b/tests/test_phase20_agent_backend.py index b024335..e1b442e 100644 --- a/tests/test_phase20_agent_backend.py +++ b/tests/test_phase20_agent_backend.py @@ -102,6 +102,22 @@ def test_pi_backend_allows_only_model_provider_keys(monkeypatch: pytest.MonkeyPa assert set(env_hygiene_violations(env)) == PI_PROVIDER_ENV_NAMES +def test_prompt_includes_full_request_body_not_just_intent() -> None: + # The planner's intent is a truncated one-liner (often just the title); the + # backend must still see the full issue body (action items + constraints). + spec = TaskSpec( + change_id="FULL_BODY", + change_class="app_feature", + risk_level="low", + request="# Title\n\n## Action items\n1. Add field payment_status.\n\n## Constraints\nNo generic payment engine.", + allowed_paths={"hyrule-cloud": ("hyrule_cloud",)}, + intent="feat: title only", + ) + prompt = assemble_backend_prompt(spec, BackendConstraints(max_iterations=5)) + assert "Add field payment_status." in prompt + assert "No generic payment engine." in prompt + + def test_subprocess_backend_command_assembly_and_refusals(tmp_path: Path) -> None: spec = TaskSpec( change_id="CMD_ASSEMBLY",