Skip to content

An agent that says an item is impossible is asking for a person, not failing - #175

Merged
thedancingdeveloper merged 1 commit into
mainfrom
fix/refusal-needs-a-person
Aug 5, 2026
Merged

An agent that says an item is impossible is asking for a person, not failing#175
thedancingdeveloper merged 1 commit into
mainfrom
fix/refusal-needs-a-person

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Contributor

Closes #174.

A session agent that concluded an item could not be done as written was recorded refused / no_target — "the agent made no changes": an attempt spent, an ordinary failure, and outside NEEDS_A_PERSON. Its prompt told it to "stop and say so plainly", which meant explaining itself to a terminal nothing reads.

It now writes to .harness-refusal.md and the harness ends the item escalated / item_impossible, blocked, not consuming an attempt — the brief is what is wrong, and retrying does not rewrite a brief.

The note is read and deleted before the tree is inspected, so it reaches no commit, diff or reviewer. A note left alongside real changes is not a refusal — the tree decides. A clean tree with no note escalates as no_target and names the session, because "impossible" and "did nothing" look identical from an empty tree, both want a human, and the reason is what separates them.

Nine tests; all four gates green locally.

🤖 Generated with Claude Code

…, not failing

A session-mode agent that read the repository, concluded the item cannot be
done as written, and said so was recorded as:

    refused / no_target — "the agent made no changes"

which costs an attempt, reads as an ordinary failure, and is deliberately
excluded from the set a human is asked to look at. The rule in its own prompt
said "stop and say so plainly" — into a terminal nobody reads.

The harness already had the right word for this. `ESCALATED` is documented as
"a person has to resolve something before this can be attempted again; not a
failure of the item and not a transient condition", and it is the sole member
of `NEEDS_A_PERSON`. The code contradicted a comment eleven lines above it,
which already said a clean tree from a refusing agent "is a real answer, not a
failure to paper over".

So: the agent is told to write its reasoning to `.harness-refusal.md` and
change nothing else. The harness reads that note, deletes it, and ends the item
`escalated / item_impossible`, `blocked`, **without consuming an attempt** —
because what is wrong is the brief, and no number of retries rewrites a brief.

Deliberate, and tested:

- the note is read and removed *before* the tree is inspected, so it cannot
  reach a commit, a diff, or a reviewer;
- a note left alongside real changes is not a refusal. The tree decides;
- a clean tree with no note escalates too, as `no_target`, naming the session
  that holds the explanation. "It was impossible" and "it did nothing" are
  indistinguishable from a clean tree, both want a human, and the reason text
  is what tells them apart.

Not in scope: publishing the finding to the item's issue. `GitHub.comment()`
exists and that is a reasonable follow-up, but it is a projection of the
outcome rather than the outcome, and an item can run with no repository
configured at all.

Closes #174.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedancingdeveloper
thedancingdeveloper merged commit 1ce56c4 into main Aug 5, 2026
2 checks passed
@thedancingdeveloper
thedancingdeveloper deleted the fix/refusal-needs-a-person branch August 5, 2026 03:06
thedancingdeveloper added a commit that referenced this pull request Aug 5, 2026
#176)

Both found by running rdpapp R7 through the path added in #175. The agent
refused an impossible item correctly, with citations, and the harness then
mishandled the answer twice.

**The state never reached the queue.** `Stop(state=BLOCKED)` was honoured only
on the checks path, which copied `stop.state` across by hand; every other path
left the item in `failed`. So R7 was recorded `escalated / item_impossible`
with `attempts=0` — all correct — and sat in `failed`, where nothing looking
for work that needs a person would ever find it. The state is now applied
centrally, at the release call, beside `consumes_attempt` which was already
read there. An empty `state` still means the caller chose, which is every path
older than this taxonomy.

**The run announced it as `FAIL R7`.** `ok` and `FAIL` were the whole
vocabulary, so an outcome where nothing went wrong and no attempt was spent was
reported as a failure — and set exit 1, which makes a queue of well-formed
questions read to CI as a broken run. There is now a `YOU` marker, a closing
`waiting on you, not on a retry` line, and `_is_failure` excludes
`NEEDS_A_PERSON` from the exit status.

The summary is extracted as `run_summary()` returning lines rather than
printing them, because a formatting decision that only exists inside a print
loop cannot be tested, and this one is a decision.

One older test asserted `FAILED` for a clean tree while its own docstring said
"that is a real answer, not a failure to paper over". It asserts `BLOCKED` now.

Co-authored-by: sprooty <sprooty@sprooty.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

An agent that correctly refuses an impossible item should be flagged as needing a person

1 participant