Skip to content

A Bot cannot be moved to another parent, so a team can only be regrouped by hand #764

Closed

Description

@Birfy

What happened

A group needed two same-role Bots under one lead: Reviewer and Reviewer2, both peer, both reporting to Reviewer Lead. Nothing available to an agent can arrange that:

  1. create_team("FlyBot Reviewer", members: ["Reviewer: …", "Reviewer2: …"]) does not adopt the existing Bots. It answers A Bot named Reviewer already exists for each member and creates nothing, so a team can only be built from Bots that do not exist yet.
  2. create_child_bot(parent: "Chief") fails with limit reached: Chief already has 8 children (maxChildren=8), and the same limit blocks the parent that holds Reviewer.
  3. There is no operation that moves an existing Bot. So Reviewer2, created later, landed as a child of Reviewer (Chief → Backend → Reviewer → Reviewer2) instead of beside it, and the new Reviewer Lead ended up somewhere else entirely (Chief → Backend → FlyBot 实现 Lead → Reviewer Lead).

The result: the lead reports list_children as empty and message_bot refuses with a bot may message only its parent and its direct children, so it cannot dispatch to the two Bots it is supposed to lead. Only the owner, dragging in the tree view, can produce the intended shape.

Why it matters

The tree is how this team is wired: who may message whom, and whose results roll up to whom. When the intended shape can only be produced by hand, every regrouping costs the owner a manual step, and an agent that reads the tree sees a hierarchy that does not match the one the team is actually running.

What to build

  • A move operation for an existing Bot: reparent(bot, newParent), subject to the same maxDepth / maxChildren limits and refusing a move that would make a Bot its own ancestor.
  • create_team adopts existing Bots whose name or role matches a requested member instead of failing on them, the same way it already reuses an existing lead.
  • When the requested shape cannot be produced (a full level, a cycle, a depth overflow), the answer names the specific obstruction and the Bot it concerns, not just the limit.

Acceptance

  • A test builds Reviewer, then asks for a team of Reviewer and a second same-role Bot under a new lead; the existing Bot ends up as the lead's child, and list_children on the lead returns both.
  • Moving a Bot under its own descendant is refused with a message naming both Bots and the tree is unchanged.
  • A move that would exceed maxChildren on the destination is refused, and the tree is unchanged.

Out of scope

The owner's drag in the tree view (that keeps working), automatic reorganization by role, and any change to how group chats route messages.

