You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[bug] An agent can start work but has no tool to observe it — no run status, no terminal read after drive:false #256
surfaceOptions.coding.drive === false correctly removes the CODING group
(list_coding_repos, read_terminal, send_to_cli) — a chat driving the engine would be the
second uncoordinated driver #154 exists to prevent. Correct decision.
But nothing replaced the read half. So after calling start_work the agent has:
no way to read the engine's terminal (read_terminal removed with the drive tools)
no way to query the run it just started — start_work returns a run id, and there is no tool
that takes one. GET /v1/instances/:id/loop/:runId exists for the console; the agent has no
equivalent.
no way to list its own coding sessions (list_coding_repos also went with the group)
get_activity is the only observability it has, and it is a generic event feed, not an answer to
"did the thing I started finish?"
What that causes
This is the mechanism behind #254. Real conversation, 2026-08-06:
start_work → Loop → Pilot ran git pull. It worked.
User: "Did agent pull it or you pull it?"
Agent: "I did not pull anything. I have no ability to run shell commands."
Faced with a direct challenge, it had no tool that could have settled the question. It could
not check the run, could not read the terminal, could not see the session. All it had was a system
prompt saying it cannot act. With no evidence, it deferred to the prompt and denied real work.
An agent that can act but cannot observe its own actions is structurally forced to either
fabricate or deny. Both were observed on this instance within two days.
Proposal
A read-only counterpart to start_work, granted wherever start_work is:
check_work(runId?) — status, iteration, outcome, detail for a run this instance started;
with no argument, the most recent. Backed by getLoopRun / listLoopRuns, which already exist.
Restore a read-only terminal for drive:false agents. Reading is not driving; the reason to
remove send_to_cli does not apply to read_terminal, and removing both was the overcorrection.
Consider surfacing the last outcome in the context block automatically, so the agent does not
need a tool call to answer "what happened" about work it started this turn.
Verification
After start_work, the agent can answer "did that finish?" from a tool result rather than memory.
Asked "did you do that?", it cites the run id and outcome.
It still cannot send input to the engine — drive:false is unchanged.
Verified TRUE. Fixed in 27f4c4e — with one part deliberately not done, explained below.
Confirmed each gap:
toolNamesFor with drive:false resolves to new Set(BASE) and the CODING invariant is gated on optionsFor(caps,"coding")?.drive, so read_terminal / list_coding_repos / send_to_cli all go together. Correct.
No tool took a run id. getLoopRun / listLoopRuns existed and had exactly two readers — routes/tools.ts (/loop, /loop/:runId, for the console) and connectors/supervision.ts (for a supervisor). The agent itself had none.
Grep for check_work returned nothing.
Your framing is the right one and I have quoted it in the module header: an agent that can act but cannot observe its own actions is structurally forced to either fabricate or deny.
What shipped
check_work(runId?) — base tier, granted wherever start_work is, backed by getLoopRun / listLoopRuns as you proposed. Rendering is pure and tested in lib/work-report.ts. Three things worth calling out:
The empty case is a success, not an error."You have not started any work on this instance — there are no runs. If you told the user you did something, that was wrong; say so." An error would read as "could not tell", which is the state that produces a guess. This has to be usable to contradict the agent's own earlier claim.
A silent running run is reported as STALLED, not as live work. A Workflow that dies mid-step leaves status='running' forever, so reporting it as live would be the same over-claim in a new place. Uses last_progress_at (0067), 15 min.
Scoped to the calling instance, not just the owner — getLoopRun is user-scoped, so without that check an agent could read a sibling agent's run and report it as its own.
Also added to READ_ONLY_TOOLS, so the cross-round dedup does not limit a challenged agent to one look per conversation.
Your third bullet, adopted: recent runs are injected into context automatically (recentWorkPrompt, capped at 3) for any agent with an executor. The denial happened in one turn, in reply to a direct challenge, and a model that must first decide to call a tool before it can defend a true statement will often just apologise instead. Having the answer already in the prompt removes the decision.
Not done: restoring read_terminal for drive:false
I disagree with this half, and want to be explicit rather than quietly skip it. Migration 0070 already argued it and the argument still holds: agent-think.ts pulls a fresh capture of every active session's pane into the chat's context on every turn, and read_terminal needs a tmux session id the chat is never given — so it would be a third copy of a capability the chat already has.
The real gap it was aimed at is ended sessions, where the context block says "No active coding session" and the transcript was unreachable. That is #257, and it is fixed at the source there. If a case survives both check_work and repo-scoped history, reopen and I will add it.
drive:false is unchanged — this restores looking, never steering. 10 new tests in start-work.test.ts, 11 in work-report.test.ts.
Activity
fix(coder): let an agent describe and observe its own work (#254, #255,
serge-ivo commentedon Aug 6, 2026
Verified TRUE. Fixed in 27f4c4e — with one part deliberately not done, explained below.
Confirmed each gap:
toolNamesForwithdrive:falseresolves tonew Set(BASE)and theCODINGinvariant is gated onoptionsFor(caps,"coding")?.drive, soread_terminal/list_coding_repos/send_to_cliall go together. Correct.getLoopRun/listLoopRunsexisted and had exactly two readers —routes/tools.ts(/loop,/loop/:runId, for the console) andconnectors/supervision.ts(for a supervisor). The agent itself had none.check_workreturned nothing.Your framing is the right one and I have quoted it in the module header: an agent that can act but cannot observe its own actions is structurally forced to either fabricate or deny.
What shipped
check_work(runId?)— base tier, granted whereverstart_workis, backed bygetLoopRun/listLoopRunsas you proposed. Rendering is pure and tested inlib/work-report.ts. Three things worth calling out:runningrun is reported as STALLED, not as live work. A Workflow that dies mid-step leavesstatus='running'forever, so reporting it as live would be the same over-claim in a new place. Useslast_progress_at(0067), 15 min.getLoopRunis user-scoped, so without that check an agent could read a sibling agent's run and report it as its own.Also added to
READ_ONLY_TOOLS, so the cross-round dedup does not limit a challenged agent to one look per conversation.Your third bullet, adopted: recent runs are injected into context automatically (
recentWorkPrompt, capped at 3) for any agent with an executor. The denial happened in one turn, in reply to a direct challenge, and a model that must first decide to call a tool before it can defend a true statement will often just apologise instead. Having the answer already in the prompt removes the decision.Not done: restoring
read_terminalfordrive:falseI disagree with this half, and want to be explicit rather than quietly skip it. Migration 0070 already argued it and the argument still holds:
agent-think.tspulls a fresh capture of every active session's pane into the chat's context on every turn, andread_terminalneeds a tmux session id the chat is never given — so it would be a third copy of a capability the chat already has.The real gap it was aimed at is ended sessions, where the context block says "No active coding session" and the transcript was unreachable. That is #257, and it is fixed at the source there. If a case survives both
check_workand repo-scoped history, reopen and I will add it.drive:falseis unchanged — this restores looking, never steering. 10 new tests instart-work.test.ts, 11 inwork-report.test.ts.feat(agents): an agent can stop the work it started, and end the sess…