Skip to content

tree: cannot move a bot to a new parent; bots skipped by maxChildren never rejoin #635

Closed

Description

@hsliuustc0106

Summary

The bot tree cannot be reorganised. A bot's parent is fixed when it is created; there is no reparent/move operation. Separately, bots skipped by maxChildren never rejoin the tree even after a slot frees up.

What happens today

  • create_child_bot always creates a new bot under the caller. No tool, API or UI action changes an existing bot's parent, so the only way to reorganise a team is to delete bots and create them again — which loses their conversations.
  • adoptUnattached runs at startup, a few retries, and on bot.created. When the root is already at maxChildren, new bots are created without fields['flybot-tree'] and stay outside the tree permanently; deleting a child to free a slot does not re-adopt them.
  • A bot outside the tree has no tree tools (visibleTo = tree.isMember): list_children and create_child_bot both return ... is not available to this Bot.

Steps to reproduce

  1. Fill the root to maxChildren (8 children).
  2. Create another bot from the roster.
  3. Its record has no fields['flybot-tree']; calling create_child_bot as that bot returns create_child_bot is not available to this Bot.
  4. Delete one of the root's children to free a slot.
  5. The bot is still outside the tree; no re-adoption happens.
  6. With any team, try to move an existing bot under another parent: no tool or API exists.

Expected

  • A move_bot({ bot, parent }) tool (Main Bot only, or the bot's current parent), validating: no cycle, maxDepth, maxChildren, sibling name collisions. After a move, refresh the prompt/trace of the moved subtree. A no-op (same parent) is refused or ignored.
  • Re-run adoption when a slot frees (bot.removed, or an ensureRoot pass), so bots left outside only because the root was full join as soon as there is room.
  • Deleting a bot already re-attaches its children to its parent, so a sideways move is the missing counterpart.

Where

  • plugins/tree/src/tree.js — createChild, adoptUnattached, onBotRemoved
  • plugins/tree/src/tools.js — registerTreeTools
  • plugins/tree/README.md — document the new tool and the adoption trigger
  • plugins/tree/test/tree.test.js — tests for move and re-adoption

Acceptance

  • New tests cover: move within limits; refuse a move that exceeds maxChildren/maxDepth or creates a cycle; moved bot keeps its subtree and gets a fresh prompt; a bot outside the tree is adopted after a slot frees.
  • No behaviour change for teams under the limit.

Diagnostics

Observed on a local bench, 2026-10-21. Tree limits: maxDepth=5, maxChildren=8. Two bots created while the root was full had no fields['flybot-tree'] and got create_child_bot is not available to this Bot. No keys, tokens or personal data are included; the repro is structural, so there is no screenshot to attach.

Activity

  1. Birfy commented on Oct 21, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    Triage: labelled bug + area:automation (a missing operation in the tree tool layer, not a UI-only gap).

    Scope confirmed reproducible from the description: create_child_bot always creates a new Bot, so an existing Bot's parent cannot change, and a Bot skipped by maxChildren never rejoins after a slot frees up. Two behaviours, one root cause (the tree is only ever appended to).

    Carried by PR #636 (move_bot + adoption re-run), so this stays off the agent-ready list to avoid a second implementation; the acceptance question for review is whether move_bot refuses the four cases named there (cycle, maxChildren, duplicate sibling name, maxDepth) and whether adoption re-runs on slot release.

  2. hsliuustc0106 commented on Oct 21, 2026

    @hsliuustc0106
    CollaboratorAuthor

    Live occurrence on a real team (2026-10-21), in addition to the bench repro in the body. Same root cause; this one shows the user-visible blast radius and the only recovery path.

    Sequence:

    1. The team already had the root at maxChildren (8).
    2. Two coordinator Bots were created from the roster at that moment. Both were skipped by adoptUnattached and got no fields['flybot-tree'].
    3. Both were then unable to create their own children: create_child_bot and list_children both returned ... is not available to this Bot (visibleTo = tree.isMember).
    4. The owner deleted several old Bots, freeing slots under the root. Nothing re-adopted the two stranded Bots: bot.removed only re-attaches the deleted Bot's children. They stayed outside the tree.
    5. There is no tool, API or UI action to attach an existing Bot to the tree. The only recovery was a GUI restart (startup adoption then attached them).

    Expected from the owner's side: every Bot should be able to create child Bots. A Bot must not end up permanently outside the tree just because the root was full at its creation time, and freeing a slot should be enough to bring it in without a restart.

    PR #636's re-adoption after bot.removed would have fixed step 4, which is the step that turned a transient skip into a stuck team.

  3. added
    agent-prPull request opened by an agent
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 22, 2026
  4. added a commit that references this issue on Oct 22, 2026
  5. Birfy commented on Oct 22, 2026

    @Birfy
    Collaborator

    Claimed for the missing half, and #636 kept as this issue's only pull request.

    What was still missing on #636. The branch implemented move_bot, but not the second Expected bullet of this issue, and its README said so out loud: "A freed slot does not pull anyone back up." adoptUnattached only ran at startup and on bot.created, so a Bot kept outside because a level was full never joined after a deletion — the live case recorded above.

    What I pushed to fix/tree-move-bot-635 (tip 2787ef883aee27716466e38cd5652b0690bf6524, on top of the author's 8ae2940b; the author's commit is untouched):

    • plugins/tree/src/tree.js: onBotRemoved re-attaches the deleted Bot's children as before, then re-runs adoptUnattached on the current root, so a freed slot is used without a restart; a Bot that still fits nowhere stays outside, logged, and is retried.
    • plugins/tree/test/tree.test.js: #635: a Bot left outside because the tree was full joins when a deletion frees a slot — with maxChildren: 1 and a full root the Bot stays out; after deleting the only child it is under the root.
    • plugins/tree/src/tools.js: move_bot no longer destructures a caller id it does not use — that unused parameter was the plugins (tree) lint failure on the branch.
    • docs/subsystems/tree.md: a section on move_bot, on the deletion-triggered adoption and on these tests — the arch-docs failure, since plugins/tree/ is an owned path.
    • plugins/tree/README.md: the "freed slot" sentence now says what the code does.

    Evidence. plugins/tree/test/tree.test.js 81/81; removing only the new adoption call → 80/81, red exactly the new case; restored → 81/81. The other two reds were reproduced from the CI job logs: Parameter 'botId' is declared but never used (lint) and arch-docs: owned file(s) changed without subsystem doc update. I could not run oxlint or host.test.js locally (no core/node_modules / harness install on this box), so CI is the check for those.

  6. Birfy commented on Oct 22, 2026

    @Birfy
    Collaborator

    Closing as completed: both Expected bullets are now on main.

    • move_bot ships in plugins/tree/src/tools.js (name: 'move_bot') via PR tree: add move_bot for reorganising an existing tree #636 (2787ef88, merged 2026-10-22T05:00:27Z).
    • A Bot skipped by maxChildren rejoining once a slot frees up is on main in plugins/tree/src/tree.js: onBotRemoved calls adoptUnattached(chief.id) after organizer.repair(), and pending adoptions are caught and logged instead of becoming unhandled rejections.

    Coverage landed with it: plugins (tree) tests-ok was green at the tip for both the move path and the re-adoption path.

    Two defects found while reviewing #636 are filed separately and stay open: #677 (heightOf has no cycle guard) and #678 (maxDepth had no case).

  7. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    agent-prPull request opened by an agent
    on Oct 22, 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 automationbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions