Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/819-burst-reuse-seat.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A lead created for two simultaneous `create_child_bot` calls no longer claims a Bot it does not hold: a call that only reuses an existing Bot creates nothing, so it is no longer counted as a member of the group, and a burst of one new Bot plus one reused one makes no lead at all ([#819](https://github.com/ThinkFlowLab/FlyBot/issues/819)).
4 changes: 2 additions & 2 deletions docs/subsystems/tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Allocation puts a new Bot in a sensible place once; the self-organization below
- **Reuse before create.** `find_bots(query?)` (read-only, every tree Bot) lists the Bots in the caller's branch (for Chief: the whole tree) with `id`, `name`, `role`, `parent`, `depth`, `path`, `children`, `lead` and `via` (the caller's direct child on the path, whom it delegates to). With a query, only matches, best first: same role (`sameRole`), the query in the name or role, shared role words (stems: "test" finds "Tester"), the same project, the same domain. Empty query: an overview (first 100). `create_child_bot` checks the whole branch first: a Bot with the same role at any depth is returned as `{ reused, note }` and nothing is created. A Bot outside the caller's branch is not looked at (the daily tidy merges duplicates across branches).
- **Placement-aware creation.** `create_child_bot(name, role_description, parent?)`: `parent` (id or unique name) must be the caller or a descendant, else `parent must be you or one of your descendants`. Limits, depth and the sibling-name check apply at the target, under that parent's lock. Without `parent`, a **marked group lead** in the caller's branch whose group the new Bot belongs to takes it (most specific group first, then the shallower lead, when it has room): "FlyBot Docs" goes under "FlyBot Lead". A Bot that could lead that group itself, or a namesake, never goes under it; with no such lead the caller is the parent. The result names the parent and the tree path.
- **Teams: lead plus children.** `create_team(team, members, parent?)` with `members` as `"Name: role"` strings (2 to `maxChildren`): `"<team> Lead"` (role "Lead of the <team> group: coordinates …", `lead { key, label }` from `roles.js`) under the caller or `parent`, and the members under it: `Chief → FlyBot Lead → {Dev, Test, Maintenance, Repo}`, one slot under Chief. An existing lead of that group in the caller's branch (marked for it, named "<team>" / "<team> Lead", or coordinating it) is reused and marked; a member with the same role already in the branch is reused, and moved under the lead when it sits flat next to it (that frees its slot, so a team fits at a full level when it reuses a flat member). Members that do not fit are listed as not created. A team is refused **before anything is written** when the lead's name or a member's name is already taken anywhere on the home — the host keeps names unique across the whole roster, not just under one parent — and the error names every clashing name with the path of the Bot that holds it; that is why a clash can no longer leave a childless `<team> Lead` behind ([#764](https://github.com/ThinkFlowLab/FlyBot/issues/764)). A team that moves or marks existing Bots is a journaled run (trigger `team`, one-click undo); a fresh team is a plain create.
- **Concurrent burst (#810).** Two `create_child_bot` calls from the same caller that are in flight at the same time — the second started before the first returned — and share the first group key become **one group**: one lead for that key, reused when `leadIn()` finds one and created once otherwise, with every member of the burst under it, so the second member does not fall into the broader pre-existing group the first one was heading for. The overlap is the definition: the seats are compared after one turn of the event loop, never after a timer or a settle window, and a call that starts after the previous one returned keeps the plain placement above. Members of a burst whose first keys differ are not forced together; an explicit `parent` keeps the caller's own placement; a role the vocabulary cannot place (`area:`) is not a group. `burstGrouping: false` turns it off. Limits are unchanged and each member is created through the lead's own per-parent lock, so a member that does not fit is refused with the existing message and one failure does not undo the others; no classifier and no model call takes part.
- **Concurrent burst (#810).** Two `create_child_bot` calls from the same caller that are in flight at the same time — the second started before the first returned — and share the first group key become **one group**: one lead for that key, reused when `leadIn()` finds one and created once otherwise, with every member of the burst under it, so the second member does not fall into the broader pre-existing group the first one was heading for. The overlap is the definition: the seats are compared after one turn of the event loop, never after a timer or a settle window, and a call that starts after the previous one returned keeps the plain placement above. Members of a burst whose first keys differ are not forced together; an explicit `parent` keeps the caller's own placement; a role the vocabulary cannot place (`area:`) is not a group. A call that reuses an existing Bot creates nothing, so it takes no seat for the roster: the lead of a burst holds exactly the members that were created in it, and a burst of one new member plus one reused makes no lead (the seat count decides, as it does for a serial call) (#819). `burstGrouping: false` turns it off. Limits are unchanged and each member is created through the lead's own per-parent lock, so a member that does not fit is refused with the existing message and one failure does not undo the others; no classifier and no model call takes part.
- **Plan card**: not built. The codebase has no chat card for a model-proposed plan (approval cards belong to social's message holds), so forward allocation proceeds and names the resulting paths; deferral recorded on #447. Automatic reorganization never asks first.
- **Prompts.** Chief (and every coordinator) is told: `find_bots` before creating and reuse what fits; a project with several roles is one lead with children (`create_team`); a new Bot of a group goes under its lead; at `maxChildren - 2` children or more, group related children under a lead instead of adding another flat one; name the tree path of every new Bot; a full level, orphans and duplicates are fixed by the automatic reorganization, without asking the owner first.
- **Over capacity and orphans → #446.** A full target refuses with `limit reached` (pass a fitting `parent`, or let the tree regroup the level by role). A Bot made outside `create_child_bot` is placed by role (below). An **orphan** (its `parent` no longer in the tree) is a reparent candidate, never parked outside for good: each repair pass (startup, every `bot.created`, after a deletion) moves it with its subtree to the home its role points to (its group's lead, else the root) where it fits; a journaled, undoable move. One that fits nowhere yet is retried on the next pass.
Expand Down Expand Up @@ -126,7 +126,7 @@ The plugin's discarded `.catch(() => {})` calls carry a one-line reason comment

## Tests

Covered by the plugin's own tests under `plugins/tree/` / core test suite as wired. The `#447` tests cover reuse of a same-role Bot at depth 3 and `find_bots` (scope, fields, ranking), `create_child_bot(parent)` refusing a non-descendant (sibling, ancestor, outsider, unknown) and checking limits at the target under its lock, `create_team` (Chief → FlyBot Lead → four members, one slot; reuse of the lead and members; a flat member's slot at a full level, journal and undo; a name that is already taken anywhere refusing the whole team with no Bot created, no journal entry and the tree byte-identical), later Bots joining their group's lead (only down leads of groups they belong to), and orphan reattachment; `test/host.test.js` runs `find_bots`, `create_team` and `create_child_bot(parent)` on the real host. The `#446` tests in `test/tree.test.js` cover role grouping (Life Lead, FlyBot Lead), existing leads, no unrelated fallback, the cap at every step, the daily tidy (merge, idle, archive), journal, undo and the client notice; `test/host.test.js` runs `create_bot` and the `tree/reorg` / `tree/undo` endpoints on the real host. `#16 Option B` in `test/tree.test.js` (and the matching host test) asserts a non-Main tree Bot can create a child under itself, that `maxChildren`/depth still refuse, and that the child is not auto-granted. Runtime load (#507) is covered by `test/load.test.js` (pure) and a `test/tree.test.js` case: a child's running turns show in the coordinator's stored prompt and `list_children`, and drop to zero at `turn.end`. The archived sidebar section and the concurrent burst (#810) are covered by `test/archived-section.test.js` (nothing at N=0, the collapsed header, a merged tag, a `归档-` name, a row opening the chat, and the structure entry still hiding all of them) and by `test/tree.test.js` against a fake `fb` that holds its creates open (one lead and two children for one burst, never a second lead, an existing lead reused, different keys and the `burstGrouping: false` control landing as before, a serial call unchanged, an explicit parent opting out). The age of an open turn (#624) is covered in both: `openFor` reads the oldest start and falls back to the newer turn once one ends, an end that names its turn closes that one and not the oldest (#696, unknown and missing ids included), the line names the age only past the threshold and never calls an idle child stalled, and `list_children` adds `openForMs`/`stalled` for an open turn while an idle child keeps the old shape.
Covered by the plugin's own tests under `plugins/tree/` / core test suite as wired. The `#447` tests cover reuse of a same-role Bot at depth 3 and `find_bots` (scope, fields, ranking), `create_child_bot(parent)` refusing a non-descendant (sibling, ancestor, outsider, unknown) and checking limits at the target under its lock, `create_team` (Chief → FlyBot Lead → four members, one slot; reuse of the lead and members; a flat member's slot at a full level, journal and undo; a name that is already taken anywhere refusing the whole team with no Bot created, no journal entry and the tree byte-identical), later Bots joining their group's lead (only down leads of groups they belong to), and orphan reattachment; `test/host.test.js` runs `find_bots`, `create_team` and `create_child_bot(parent)` on the real host. The `#446` tests in `test/tree.test.js` cover role grouping (Life Lead, FlyBot Lead), existing leads, no unrelated fallback, the cap at every step, the daily tidy (merge, idle, archive), journal, undo and the client notice; `test/host.test.js` runs `create_bot` and the `tree/reorg` / `tree/undo` endpoints on the real host. `#16 Option B` in `test/tree.test.js` (and the matching host test) asserts a non-Main tree Bot can create a child under itself, that `maxChildren`/depth still refuse, and that the child is not auto-granted. Runtime load (#507) is covered by `test/load.test.js` (pure) and a `test/tree.test.js` case: a child's running turns show in the coordinator's stored prompt and `list_children`, and drop to zero at `turn.end`. The archived sidebar section and the concurrent burst (#810) are covered by `test/archived-section.test.js` (nothing at N=0, the collapsed header, a merged tag, a `归档-` name, a row opening the chat, and the structure entry still hiding all of them) and by `test/tree.test.js` against a fake `fb` that holds its creates open (one lead and two children for one burst, never a second lead, an existing lead reused, different keys and the `burstGrouping: false` control landing as before, a serial call unchanged, an explicit parent opting out, and a burst whose member was reused — no lead for one new member, and no lead naming a Bot it does not hold (#819)). The age of an open turn (#624) is covered in both: `openFor` reads the oldest start and falls back to the newer turn once one ends, an end that names its turn closes that one and not the oldest (#696, unknown and missing ids included), the line names the age only past the threshold and never calls an idle child stalled, and `list_children` adds `openForMs`/`stalled` for an open turn while an idle child keeps the old shape.

## Common mistakes

Expand Down
Loading
Loading