Skip to content

feat(agents2): show agents working: 👀💬 reactions, local typing, Codex settles - #860

Merged
matt2e merged 3 commits into
mainfrom
baxen/agent-working-reactions
Oct 12, 2026
Merged

matt2e merged 3 commits into
mainfrom
baxen/agent-working-reactions

Conversation

@baxen

@baxen baxen commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Merge stack, bottom to top: #838 → #855 → #856 → #858 → #859 → #860 → #865 → #867 → #871. Each PR targets the one before it. Earlier stack notes here are superseded.

Folded in: #861, #863 (closed). Their descriptions are below.

What

A stopgap until Agents2 agents show as typing. When an agent's run starts for a mention, the host reacts to the mention as the agent with 👀 and 💬. It deletes them, one kind 5 per reaction because the relay takes one target per deletion, once the run, and any work the run handed off, has settled. The cap is the longest run allowed (30 min).

  • Central, not per-plugin. The reactions live in src/features/agents2/working-reactions.ts plus one call in Agents2Service.working(). Plugins never publish them.
  • Delivery.workingUntil(done). Claude Code and Codex return from run early and finish their turns in the background, so only they know when a turn ends. This hook lets them say so: one call in each. It has the same shape as feat(agents2): show agents as typing while they work on a mention #792's typing hook, so it is meant to stay when the reactions go.
  • To remove later: every piece is tagged STOPGAP(agent-working-reactions). Run grep -rn "STOPGAP(agent-working-reactions)" src and delete what it marks.

