Skip to content

fix(swarm): in-process teammates claim tasks from the team's task list - #183

Open
Sagar07Kr wants to merge 1 commit into
GoDiao:mainfrom
Sagar07Kr:fix/163-teammates-claim-team-tasks
Open

Sagar07Kr wants to merge 1 commit into
GoDiao:mainfrom
Sagar07Kr:fix/163-teammates-claim-team-tasks

Conversation

@Sagar07Kr

Copy link
Copy Markdown

Closes #163
Part of #162

What changed

  • src/utils/swarm/inProcessRunner.ts: runInProcessTeammate calls getTaskListId() once and passes the result to both the claim at spawn (tryClaimNextTask) and the idle loop (waitForNextPromptOrShutdown). Both used identity.parentSessionId before. The outdated comment about parentSessionId above the spawn claim is removed, and no new comments are added.
  • src/utils/swarm/inProcessRunner.test.ts (new): runs the real runInProcessTeammate with runAgent stubbed, a temporary config dir and the lead's team name set. It covers both paths:
    • Task exists when the teammate starts: a pending task created in the team's list beforehand is owned by the teammate and in_progress by the time the teammate's first turn starts.
    • Task created after the teammate is idle: a task the lead creates once the teammate has finished its first turn is claimed through the idle loop, and its subject reaches the teammate's next prompt.
  • CHANGELOG.md: entry under [Unreleased] → Fixed.

Why

Once the lead creates a team, getTaskListId() resolves to the team name and the lead writes its tasks to that list. In-process teammates read a list keyed by the lead's session id, so they never found a task and every task stayed unowned. Both calls run in the lead's process before the teammate context starts, so getTaskListId() there returns the id the lead writes to, including when the task list override is set. Resolving it once keeps the two paths from disagreeing if the team is deleted in between.

How I tested

Bun 1.4.2 (the CI version), macOS arm64.

  • bun test src/utils/swarm/inProcessRunner.test.ts: 2 pass.
  • With src/utils/swarm/inProcessRunner.ts restored from main, both tests fail on their toMatchObject assertion: the task stays pending with no owner.
  • Each half of the fix on its own: with only the claim at spawn fixed, the idle-loop test fails; with only the idle loop fixed, the spawn test fails.
  • bun test --randomize --seed=1 src/utils/swarm/inProcessRunner.test.ts: 2 pass. The test restores the config dir, the lead's team name and the runAgent spy, and drains the SDK event the runner queues when it stops.
  • bun test (full): 1395 pass, 1 fail. The failure is filesystem API > falls back to ripgrep search outside git and still respects ignore files. It fails the same way on this machine with the tree reset to main (the search returns no entries and the test times out), so it is not caused by this change.
  • cd desktop && bun run lint: passes.
  • cd desktop && bun run test -- --run: 124 files, 1278 passed, 4 skipped. My local Node is 26, whose built-in localStorage shadows jsdom's, so I ran it with NODE_OPTIONS=--no-experimental-webstorage; without that flag, 16 files fail, mostly because localStorage is undefined. This PR doesn't touch desktop/.
  • The comment check from AGENTS.md prints nothing for the added lines.

I have not run the manual repro from the issue (teammates claiming all three tasks in the app), because it needs a model provider.

Once the lead creates a team, getTaskListId() resolves to the team name
and the lead writes its tasks to that list. In-process teammates looked
for tasks in a list keyed by the lead's session id
(identity.parentSessionId), both in the claim at spawn and in the idle
loop, so they never found one and every task stayed unowned.

The runner now calls getTaskListId() once, in the lead's process before
the teammate context starts, and passes the result to both the claim at
spawn and the idle loop. That is the id the lead writes to, including
when the task list override is set, and using one value keeps the two
paths from disagreeing if the team is deleted in between.

Closes GoDiao#163

@GoDiao GoDiao left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks @Sagar07Kr, this looks good! Resolving the task list id once and passing it to both paths is exactly what we asked for, and CI passes. We checked that the other readers of the task list (the task tools, task reminders and the stop hook) already use getTaskListId(), so these two calls were the only ones left.

One small thing in the idle-loop test before we merge:

  • inProcessRunner.test.ts:138-141: the poll stops as soon as owner is set, then asserts status: 'in_progress'. Claiming writes the owner first (claimTask), and tryClaimNextTask sets the status in a second write. If a poll reads between the two, it sees the owner with status: 'pending', and the test fails even though the code is right. Please make the poll wait for both:

    found => found?.owner === TEAMMATE && found.status === 'in_progress',

    The spawn-path test is fine as it is, because it reads the task inside runAgent, after both writes.

Could you also run the manual repro from the issue? You don't need a paid provider for it: OpenRouter has free models, e.g. nvidia/nemotron-3-ultra-550b-a55b:free, which we used for testing recently. Add OpenRouter in Settings → Providers, fetch models and pick a free one that supports tool calls. Free models are rate-limited per day, so a short run with the issue's three small tasks should be enough. Please post what you see: whether both teammates claim tasks and all three end up completed.

Thanks!

This branch has not been deployed

No deployments
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.

Agent teams: in-process teammates never claim tasks from the team's task list

2 participants