Skip to content

fix: keep subagents in spawn order in the Agents panel - #151

Merged
adampeterhiggins merged 1 commit into
mainfrom
ah/stabilize-subagent-order
Oct 5, 2026
Merged

adampeterhiggins merged 1 commit into
mainfrom
ah/stabilize-subagent-order

Conversation

@adampeterhiggins

Copy link
Copy Markdown
Owner

Problem

The Agents panel is meant to list subagents in spawn order, but with a busy fleet the rows kept moving: each agent dropped to the bottom whenever it ran a tool call, and the elapsed timers reset to a few seconds.

Three things combined to cause it:

  • The panel's order key (firstSeenAt) is the time of the oldest activity row the client holds for that agent.
  • Thread snapshots keep only the newest 500 activities. In the thread where this showed up, every task.started row was 778–2,919 rows deep, so none of them reached the client.
  • Each agent's task.progress row has a fixed id and is replaced on every tick. That keeps it in the window, but it makes it the agent's "first seen" row. Every update gave the agent a new firstSeenAt, so it re-sorted to the end. Its startedAt came from the same row, which is why the timers were wrong.

Fix

  • Server: ingestion stamps the task's spawn time on its progress rows, both the activity row and the usage row, as spawnedAt. The time is cached per task when task.started arrives. On a cache miss (restart, TTL or eviction), a new repository query, getTaskSpawnedAt, reads the earliest start or stamped progress time from the database, which still has every row. If there is none, the first observed time is used and cached, so the value stays stable.
  • Client: foldSubagentActivities sorts by spawnedAt when it is earlier than what it has seen. If a progress row is the first thing it sees for an agent, it also uses spawnedAt as that agent's startedAt. Web and mobile share this fold.

Workflow members sort on the same key, so they get the same fix.

Tests

  • Fold ordering and start time when only replaced progress rows are retained (subagentRuntime.test.ts)
  • Ingestion stamps the spawn time on the replaced progress row (ProviderRuntimeIngestion.test.ts)
  • getTaskSpawnedAt reads it from a start row, from a stamped progress row, and returns none for an unknown task (ProjectionThreadActivities.test.ts)

🤖 Generated with Claude Code (Claude Opus 5.5, T3 Code)

Each agent's task.progress row is replaced on every tick, and thread
snapshots keep only the newest 500 activities, so busy fleets lose their
task.started rows. The panel then ordered agents by their latest progress
time and moved each one to the bottom whenever it ran a tool.

Ingestion now stamps the task's spawn time on its progress rows (cached per
task, falling back to persisted rows after a restart), and the client fold
orders and times agents from it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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