Behavior notes

  • Only mentions get the reactions, not watches or timers.
  • Only the app copy that holds the agent's claim reacts.
  • A mention that the plugin then ignores, such as Claude Code with respondTo: owner getting a non-owner mention, briefly shows 👀💬 and then removes them. That follows from reacting at invocation. Reacting on the first workingUntil call instead would avoid the flicker.
  • The reaction tags match the app's own reactions: h and e, no p.
  • Known stale-reaction cases: the app quits mid-turn, or the agent is removed mid-turn (its key is gone, so the deletion can't be signed). Claude Code turns longer than 30 min have their reactions cleared at the cap.

Validation

  • vitest run src/features/agents2 src/bundled/codex src/bundled/claude-code: 17 files / 190 tests pass. tsc --noEmit is clean.
  • New service test: reactions appear at invocation and stay while the handed-off work is pending. They are removed when it settles and also when the run fails. Watches get none. I confirmed it fails when workingUntil holds are ignored.
  • Plugin tests: Claude Code's handed-off turn settles only when the turn ends. All three Codex steered mentions settle only after the turn completes.
  • Independent agent review: no blockers. Its fixes are applied: no p tag, synchronous publish errors can no longer wedge drain, and a real barrier before a negative assertion.
  • baxen's first native test found the reactions were never cleared: the single two-target deletion was rejected by the relay. Fixed in 71f044f, and the service test now asserts one target per deletion.
  • Not yet done: a re-test of that fix.

🤖 Generated with Claude Code

From #861

What

STOPGAP, to be deleted once agents' typing pulses reach the relay (#792 plus block/buzz#8207). While an Agents2 agent works on a mention (the same lifetime as #860's reactions, Agents2Service.working()), this device shows the agent typing in the mention's thread. Nothing is published, so other devices don't see it.

  • agents2/local-typing.ts builds a kind 20002 pulse every 3 s. It uses the tags feat(agents2): show agents as typing while they work on a mention #792 would publish: h, the thread root, and the mention as reply.
  • The new typing.local(event) in relay/typing.ts feeds that pulse into the session's existing typing store as if it came live. The store's expiry (8 s), the end-on-reply rule (the agent's kind 9 hides the indicator, then a 2 s quiet period) and the existing indicators all work unchanged.
  • The session is the same per-community session the UI uses (communities.open → acquire). Agents2 agents are already in agentChoices through localAgents.bind(agents2.local), so the floating indicator and the thread rows show them.
  • No FOUNDATION file changes: relay/session.ts already exposes typing.capability.

Removal

Every piece is tagged STOPGAP(agent-local-typing). Run grep -rn "STOPGAP(agent-local-typing)" src and delete what it marks. #860's reactions are tagged STOPGAP(agent-working-reactions) the same way.

Notes

  • When the turn settles, the host feeds a local kind 9 end, so the indicator clears at once instead of when the last pulse expires (up to 8 s). Codex makes one more model call after a final send, so its indicator can stay about 3 s past its reply.
  • The agent's own posts in that thread (progress updates) don't hide it, so it doesn't blink off for 2–5 s after each one. Only the local end does.
  • A failed pulse warns once, then stops pulsing; it never breaks the run.
  • typing.local sits on the session's typing capability, so code that can see the session could show fake typing for any pubkey. It only affects this device.

Validation

  • vitest run across agents2, relay typing and activity-typing, Codex, Claude Code and TypingIndicator: 22 files / 244 tests pass. tsc --noEmit is clean.
  • New tests:
    • a local pulse shows like a live one and the agent's reply ends it
    • pulses come every 3 s in the mention's thread and stop when told to
    • a pulse is skipped while the community is not ready
    • a throwing pulse stops the pulses
    • the service starts typing for a mention run
    • stopping ends the indicator at once, and a local end hides it like a reply
  • Not yet done: a re-test after baxen's first test (which found the 8 s tail, now fixed).
From #863

What

Codex doesn't end its turn when it sends a reply with final: true. It makes one more model call that returns nothing, then ends the turn about 3s later. That call is visible in the #buzzin-in-secret rollouts. Until now, the request's workingUntil handoff settled only when the turn ended, so the agent's working reactions and typing (#860, #861) lingered past its reply.

The handoff now settles as soon as a final send succeeds. That covers every request the turn has taken in by then: the first one and each steered follow-up. A follow-up steered in after the send keeps counting as working until its own answer.

Why not end the turn

Claude Code ends its turn on a final send through a hook. The Codex app-server has no matching option, so the only way to end the turn is turn/interrupt. Codex then writes a <turn_aborted> note into the session after every reply: "The user interrupted the previous turn on purpose…". The model would read that note on every later turn, and live.mjs already asserts that ordinary final sends leave no such note. So the turn still ends by itself, and only the "working" signal moves. That extra model call still happens; it is cheap, with cached context and no output.

Validation

  • vitest run src/bundled/codex src/bundled/claude-code src/features/agents2 src/features/relay/typing: 218 tests pass. tsc --noEmit is clean.
  • Tests:
    • A final send settles the handoff while the turn is still running.
    • When a follow-up is steered in after a final send, only the first request settles.
    • Each request steered into a turn settles at that turn's answer.
    • With the answered() call removed, the first two tests fail.
  • Not yet done: a native run.

baxen added a commit that referenced this pull request Oct 11, 2026
Mirror baxen/agent-working-reactions at 11d731a (#860, stacked on #859). Preserve the existing next/green ancestry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
baxen added a commit that referenced this pull request Oct 11, 2026
Mirror baxen/agent-local-typing at 8d2bcec (#861, stacked on #860, which now carries its STOPGAP tags). Preserve the existing next/green ancestry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
@baxen
baxen force-pushed the baxen/remove-agents-v1-tail branch from 261db4c to afa8713 Compare October 11, 2026 23:11
@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 71f044f to 737b32d Compare October 11, 2026 23:11
@baxen baxen changed the title feat(agents2): react 👀 💬 to a mention while its agent works feat(agents2): show agents working: 👀💬 reactions, local typing, Codex settles Oct 11, 2026
@baxen
baxen force-pushed the baxen/remove-agents-v1-tail branch from afa8713 to 8fd8bb0 Compare October 11, 2026 23:55
@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 737b32d to 84e88c1 Compare October 11, 2026 23:55
@baxen
baxen marked this pull request as ready for review October 11, 2026 23:56
@baxen
baxen requested a review from a team as a code owner October 11, 2026 23:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84e88c1200

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
// STOPGAP(agent-local-typing): an agent's progress posts would hide its
// typing between pulses; the local end says when the turn is over.
if (local) record.local = typing;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track overlapping local typing runs independently

When two mentions for the same agent and thread overlap—as supported by Codex steering—both pulse the same activity record, but this single boolean cannot represent both producers. When the first request settles, its local kind-9 end sets record.local to false and clears the indicator even though the second request is still working; the second interval restores it only after its next pulse and post-message quiet period, causing a several-second false idle transition. Keep a per-scope active-run count/token, or otherwise end the record only after the final local producer stops.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T00:02:49.430138Z 84e88c1 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@baxen
baxen force-pushed the baxen/agent-working-reactions branch from 84e88c1 to ba9b31b Compare October 12, 2026 00:12
@baxen
baxen force-pushed the baxen/remove-agents-v1-tail branch from 8fd8bb0 to 0ab40dd Compare October 12, 2026 00:12

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes needed:

  • Settle rejected Codex follow-ups independently of the preceding turn (new P2 inline).
  • The existing overlapping local-typing finding still applies at this head; retain per-scope producer ownership until the last run ends.

Star Lord’s automated source review via Wes’s account. Head ba9b31b90f55dba431787043f94a8463928953de; base 0ab40dd5db1b040db1f221ec3fee4de6ed263bd6. Source-only: no tests, builds, app/browser execution or CI verification. Reviewed the public description and commit messages; the description contains no image attachments.

// The steered turn, or one started in its place, answers this mention.
const steered = lane;
delivery.workingUntil(
Promise.race([replied, accepted.then(() => steered.tail)]),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Release a rejected follow-up’s working hold

When turn/steer (or the preceding history read) fails, the catch above posts “Steering was not accepted” but resolves accepted without adding this request to active.requests. Its replied promise can therefore never resolve, while this fallback waits for the previous turn’s tail. A rejected mention keeps its 👀💬 reactions until that unrelated work ends; with channel-scoped sessions, a follow-up in another thread also keeps that thread falsely typing after the rejection. Settle this request’s hold on the handled rejection, retaining the turn-tail fallback only for accepted steering or a replacement turn. Add coverage with the original turn held open while steering fails, asserting the rejected hold settles without ending the original work.

@matt2e
matt2e force-pushed the baxen/remove-agents-v1-tail branch from 0ab40dd to 78c3214 Compare October 12, 2026 04:33
@matt2e
matt2e requested a review from a team as a code owner October 12, 2026 04:33
Base automatically changed from baxen/remove-agents-v1-tail to main October 12, 2026 05:44
baxen and others added 3 commits October 12, 2026 16:49
Until Agents2 agents show as typing, the host reacts to a mention as the
agent with 👀 and 💬 when its run starts, and deletes both once the run and
the work it handed off have settled (at most the longest run allowed).

Plugins return from `run` early and finish their turns in the background,
so `Delivery.workingUntil(done)` lets them say when that work settles.
Claude Code and Codex now call it. The reactions live in
working-reactions.ts and its one call in the service, so they can be
deleted once typing indicators ship; `workingUntil` stays for those.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
STOPGAP until agents' typing pulses reach the relay: the host shows a
mention's agent typing in the mention's thread on this device only, for as
long as the run counts as working. Every 3 s it feeds a pulse into the
session's existing typing store through a new `typing.local`, so expiry and
the agent's reply ending it work as they do for live pulses.

Every piece is tagged STOPGAP(agent-local-typing); grep for it and delete
what it marks to retire this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Codex makes one more model call after a final send before its turn
ends, so the request's working handoff, and with it the agent's working
reactions and typing, outlasted its reply by about 3s. The handoff now
settles at the final send, for every request the turn has taken in by
then. The turn itself still ends on its own: interrupting it would leave
a <turn_aborted> note in Codex's history after every reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e force-pushed the baxen/agent-working-reactions branch from ba9b31b to 04c3944 Compare October 12, 2026 05:51
@matt2e
matt2e merged commit ecfbb8a into main Oct 12, 2026
41 of 43 checks passed
@matt2e
matt2e deleted the baxen/agent-working-reactions branch October 12, 2026 07:21
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.

3 participants