Skip to content

the-agent-asks-the-operator - #844

Merged
VeryComplexAndLongName merged 15 commits into
mainfrom
the-agent-asks-the-operator
Oct 8, 2026
Merged

VeryComplexAndLongName merged 15 commits into
mainfrom
the-agent-asks-the-operator

Conversation

@VeryComplexAndLongName

Copy link
Copy Markdown
Owner

What

An agent stage that needs a decision the change's files leave open prints Question for the operator: <question>, and the run stops and waits for the answer under every autonomy level, alone or in a chain (ADR 0042). local-llm-acp can also ask mid-turn with an ask_operator tool.

  • Each question goes into the change's decisions.md and the audit log. Once every question is answered, the stage runs again with the answers: update after propose/review, the same command after apply/verify.
  • While a question is open, agent runs on the change are refused, and the refusal names the question.
  • A question can be answered from:
    • the change's card (Answer...);
    • the AI and chain panels;
    • the Human-Only Inbox, which reads both the checkout and each change's worktree;
    • openspec-ui-cli answer;
    • decisions.md itself.
  • status and the supervisor show the command that answers a waiting run.

How it differs from the proposal (design.md updated)

  • The wait is implemented as a wrapper (withOperatorQuestions) around each default runner; agent-runner.ts is unchanged.
  • An answer reaches a run in another host through decisions.md, which the waiting run reads every 3 s. The run's message channel is not used.
  • Only questions still open at the end of a pass are waited on, so an ask_operator already answered within the turn causes no second pass.

Checks

  • typecheck clean; lint has 0 errors.
  • Affected tests pass in each package, run locally with two workers. The full projects run here in CI.
  • openspec validate --strict passes.
  • Live run on copilot-cli-acp, recorded in tasks 4.3:
    • the review asked about the storage backend and waited;
    • an implement started meanwhile was refused;
    • openspec-ui-cli answer from another process woke the run;
    • the update rewrote the plan for the answer.

Still open (the gate names them)

  • 4.3: local-llm-acp asking mid-turn, run live. There is no local LLM endpoint in my environment.
  • 4.4 Human-only: in a host, see a run stop on a question, answer it from the card, see it go on, and see apply refused while a question is open.

🤖 Generated with Claude Code

VeryComplexAndLongName and others added 15 commits October 8, 2026 09:52
From a tester's feedback on 2026-10-07 and the owner's decisions the same
day; proposal, design, spec deltas, tasks and a proposed ADR. Not yet
implemented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
the-agent-asks-the-operator 1.1-1.7 (ADR 0042): the question marker on every
agent stage; decisions.md; the question, awaitingAnswers and
questionAnswered events and the answerQuestion command; a runner wrapper
that records questions, holds completed while any is open, waits for the
answers from anywhere and goes on with them; the status record's question
wait; ask_operator for the local agent; a chain's stage time leaves the
wait out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The card answers a change's open questions read through the survey,
whether or not the run that asked is alive; the AI and chain panels show
the run's questions; the Human-Only Inbox lists them first, from the
checkout and from each change's own worktree, with an inline Answer in
VS Code and a field in the standalone app. The server routes an answer to
the runner holding the run. openspec-ui-cli gains answer, status prints
each waiting run's questions with the command that answers them, and the
supervisor offers that command beside status.

ADR 0042 accepted; design.md and two spec deltas say what was built:
the wait is a wrapper around each default runner, an answer reaches a run
in another host through decisions.md, and only questions still open at the
end of a pass are waited on. HARNESS.md section and changeset.

Live run 4.3 on copilot-cli-acp recorded; local-llm-acp mid-turn still to
run live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge's own worktree

A chain asks in the change's worktree; an apply started from the checkout read only the checkout's decisions.md and went past the question. The refusal now also reads the change's own worktrees, found with git worktree list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s question instruction

The path-pointing prompt now carries how to ask the operator, so it is about 1250 characters; the test's bound was 1000, against a real argv ceiling of 6000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ger blanks it

The answer field read e.currentTarget inside the state updater, which React runs after the event, when currentTarget is null: the render threw on the first key and the panel unmounted. The value is read in the handler now, here and on the card's form. The test types two keys in one batch, as a browser does; it failed with the same TypeError before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
copilot-cli-acp asked 'Edit file' and 'Running command' at once (live, 2026-10-08). The panels showed only the latest pending request, so after it was answered the other one's Allow/Deny stood in the same place and read as buttons that never went away, while the run waited on it. Each pending request is now shown with its own buttons, and none once the run has ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live on 2026-10-08 the chain paused at its checkpoint with Allow/Deny still showing for two requests copilot-cli-acp had stopped waiting on: it cancels a request ($/cancel_request) when a tool call beside it fails, and goes on. The ACP driver ignored the request's abort signal, so the request stayed pending and on screen, and each later Allow seemed to add a row.

The driver now answers a cancelled request as cancelled and emits a new permissionWithdrawn event, and does the same for a request still open when the turn ends. The panels drop a withdrawn request, and every request of a stage that has completed, reached a checkpoint or been handed off. Tests in acp-session-driver.test.ts, AiPanel.test.tsx and HarnessChainPanel.test.tsx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live on 2026-10-08 local-llm-acp, given the instruction every agent gets, printed 'Question for the operator:' and ended its turn rather than calling its ask_operator tool. That path works (the run waited and went on as a second pass), but the tool exists to ask in the turn. Its stage instruction now tells it to call the tool; every other agent keeps the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…panel

Live on 2026-10-08 local-llm-acp asked with ask_operator, the operator answered on the change's card, and the run panel kept the answer field, also after the run ended: only the path that waits after a pass said questionAnswered in the run's stream. The wrapper now reads decisions.md while the agent works and says each question of the run answered there, whoever answered it; the panels show no answer field once the run has ended. Task 4.3 recorded: local-llm-acp asked twice with ask_operator in one turn and went on with the answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orm follows the card

Live on 2026-10-08: a chain runs every stage under one run id and each stage numbered its questions from 1, so the verify stage's question took the apply stage's id. The wrapper took it for answered and the chain ended, and the card offered an answer that could never be recorded. Ids now go on from those the run already has in decisions.md; an answer to an id a file holds twice goes to the open entry; ask_operator waits on the latest question of its text.

The card's answer form kept the questions it was opened with, so a question asked since never showed and one answered in the run panel stayed. It now follows the card, and closes when nothing is left to answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live on 2026-10-08: Allow given on the change's card left the run panel's Allow/Deny in place, since a panel dropped only the requests it had answered itself. The ACP driver now says every answer in the run's own stream, and permissionWithdrawn becomes permissionSettled with an outcome of allow, deny or withdrawn; the panels drop a settled request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aid as it lands

Live on 2026-10-08: copilot-cli-acp asked for four permissions at once; the card knew only the latest, so once it was answered the card offered nothing while three held the run, and any event from the agent cleared the wait. LiveRuns and the status record now hold every open request, oldest first, until permissionSettled; the card offers the oldest and says how many are open.

Also: with two questions open, one answered on the card stayed on the run panel until the other was answered, since the wait said its answers only once all had come. It now says each as it lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@VeryComplexAndLongName
VeryComplexAndLongName merged commit c8de5d2 into main Oct 8, 2026
10 checks passed
@VeryComplexAndLongName
VeryComplexAndLongName deleted the the-agent-asks-the-operator branch October 8, 2026 11:02
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