Skip to content

fix(kap-server): carry the live subagent roster in the session snapshot#1719

Merged
wbxl2000 merged 7 commits into
mainfrom
fix/kap-server-snapshot-roster
Jul 15, 2026
Merged

fix(kap-server): carry the live subagent roster in the session snapshot#1719
wbxl2000 merged 7 commits into
mainfrom
fix/kap-server-snapshot-roster

Conversation

@wbxl2000

Copy link
Copy Markdown
Collaborator

Related Issue

No issue — this ports the already-merged v1 fix #1589 to the v2 server.

Problem

On the v2 (kap-server) backend, a mid-swarm page refresh loses the swarm card's member list: the session snapshot does not carry the live subagent roster, and earlier subagent.spawned events (the only carriers of the swarm identity metadata) are never replayed to a client that resubscribes at the snapshot watermark. The v1 server got the same fix in #1589.

What changed

  • Added SubagentRosterTracker (ported from v1, with one adaptation: the roster is dropped only on the MAIN agent's turn.ended, so a swarm member's own turn end cannot wipe the roster mid-swarm).
  • The broadcaster updates the roster inside the per-session dispatch queue (same discipline as InFlightTurnTracker) and exposes it via getSnapshotState; cold (non-live) sessions report an empty roster.
  • The snapshot route and snapshot reader pass the roster through as the subagents field (protocol type shipped in fix(web): restore swarm member list after page refresh #1589).
  • Tests: roster tracker unit tests, broadcaster snapshot-state roster lifecycle, snapshot route passthrough, reader default.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 50da3ea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 14, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@50da3ea
npx https://pkg.pr.new/@moonshot-ai/kimi-code@50da3ea

commit: 50da3ea

@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: 0ff7fd710f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// and becomes the restore source. A subagent's own `turn.ended` flows
// through the same session queue and must not drop the roster mid-swarm.
if (event.agentId === MAIN_AGENT_ID) {
this.bySession.delete(sessionId);

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 Don't clear the roster before the transcript is durable

When a user refreshes immediately after the main turn.ended is broadcast but before the async wire append has flushed the final AgentSwarm tool result, this deletion makes /snapshot return neither the live roster nor the transcript result. The default SnapshotReader reads wire.jsonl directly in packages/kap-server/src/services/snapshot/snapshotReader.ts:100-103/:302-303, while the append log only queues a flush in packages/agent-core-v2/src/persistence/backends/node-fs/appendLogStore.ts:50-56; since the client then subscribes at the turn.ended seq, the earlier subagent.spawned events are not replayed and the swarm member list can still disappear in that race window.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: b280c94f43

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +49 to +50
roster.set(event.subagentId, {
id: event.subagentId,

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 Exclude background subagents from the snapshot roster

When an Agent call uses run_in_background=true, the core emits subagent.spawned with subagentId set to the child agent id, then registers a separate background task whose /tasks id is generated by AgentTaskService.registerTask. Storing those background spawns in the snapshot roster means a refreshed web client gets one synthetic task keyed by the agent id from snap.subagents and another real task keyed by the task id from REST, so the subagent dock can show duplicate rows and cancellation/detail actions can target the non-existent synthetic task. The roster should be limited to foreground/live-only subagents that REST cannot return, or keyed to the real task id for background runs.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: 9614dc02fc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

entry.output_preview = event.error;
return;
}
case 'turn.started': {

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 Finalize roster entries when the main turn aborts

When the main turn is cancelled/failed/blocked before spawned foreground subagents finish, this tracker keeps their last running entries until a future main turn.started. In that abort path mirrorAgentRun suppresses subagent.failed for aborts (packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts:135), and the broadcaster maps those main turn.ended reasons to an aborted session, so a page refresh after pressing Stop seeds the snapshot with still-running subagents even though no later lifecycle event may correct them. Please mark or clear the roster on terminal main turn.ended reasons instead of waiting only for the next turn.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: 604977c6e9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// task id) — tracking them here too would duplicate the row (keyed by
// agent id) and mis-target cancel/detail actions. The roster exists
// for the foreground/live-only subagents REST cannot serve.
if (event.runInBackground === true) return;

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 Drop roster entries once foreground subagents detach

This only excludes subagents that were launched with run_in_background=true, but a foreground Agent run can later be detached (Ctrl+B/timeout): AgentTool.execution waits for waitForForegroundRelease, and TaskService.detachEntry re-emits it as a detached task that /tasks serves under the generated task id. Until the next main turn starts, the snapshot still carries the original agent-id roster entry, so refreshing in that detached-but-same-turn window seeds both the roster item and the REST task, duplicating the subagent and mis-targeting detail/cancel actions; remove or update the roster when the matching detached task.started event arrives.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: dec5278f67

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: dec5278f67

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +148 to +149
if (event.agentId === MAIN_AGENT_ID) {
this.bySession.delete(sessionId);

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 Preserve aborted swarm roster past next turn

When the previous main turn ended as cancelled, failed, or blocked, the block above synthesizes failed subagent entries because the member subagent.failed events may never arrive/replay. Deleting the entire session roster on the next main turn.started drops that only snapshot source, so if the user starts another prompt and then refreshes, the snapshot contains no subagents and the interrupted swarm card loses its member rows/states again.

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: e709de6d3a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// neither the live roster nor the transcript result. A subagent's own
// turn boundaries must never drop the roster mid-swarm.
if (event.agentId === MAIN_AGENT_ID) {
this.bySession.delete(sessionId);

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 Keep the roster until the transcript is durable

When a reconnect/resync happens just after the main agent starts a follow-up turn, this delete can make the snapshot contain neither the live roster nor the previous swarm result. SnapshotReader reads wire.jsonl directly and in parallel with getSnapshotState, while the agent wire append path is asynchronous, so there is no guarantee that the <agent_swarm_result> this comment relies on has reached disk before the roster is cleared. Keep the roster until the disk transcript has observed the result (or make the snapshot reader flush/read through the append log) to avoid losing the member list in that window.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fair point in the absolute sense — turn.started does not establish a happens-before with the async append flush. We accept the trade-off deliberately:

  1. The result record is queued for the append before turn.ended, so the residual race is limited to immediately-followed turns (queued/cron prompts) — a ms-scale window, versus the minutes-long member-list loss this tracker exists to fix.
  2. The failure inside that window is transient: once the flush lands, the next refresh restores the member list from the transcript.

Fully closing it needs the snapshot reader to read through (or flush) the agent append log, which spans agent-core-v2 persistence and the kap-server reader — intentionally out of scope for this PR. Also qualified the over-strong 'durable' claim in the tracker comment in 50da3ea to say exactly this.

@wbxl2000 wbxl2000 merged commit b24a347 into main Jul 15, 2026
14 checks passed
@wbxl2000 wbxl2000 deleted the fix/kap-server-snapshot-roster branch July 15, 2026 04:40
@github-actions github-actions Bot mentioned this pull request Jul 15, 2026
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