Keep a subagent's work out of the inline feed and surface the Agents tab - #160
Merged
Conversation
A background Claude agent's activity flooded the parent conversation: every
per-tool task.progress tick rendered as its own inline row ("Activity - 128
actions" with 110+ leaked entries), while the agent tracker that should
summarize the delegation never showed for turnless background stretches.
Conversation feed:
- deriveWorkLogEntries no longer strips activityKind from emitted entries,
which had made the timeline's agent-lifecycle parking (isAgentLifecycleEntry)
dead code in production - the exact check #157 shipped.
- Background tasks an agent starts inside its own conversation (e.g. its test
runs) are now attributed to the spawning agent: the Claude adapter records
every tool_use id seen in forwarded subagent messages and stamps task events
with ownerAgentToolUseId (new optional field on the task payload contracts),
which the web parks out of the feed alongside the agent's own task stream.
- Work groups with no turn to key on carry trackerAgentSpawnIds, and
selectTurnAgents gained a spawn-call fallback, so a turnless background-agent
group at the conversation tail shows the tracker bars and live status line
instead of nothing. An all-delegation group's receipt reads "Agent working" /
"Agents working" / "Agent activity" instead of claiming zero actions.
Agents tab:
- A fresh idle-to-running agent transition auto-surfaces the Agents tab:
focused when the sidebar is hidden or on the launcher, joined in the
background (live node only) when another tab has focus. The edge detector
(advanceAgentsAutoOpenEdge) only believes idleness after the thread's detail
snapshot has synced - the chat column now publishes a hydrated flag with its
agent state - so loading or reloading a thread whose agent is already running
never yanks the panel open. Once per batch, never on sheet layouts, and
closing the tab mid-batch is respected.
Durable roster:
- projection_thread_subagents was empty for every thread: the projection only
recognized Codex-shaped collab items (data.item). The Claude item shaping
moved from web session-logic into @threadlines/shared/claudeSubagentActivity
and the server projection now uses it too, links Claude transcript task ids,
settles agents from task.completed (including restart-synthesized completions
that carry only a taskId), and maps the shaper's "errored" state to failed.
- Migration 048 backfills the roster by replaying each thread's roster-moving
activities through the same fold, in rowid (append) order so same-timestamp
lifecycle rows cannot resurrect a settled agent, one thread at a time.
Verified end to end against a snapshot of real data: migration backfilled 27
agents in ~6s, the flood thread renders clean with the tracker live at the
tail, and load/reload of a running-agent thread no longer auto-opens the panel.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
CodeQL flagged the moved sanitizer's regexes (js/polynomial-redos): the trailing <usage> stripper and the SendMessage continuation-footer stripper both backtracked polynomially, and agent output is provider-influenced input. The usage stripper is now index arithmetic, and the footer pattern only ever runs anchored against a bounded slice from the last "agentId:". Covered by direct tests for the shared module, including an adversarial repetition case that would time out on the quadratic implementation.
badcuban
added a commit
that referenced
this pull request
Aug 16, 2026
…, detection shielded Fixes for three issues observed live after #160: - Promoted codex exec runs never appeared in the panel. The server narrates a promoted run entirely through subagent.metadata activities, and the only thing the web did with that kind was filter it out of the work log - the durable roster row it fed only reached the client on a full detail snapshot, i.e. after a reload. The shared subagent record fold now applies metadata activities (spawn with callId, the later agent-id link, completion with the result body), so a promoted run shows up while it runs and settles in place. Tests use the three payloads a live run projected. - "Detected agent process" rows surfaced the session's own transient children: any pid or command mentioned in chat prose (pgrep output, a quoted setup script) could match the provider's shell wrappers and search helpers, offering stop buttons for the harness's own processes. Process-only detection now shields the entire subtree under any live provider CLI process - that work is tracked, not lost - while genuinely orphaned processes (reparented to init/launchd) stay detectable. - Background command runs (CI watchers, gate runs) crowded the top of the Agents tree while actual agents sat under "Earlier". The view now splits them into a Commands section below the agents, keeping their rows and stop handles. Consistently, a running command no longer counts as a running agent anywhere: not in the tab's live dot, not in the auto-open trigger, not in the launcher counts (which now name background commands as what they are).
badcuban
added a commit
that referenced
this pull request
Aug 17, 2026
* Harden the subagent roster fold and close review gaps from the live demo Follow-ups from two Opus review agents and a sol second-opinion run against the merged #160 (which also served as the live verify of the new agent UX): - The roster fold could emit two rows for one agent when a spawn's pending placeholder and an id-keyed row (learned from a later wait item) were bridged by a patch carrying both keys. The persisted table is unique on id/agentThreadId/spawnCallId per thread, so the write fails - and in migration 048 that failure aborted the transaction and left the server unbootable on every start. Patches now merge ALL matching rows into one. - Migration 048: json_valid() guards the candidate filter (json_extract raises on malformed payloads rather than returning NULL), orphaned roster rows for threads with no roster-moving activities are cleaned up, a thread whose replay fails is logged and skipped instead of failing the migration, and the rowid-ordering docblock now notes the thread.reverted caveat. New migration test covers both provider shapes, the collision, a malformed payload row, and orphan cleanup. - An agent's owned background tasks (ownerAgentToolUseId rows) were parked from the conversation but invisible in the Agents rail; the drill-in work view now includes them alongside sourceAgentThreadId rows. - An all-anchor work group with no resolvable tracker rendered null inside a padded timeline row, leaving phantom 16px gaps; the row wrapper now collapses its padding when the section renders nothing. - The <usage> stripper in the shared sanitizer re-lowercased the string per stacked block (quadratic on hostile tails); it now lowercases once and walks an index backward, with a stacked-blocks regression test. * Make the Agents tab about agents: commands sectioned, promotions live, detection shielded Fixes for three issues observed live after #160: - Promoted codex exec runs never appeared in the panel. The server narrates a promoted run entirely through subagent.metadata activities, and the only thing the web did with that kind was filter it out of the work log - the durable roster row it fed only reached the client on a full detail snapshot, i.e. after a reload. The shared subagent record fold now applies metadata activities (spawn with callId, the later agent-id link, completion with the result body), so a promoted run shows up while it runs and settles in place. Tests use the three payloads a live run projected. - "Detected agent process" rows surfaced the session's own transient children: any pid or command mentioned in chat prose (pgrep output, a quoted setup script) could match the provider's shell wrappers and search helpers, offering stop buttons for the harness's own processes. Process-only detection now shields the entire subtree under any live provider CLI process - that work is tracked, not lost - while genuinely orphaned processes (reparented to init/launchd) stay detectable. - Background command runs (CI watchers, gate runs) crowded the top of the Agents tree while actual agents sat under "Earlier". The view now splits them into a Commands section below the agents, keeping their rows and stop handles. Consistently, a running command no longer counts as a running agent anywhere: not in the tab's live dot, not in the auto-open trigger, not in the launcher counts (which now name background commands as what they are).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A background Claude agent's activity flooded the parent conversation ("Activity · 128 actions" with 110+ leaked rows), the inline agent tracker never rendered for turnless background stretches, the Agents tab gave no signal that a delegation had started, and the durable subagent roster (
projection_thread_subagents) had never contained a single row.Conversation feed
deriveWorkLogEntriesno longer stripsactivityKind, which had silently disabled the agent-lifecycle parking from Keep the turn activity tracker steady while subagents run #157 in production. A subagent'stask.progress/task.completedrows now park as invisible anchors; the Agents rail owns the detail.vp testruns) are attributed to the spawning agent: the Claude adapter records every tool_use id seen in forwarded subagent messages and stamps task events withownerAgentToolUseId(new optional contract field), which the web parks alongside the agent's task stream. Historical rows from before this change cannot be re-attributed and may still render; that fades as threads wrap.trackerAgentSpawnIdsandselectTurnAgentsgained a spawn-call fallback, so the group at the conversation tail shows tracker bars + the live status line while a background agent works. An all-delegation group's receipt reads "Agent working" / "Agents working" / "Agent activity" instead of claiming zero actions.Agents tab auto-open
A fresh idle→running transition surfaces the Agents tab: focused when the sidebar is hidden or on the launcher, joined in the background (live node only) when another tab has focus. The edge detector (
advanceAgentsAutoOpenEdge, unit-tested) only believes idleness after the thread's detail snapshot has synced — the chat column publishes ahydratedflag with its agent state — so loading a thread whose agent is already running never yanks the panel open. Once per batch, sheet layouts opt out, closing mid-batch is respected.Durable roster
The projection only recognized Codex-shaped collab items (
data.item), so Claude agents never produced roster rows. The Claude item shaping moved from web session-logic to@threadlines/shared/claudeSubagentActivity; the server projection uses it too, links Claude transcript task ids, settles agents fromtask.completed(including restart-synthesized completions carrying only ataskId), and maps the shaper'serroredstate tofailed. Migration 048 backfills the roster by replaying each thread's roster-moving activities through the same fold — per thread, in rowid (append) order so same-timestamp lifecycle rows cannot resurrect a settled agent.Verification
errored, taskId-only settles) — all fixed.