Skip to content

fix(swarm): retain model identity while member is busy - #1267

Open
shua0326 wants to merge 2 commits into
1jehuang:masterfrom
shua0326:fix/swarm-busy-member-model-identity
Open

shua0326 wants to merge 2 commits into
1jehuang:masterfrom
shua0326:fix/swarm-busy-member-model-identity

Conversation

@shua0326

@shua0326 shua0326 commented Sep 15, 2026

Copy link
Copy Markdown

Fixes #1266.

Problem

swarm list and swarm status read provider identity from a member's live agent handle under try_lock(). That is deliberate: the roster must never block on an agent that is mid-turn. The consequence is that the lock is exactly the state a busy member is in, so its own Model: line was dropped.

Observed symptom: a worker calling swarm list from inside its own turn sees the coordinator's entry with a model and its own entry with none.

Change

  • Keep the non-blocking try_lock() behavior unchanged.
  • When the live handle is unavailable (busy, or absent after the member stops), fall back to the model/provider/effort already retained on SwarmMember.runtime by SubagentStatus.
  • Live identity stays authoritative whenever the lock is free.
  • Thread the retained runtime snapshot through the swarm list enrichment path and apply the same fallback to swarm status.

Design notes:

  • Identity is roster/status metadata that is already exposed. This does not inject model names into prompts and does not export JCODE_MODEL (that name is a config override for nested runs, so reusing it would change behavior elsewhere).
  • No session files are read: the fallback uses in-memory state, so it cannot surface stale persisted routes. This matters because a mid-turn model switch would otherwise be reported incorrectly.
  • An earlier draft read the persisted session record instead. It was discarded because it added synchronous file I/O to the roster path and could report stale identity.

Tests

New/updated coverage in this PR:

  • comm_list_includes_member_status_and_detail: roster entry for a member whose agent lock is held reports the retained provider/model/effort.
  • comm_status_reports_retained_identity_while_member_is_busy: the status surface reports retained identity while the member is busy.
  • member_identity_tests: retained identity is used when live identity is unavailable, and live identity wins when it is available.
  • communicate_status_returns_busy_snapshot_for_running_member (existing workflow, restored): it previously timed out before its assertions. Root cause: both raw debug clients are independent root sessions, and swarm_id_for_session gives each root session its own session:{id} swarm, so ensure_same_swarm_access refused the cross-session comm_list/comm_status calls. Opting both clients into one shared swarm makes the workflow run again (passes in 1.4s instead of timing out at 5s).

Observed locally:

Check Result
Roster regression pass
Status regression pass
Status regression against pre-change implementation (negative control) fail: None vs OpenCode Go, so the test guards the change
Identity precedence pass
Restored busy-status workflow pass (1.4s)

Real-daemon acceptance check (no stubs, the shipped path): daemon started with a shared swarm id, worker spawned through the public swarm tool, worker then calls swarm list from inside its own turn.

  • With this change, the worker's own entry shows Status: running, Activity: thinking, Model: Gemini/gemini-2.5-pro.
  • With the pre-change binary (a195ceecc), the identical probe shows its own entry with Status, Activity and Work but no Model: line, while the coordinator entry below does have one.

Edge cases and known limits

  • The restored end-to-end workflow cannot assert identity: in that harness the member runtime is never populated during a turn, so identity only appears from the live path after the turn ends. Identity coverage therefore lives in the seeded regressions.
  • Stopped members are covered by the same fallback path, since it applies whenever the live handle is missing; there is no separate assertion for that case.
  • The fallback can only be as fresh as the last SubagentStatus update, which is the same data the inline swarm strip already uses.

CI status

Workflow runs for this fork PR require maintainer approval, so the CI jobs have not executed. I ran the non-compiling guardrail scripts locally on this branch: module declarations, dependency boundaries, and wildcard re-export pass. The code-size, test-size, panic, and swallowed-error ratchets report failures identical to untouched upstream/master (src/cli/login.rs, src/cli/provider_init.rs, src/cli/commands_tests.rs, tests/e2e/test_support/mod.rs, src/cli/ssh*.rs, src/cli/tui_launch.rs); none involve files in this PR.

`swarm list` and `swarm status` use `try_lock()` to avoid blocking on an
agent that is in the middle of a turn. That makes a worker invoking a
swarm tool unable to see its own model, even though SubagentStatus has
already retained the model on its SwarmMember runtime snapshot.

Use the retained in-memory runtime identity only when the live agent is
unavailable. Keep live identity authoritative when the lock is available.
This avoids synchronous session-file reads and stale persisted metadata.

Add a roster regression that holds the peer agent lock, reproducing the
worker-self query path, plus focused precedence tests.
@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

Safe to merge.

Summary

This PR preserves swarm-member provider and model identity when a live agent cannot be locked without blocking. Retained runtime identity is used as a fallback for status requests, while successfully acquired live identity remains authoritative. Regression and end-to-end coverage now exercise the busy-member workflow.

Reviews (2) · Last reviewed commit: "test(swarm): restore busy-member status ..."

The busy-member status workflow never ran. Both raw debug clients are
independent root sessions, and `swarm_id_for_session` gives each root
session its own `session:{id}` swarm, so `ensure_same_swarm_access`
refused the cross-session `comm_list`/`comm_status` calls. The wait for
the peer to enter `running` timed out and the test never reached its
assertions, on master and on this branch alike.

Opt both clients into one shared swarm so the workflow runs again, and
add a deterministic status-path regression that seeds the member's
retained runtime and holds its agent lock, covering the retained
identity fallback on the status surface.

Evidence:
- restored workflow passes in 1.4s (was a 5s timeout).
- new status regression fails without the fallback (None vs OpenCode Go).
- roster regression and identity precedence tests still pass.
@shua0326

shua0326 commented Sep 15, 2026

Copy link
Copy Markdown
Author

P2 is valid, and it is pre-existing rather than introduced here.

Reproduction

communicate_status_returns_busy_snapshot_for_running_member fails deterministically:

  • 3/3 runs on this branch
  • 1/1 run on untouched upstream/master (74e7a4be5), with no local changes

Failure: peer should enter running state: timed out waiting for member ... to reach status running.

Root cause

Both raw debug clients are independent root sessions. swarm_id_for_session gives each root session its own session:{id} swarm unless JCODE_SWARM_ID opts in to a shared one (crates/jcode-app-core/src/server/util.rs), which is intentional and covered by independent_root_sessions_have_distinct_swarm_ids.

Consequently ensure_same_swarm_access refused the watcher's cross-session calls:

  • comm_list returned only the requesting session, so the wait at line 332 could never observe the peer.
  • comm_status was refused for the same reason and the error event was skipped by the read loop, surfacing as a 5s deadline.

The test therefore never reached its assertions.

Fixes in this PR

  1. Opt both clients into one shared swarm so the workflow actually runs. It now passes in 1.4s instead of timing out at 5s.
  2. Add a deterministic status-path regression that seeds the member's retained runtime and holds its agent lock, covering the retained identity fallback on the comm_status surface. Negative control: it fails against the pre-change implementation (None vs OpenCode Go).

Note on asserting identity in the restored workflow

The restored end-to-end test cannot assert the retained provider/model: in that harness the member's runtime snapshot is never populated during the turn. Instrumenting the busy window shows provider=None model=None for the whole turn, with identity appearing only from the live path after the turn ends (status=ready). Adding an identity assertion there would fail regardless of this change, which is why the identity coverage lives in the seeded regression instead.

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.

swarm list omits model identity for a busy worker

1 participant