Activity

  1. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Triaged enhancement + area:automation (Triage), not agent-ready yet — one of the three things to build already exists, and building a second reparent would duplicate it.

    Bullet 3 is already shipped, just not released

    move_bot exists on main: plugins/tree/src/tools.js:139-147 (root only), added by PR #636 in 8e80237b on 2026-10-22T04:58Z. It does exactly what the bullet asks — moves a Bot with its subtree and refuses a cycle, a maxChildren overflow, a maxDepth overflow and a duplicate sibling name — with five tests at plugins/tree/test/tree.test.js:1579-1694 (moves with the subtree, refuses a descendant / its own parent / an existing parent / duplicate names, refuses a subtree that would end below maxDepth, refuses an overflowing parent, visible to the root only).

    It is not in v0.2.3, the latest release (2026-10-21T18:34Z), which is what the host you were talking to runs. So the tool was missing for a release reason, not a code reason: after the next Release run move_bot should appear to the root, and if it still does not, that is a tool-visibility bug and a separate issue — not this one.

    Two consequences for the write-up:

    • Drop "add a move operation" from ## What to build; keep it as "verify move_bot on the next release". A new reparent beside move_bot would be two tools for one edge, which is the same duplication problem this issue is about.
    • maxChildren=8 does not go away with a move. It is enforced on the destination too (limits.js, and the move test above refuses an overflowing parent). Whatever shape you want needs a level with room — FlyBot Reviewer Lead must hang where it can hold two children. If the acceptance says "regroup the team", it has to name that place, or it is asking the mover to break a limit that exists on purpose.

    The part that is genuinely broken

    Bullet 2, and it is worse than "does not adopt": create_team's own description promises "An existing lead of that group and existing Bots with the same roles in your branch are reused" (tools.js:129), but buildTeam decides reuse by the role profile (allocate.js:195-200, sameRoleIn), not by the name. When the requested member's role text does not profile-match the existing Bot, nothing is reused, the lead is created first (:215), and only then does each member fail — on the name — with A Bot named Reviewer already exists (:230-233). Net effect in your repro: half a team, a lead with no children, and an error that names the symptom instead of the Bot.

    So the real acceptance is:

    • A requested member whose name matches an existing Bot in the caller's branch is that Bot (reused), whatever its role text; the role text in the request must not be able to fake a second Bot with the same name.
    • A name that exists but cannot be adopted (it sits outside the branch, or moving it would break maxChildren / maxDepth) is refused before the lead is created, naming the Bot, where it sits, and which limit blocks it — no half-built team.
    • Red evidence: revert the name match and the "same-name different-role" case goes red; move the refusal after createLead and the "no half-built team" case goes red (a Reviewer Lead with zero children is already created today).

    Decision needed from the owner before agent-ready: adopt-by-name is a change to who a team can absorb, and it can move an existing Bot without the root calling move_bot — that is a policy call, not a bug fix. Say adopt, or say "fail loudly and leave the shape alone"; either way the acceptance above is what I will pin.

  2. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    This has been sitting without a state label, so the board reads it as unclaimed work when it is actually waiting on one sentence. Setting needs-human so it stops looking open for a Bot.

    The question, unchanged since the triage comment: create_team's tool description promises "existing Bots with the same roles are reused", but buildTeam matches on the role text (allocate.js:195-200), not the name, then creates the Lead (:215) before the members collide (:230-233) — so the visible result is an orphan Reviewer Lead plus A Bot named Reviewer already exists. Two ways out, and they are different products:

    • Adopt by name: a same-named Bot is the member; a team can therefore absorb Bots that already exist (and move_bot can reparent them afterwards). More useful, and it also lets a team rearrange an existing tree.
    • Fail loudly, shape unchanged: refuse before creating the Lead, and name every collision. Smaller, no new authority.

    Say "adopt" or "fail loudly" and I write the acceptance and flip it to agent-ready. Note bullet 3 (moving a Bot) is already shipped as move_bot in 8e80237b and will be in the next release, so only the create_team half is left.

  3. added
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 22, 2026
  4. Birfy commented on Oct 22, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    agent-ready, scoped to the half that needs no product decision. I am taking the second option — refuse before creating anything — because the first one (adopt an existing Bot by name) hands a team the power to absorb Bots and to reorganise a tree that already exists, and that is the owner's call, not a triage call. If the owner later says "adopt", it is a new issue on top of this fix, not a rewrite.

    The defect, at line level on main

    buildTeam (plugins/tree/src/allocate.js:191) reuses only by role text (sameRoleIn(nodes, callerId, d.name, d.role, taken) at :198, against taken at :196), so a same-named Bot with different role text is not reused. It then creates the Lead first (leadId = await createLead(...) at :215) and only afterwards tries the members (createUnder inside the loop at :227-233, whose failure is swallowed into d.error at :233). Result: an orphan Reviewer Lead with no children, plus A Bot named Reviewer already exists — two writes and one half-built team.

    Acceptance — what the PR is judged against

    1. Every member that will not be reused is checked for a name collision before createLead is called, using the same uniqueness rule that createUnder enforces today (not a second rule invented in buildTeam).
    2. On a collision the call fails with zero new Bots: no Lead, no member, and the tree snapshot is byte-identical to the one before the call. The message names every colliding name and the existing Bot's path, not just the first.
    3. The success path is unchanged: a team of brand-new Bots still creates the Lead and all members, the moved/reused members behave as now (:204-226), and the journal is committed only when something changed (:236).
    4. Red evidence: with the pre-check removed, the new case fails with an orphan Lead present (assert the node count and that no Bot named <label> Lead exists); checking only the first collision leaves the "names every collision" case red; checking after createLead leaves the "zero new Bots" case red. plugins/tree stays green otherwise (currently 110/110).

    Not in scope, on purpose: adopting an existing same-named Bot. That changes who may reshape a tree; the owner decides it separately if they want it.

  5. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    and removed
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 22, 2026
  6. added a commit that references this issue on Oct 23, 2026
    16c2945
  7. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Claimed: branch fix/764-team-name-clash, PR #800 (tip 16c2945f, base fd205c40).

    Scope is the one Triage fixed: refuse the team before the lead is created, so nothing is written — no lead, no member, no move, no journal run, and the tree is byte-identical. buildTeam's reuse-by-role check stays as it is; every name the team would create is checked against the whole roster (roster.js:65 is the host rule that used to fire only after the lead existed), and the error names every clashing name with the path of the Bot that holds it.

    Not in this PR: adopting an existing Bot by name (a product decision, not this bug), and the narrow cross-parent race where two teams under different parents create the same name concurrently — the host still refuses, so an orphan lead is possible there; noted as residual risk in the PR body.

  8. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 23, 2026
  9. Birfy commented on Oct 23, 2026

    @Birfy
    CollaboratorAuthor

    Fixed by #800, merged into main as 245881c. Closing.

  10. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:automationScheduled tasks and automationenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions