Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 4fcfc3fe278048408737243bbff3eb21
Branch: dev/5-retired-bot-tree
Base: 16d1645
At: 2026-11-04T17:36:51Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Dev-5
Reproduction (clean base 16d16456, plugins/tree unit test)
archive_bot retires a Bot by writing fields['flybot-tree'].retired = true (core/src/host/roster.js:585) and hiding it, and core/src/host/prompt.js:46 promises the retired Bot "leaves the tree (the slot is free again)". The tree model never reads that field, so neither half happens:
plugins/tree/src/tree.js:27snapshot()collects every Bot withparent/treeRole, retired or not. The retired Bot therefore stays a tree node: its parent still lists it inlist_children, its name stays in the trace of its children's prompts, and it still counts againstmaxChildreninsrc/limits.js:21.isMember()(src/tree.js:271) agrees, socreate_child_botunder a retired parent still passes the membership guard.TREE_KEYS(src/tree.js:18) has noretired, soonFieldChangeignores the write and the retired Bot's children keepparent = <retired id>. They become a dangling subtree: the root'slist_childrendoes not show them, a child's message to the retired parent is refused withthe target Bot was retired(core/src/host/hooks.js:404), and their prompt still readsChief → Retired → Child.
Probe (two tests, node --import ./test/resolve.mjs --test test/probe-retired.test.js, base 16d16456):
not ok 1 - PROBE: a retired Bot leaves the tree and its children re-attach
actual: { id: 'bot2', name: 'Work', role: 'Work stuff', parent: 'bot1', root: false }
operator: strictEqual # snapshot()[retiredWork] should be undefined
not ok 2 - PROBE: retiring a child frees a maxChildren slot
error: 'the freed slot was not reusable'
# tests 2 / pass 0 / fail 2
The first assertion (snapshot() still holds the retired Bot) is the root of the other two; isMember(), the child re-parent and the freed slot are asserted after it.
Expected
- A Bot with
retired === trueis not a tree member: absent fromsnapshot(),isMember(),list_children, prompts and themaxChildrencount. - Retiring a Bot hands its children to its own parent (or to the current root when it was a child of the root), the same way
onBotRemovedalready does for deletion, and every moved Bot gets a fresh prompt. forestOf/adoptUnattachedalready skip retired records (src/tree.js:153,src/forest.js:16); this makessnapshot()/isMember()consistent with them.
Scope
plugins/tree/src/tree.js, plugins/tree/test/tree.test.js, docs/subsystems/tree.md, one changelog.d/ fragment. No core change.
Duplicate check
Open issues on 16d16456 searched for retired / archived in title: only #2336 (automation leases), unrelated. The retired-Bot tree work in main (d4f1b24e, #1796) touched only treeOf/archivedOf/statusOf; snapshot/isMember/onFieldChange were not changed.
Activity
fix(tree): take a retired Bot out of the tree and re-attach its children
fix(tree): take a retired Bot out of the tree and re-attach its child…