Skip to content

An agent reports what it could not get past, and it stays in the run's log (#1500) - #1631

Merged
suleimansh merged 3 commits into
mainfrom
tf-error-capability
Aug 21, 2026
Merged

An agent reports what it could not get past, and it stays in the run's log (#1500)#1631
suleimansh merged 3 commits into
mainfrom
tf-error-capability

Conversation

@suleimansh

Copy link
Copy Markdown
Contributor

🤖 curated

Closes #1500 (the capability half), built as Option A from the comment you approved.

What an agent can now do

gh is not logged in

ran `gh auth status`: You are not logged into any hosts

The first line is the headline, the rest is the detail. Non-blocking — it does not stop the turn and it does not ask the user anything (AWAIT is still the way to ask).

Where it shows up

  • In the run's log, red, at the point in the run where it happened. It joins the failure lane the log already had (isFailure), so it gets the same red text and red wash as a driver error or a failed run — no second vocabulary for the same thing.
  • On the session overview, as 2 errors · <the latest headline>. The log scrolls; this does not. This is the "pin" and the "3 errors" you asked for, in the place where the overview cards already fold the event stream.

Why nothing clears it

An error is an event: it happened at a point in the run and stays there as history. Nothing dismisses it because nothing can un-happen it — which is what removed the dismiss/expire question I was stuck on.

That leaves project-errors.ts doing the other half, unchanged: the errors a background job finds between runs are conditions that are true now, and they clear themselves when the condition is gone. Both SPECs now say which is which.

Two rules worth knowing

  • Every block in a turn is kept. Two things going wrong are two errors; the other signals keep only the last, and collapsing errors would lose one.
  • The same error is recorded once, however often the agent restates its block — agents restate their blocks turn after turn, and the same failure logged ten times reads as ten failures.

The prompt side

update_tickets.md had the hard-wired line this replaces ("show an error to the user"). It now names which conditions are errors and lets the protocol own how — the same split the other signals use.

Tests

Six new, each proven by breaking the code it guards and watching exactly that test go red: the dedupe, keeping every block, skipping empty blocks, the fold keeping the detail, and both halves of the red lane. Full suite green (1536 node + 779 dashboard).

Not manually verified in the running app — that needs a daemon restart, which is yours to make.

…s log (#1500)

An agent that hits something only the user can fix had one way to say so: prose in
its reply, which the reader has to notice. `update_tickets.md` even spelled the
instruction out per preset ("show an error to the user"), with no mechanism behind it.

It now emits an `error` block, and the framework records it as an event on the run's
own timeline — a headline and the detail below it, red in the log at the point it
happened, counted on the session overview so it survives the log scrolling past.

An error is an event, not a state: nothing clears it, because nothing can un-happen
it. That is what makes it different from the project errors a background job finds
between runs (project-errors.ts) — those are conditions that are true now and clear
themselves when the condition is gone. Two natures, two homes, one red vocabulary.

- every error block in a turn is kept: two things going wrong are two errors
- an error is recorded once however often the agent restates its block, so an agent
  repeating itself does not read as the same failure happening over and over
- update_tickets.md now says which conditions are errors and lets the protocol own how
)

The parser and the fold were unit-tested, but nothing proved the turn emitter is
actually wired into a run. The backlog-turn signals test now emits an `error` block
alongside the view, session name and ready-for-merge it already covered, and asserts
the event that comes out — verified by unwiring the emitter and watching it fail.
…is (#1500)

Dogfooding found it: the count was only rendered by AgentOverview, which lives on
the PROJECT page. Open a run by its URL — the page that actually carries the log —
and the count was nowhere. The whole point of it is to survive the log scrolling.

It is now its own component, used by both headers: the run's action bar and the
project page's overview.

Placing it also had to be decided rather than guessed. In the action bar it sits with
the controls, not among the branch facts, which give up width as the row fills — the
first attempt put it there and it rendered as "1 erro" against the summary with no
gap. It never shrinks now, and the headline only renders where the row has room for
it (the overview); in the tight bar every headline is the hover instead.

Verified on two real local runs against a seeded project:
- update_tickets on a tickets/ with no lastImportedAt: the agent reached for the
  block unprompted, one red row in the log, "1 error" in the bar
- a two-failure task: two separate rows, "2 errors", both headlines in the hover
@suleimansh
suleimansh marked this pull request as ready for review August 21, 2026 23:24
@suleimansh
suleimansh merged commit 55ff96e into main Aug 21, 2026
2 checks passed
@suleimansh
suleimansh deleted the tf-error-capability branch August 21, 2026 23:27
suleimansh added a commit that referenced this pull request Aug 22, 2026
…s log (#1500) (#1631)

* An agent reports what it could not get past, and it stays in the run's log (#1500)

An agent that hits something only the user can fix had one way to say so: prose in
its reply, which the reader has to notice. `update_tickets.md` even spelled the
instruction out per preset ("show an error to the user"), with no mechanism behind it.

It now emits an `error` block, and the framework records it as an event on the run's
own timeline — a headline and the detail below it, red in the log at the point it
happened, counted on the session overview so it survives the log scrolling past.

An error is an event, not a state: nothing clears it, because nothing can un-happen
it. That is what makes it different from the project errors a background job finds
between runs (project-errors.ts) — those are conditions that are true now and clear
themselves when the condition is gone. Two natures, two homes, one red vocabulary.

- every error block in a turn is kept: two things going wrong are two errors
- an error is recorded once however often the agent restates its block, so an agent
  repeating itself does not read as the same failure happening over and over
- update_tickets.md now says which conditions are errors and lets the protocol own how

* Prove the error signal reaches the stream on an agent's real path (#1500)

The parser and the fold were unit-tested, but nothing proved the turn emitter is
actually wired into a run. The backlog-turn signals test now emits an `error` block
alongside the view, session name and ready-for-merge it already covered, and asserts
the event that comes out — verified by unwiring the emitter and watching it fail.

* The error count belongs on the run page, where the log it summarises is (#1500)

Dogfooding found it: the count was only rendered by AgentOverview, which lives on
the PROJECT page. Open a run by its URL — the page that actually carries the log —
and the count was nowhere. The whole point of it is to survive the log scrolling.

It is now its own component, used by both headers: the run's action bar and the
project page's overview.

Placing it also had to be decided rather than guessed. In the action bar it sits with
the controls, not among the branch facts, which give up width as the row fills — the
first attempt put it there and it rendered as "1 erro" against the summary with no
gap. It never shrinks now, and the headline only renders where the row has room for
it (the overview); in the tight bar every headline is the hover instead.

Verified on two real local runs against a seeded project:
- update_tickets on a tickets/ with no lastImportedAt: the agent reached for the
  block unprompted, one red row in the log, "1 error" in the bar
- a two-failure task: two separate rows, "2 errors", both headlines in the hover
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.

Error capability

1 participant