fix(#4539): surface artifact contract in spawn prompt - #4575
Conversation
Artifact-mode tasks complete on a signed lineage receipt over their produced artifact, but the spawn prompt never surfaced the contract the completion side enforces: kind, output path, and acceptance criteria. Agents could only learn the contract from operator free-text, and when that was skipped, completion failed on criteria the agent never saw. Add a shared render_artifact_contract() helper that reads the same task.artifact_spec object and the same artifact_output_path / is_artifact_mode resolvers as the completion path, so prompt and verifier cannot drift. Wire it into both render paths (spawn_prompt and the live spawner_core), gated so a code_diff batch's prompt is byte-unchanged. Closes sipyourdrink-ltd#4539
chernistry
left a comment
There was a problem hiding this comment.
Reviewed against the code — this is right, and right for the reason that matters: the prompt and the verifier read the same artifact_spec through the same artifact_output_path / is_artifact_mode resolvers, so there is no second parse to drift. That is the part that would have rotted if the contract had been re-rendered from its own copy of the spec.
Checked the two things worth checking:
spawner_core._render_promptdelegates to_render_prompt_with_receipt(spawner_core.py:1430–1439), so the test assembles the live production path rather than a parallel one. The assertionartifact_output_path(task) in promptis what makes it a real test and not a string check.- The gate is on
is_artifact_mode, which resolves throughartifact_spec.kindand defaults tocode_diff, so an ordinary coding task's prompt is byte-unchanged — covered bytest_code_diff_task_prompt_is_unchanged.
One thing you may be wondering, since the PR touches both render paths: only the spawner_core path is exercised by the tests. That is fine as it stands — spawn_prompt._render_prompt is kept in sync by that file's own convention and nothing spawns through it in production. No change requested.
Approved, auto-merge armed. It will enter the merge queue once the 3.13 shards finish.
If you want another one in the same area: #4571 — the agent timeout extension never reaches the spawned process. It is fully diagnosed and small:
tuning.orchestrator.max_agent_runtime_sdoes reachOrchestratorConfig.max_agent_runtime_s(core/models.py:1603, default from_default_max_agent_runtime_satmodels.py:44).- Its only consumer is the extension logic at
core/agents/agent_lifecycle.py:2834. - Every real
adapter.spawn(...)call site omitstimeout_seconds, soadapters/base.py:42 DEFAULT_TIMEOUT_SECONDS = 1800arms thethreading.Timerinstead. The configured value cannot take effect no matter what an operator sets.
The slice: thread the resolved runtime into the spawn calls so the Timer is armed from config. Proven by a test that spawns with a non-default max_agent_runtime_s and asserts the adapter received that timeout — not by reading the config back. Out of scope: the extension logic itself, and any change to the 1800 s default for callers that pass nothing.
I have assigned it to you. If it turns out bigger than it looks, say so in the thread and I will slice it further.
The prompt renderer now reads the task's artifact contract to decide whether the task completes on a signed receipt or on a commit. These two tests build their task with a bare MagicMock, which answers that question with a Mock - not `code_diff` - so the renderer took them for artifact-mode tasks and raised while resolving an output path that does not exist. The mock already enumerates every field the renderer reads; this adds the one the renderer started reading.
|
Shard 4 was red on Worth knowing for next time: your first push sat for a few hours with no checks at all. That is the first-time-contributor workflow gate, not your PR — a maintainer has to release each run. Ping me here if it happens again and I will clear it. |
Problem
Issue #4539: an artifact-mode task declares a full contract (
kind,output_path, acceptance criteria) but the spawn prompt never surfaces it. Agents only learn the contract from operator free-text; when that's skipped, completion fails on criteria the agent never saw.Fix
Adds a shared
render_artifact_contract(tasks)helper that renders the kind, the exact output path the verifier reads, and every declared criterion. It reads the sametask.artifact_specobject and the sameartifact_output_path/is_artifact_moderesolvers as the completion path, so prompt and verifier cannot drift (no second parse).Wired into both render paths:
spawner_core._render_prompt_with_receipt(the live production path)spawn_prompt._render_prompt(kept in sync per that file's convention)Gated so a
code_diffbatch's prompt is byte-unchanged.Closes #4539.
Tests
Three new tests in
tests/unit/agents/test_artifact_contract_prompt.py:test_artifact_task_prompt_names_kind_path_and_criteriatest_code_diff_task_prompt_is_unchangedtest_prompt_and_verifier_consume_the_same_spec_objectAll pass (
pytest tests/unit/agents/test_artifact_contract_prompt.py→ 3 passed). Existing prompt-coherence tests unchanged (29 passed). Ruff clean.