Skip to content

@flybot/tree (recursive-bot): organize bots as a tree #6

Open

Description

@Birfy

Part of #3 (umbrella). Implemented in #2. Owner: recursive-bot.
Source: Birfy/recursive-bot, branch flybot-tree, ds-bot/plugins/tree/ (current tip 324fb59).

Goal

Organize bots as a recursive tree. A Root coordinator splits a task, creates child bots under itself, and delegates level by level:

  • Each layer sees only its direct children: its prompt lists them, and it can message only them and its parent. It never plans for grandchildren; each child splits its own part.
  • Only leaves do the work. A bot with children acts as a coordinator: it splits, delegates in parallel and aggregates. A bot without children answers the request itself.

Design

  • Plugin @flybot/tree (name: 'flybot-tree', inject: ['flybot'], cordis-style apply(ctx, config)) in ds-bot/plugins/tree/. Only src/adapter.js touches ctx.flybot (flybot API v1, ds-bot/types/flybot.d.ts).
  • Layout: index.js (wiring), src/adapter.js, src/tree.js (model), src/limits.js + limits.json, src/middleware.js, src/tools.js, src/prompt.js, client.js (browser), test/mock-flybot.js, test/tree.test.js. ds-bot's test/tree-plugin.test.js runs the plugin tests under ds-bot npm test, and scripts/lint.mjs lints plugins/.
  • Root is created automatically. tree.ensureRoot() (async, idempotent) creates Root (name configurable with config.rootName) when no tree bot has a null parent. The tree starts with only the root; children are created during conversations.

Data (fields)

All reads and writes go through ctx.flybot.scope('flybot-tree'), so the data is stored under state.bots[id].fields['flybot-tree'] and never under fields.shared:

  • parent: parent bot id; null for the root.
  • treeRole: role description. A bot is a tree member if it has treeRole or parent.
  • The system prompt itself lives on the bot record (BotView.systemPrompt, set with bot.setSystemPrompt). It is not a field.
  • Events use on('change' | 'bot.removed', fn, { all? }). change delivers only this plugin's fields; tree subscribers react to parent and treeRole. bot.created is left to social; tree does not subscribe to it.

Tools (visible only to tree bots; visibleTo is sync)

The caller always comes from the tool context ({ botId }), never from the arguments, so a bot can create children only under itself.

  • create_child_bot({ name, role_description })
    • name: string, 1–60 characters (after trimming), required.
    • role_description: string, 1–1000 characters (after trimming), required.
    • Duplicate names among siblings are rejected (case-insensitive), as are limit violations.
    • Awaits bot.create(...) and returns { created: { id, name, depth } }, where id is the resolved string id. It then rebuilds the parent's prompt so the new child is listed.
  • list_children(): takes no arguments. Returns { children: [{ id, name, role }], depth, limits }.
  • Validation: tools.register keeps only type/description/enum/items from JSON Schema and drops minLength/maxLength. So each execute runs validateArgs, which checks required fields, string type, minLength/maxLength and unknown arguments (additionalProperties: false). Errors come back as { error }.

Limits

  • limits.json: { "maxDepth": 5, "maxChildren": 8 }. Depth counts the root as 1.
  • Overridable through plugin config maxDepth / maxChildren (positive integers; invalid values fall back to limits.json).

Prompt injection

src/prompt.js builds the system prompt for every tree bot, so a new child inherits the tree rules. Each prompt has the bot's name, role and position trace (e.g. Root → Work → Dev), the rule "message only your parent and direct children", and the tool usage.

  • Leaf: do the work, answer concisely, and create a child only for a clearly separate domain.
  • Coordinator: lists its direct children. Its rules: split the request; delegate only to children whose scope fits; create a child only when none fits (check list_children, no duplicates); send in parallel; aggregate the answers (pass a single answer through); start with a delegation trace.
  • Prompts are rebuilt when a child is created or removed.

Messaging rules (send middleware tree, before: ['social'])

  • Tree bot → tree bot: allowed only between a parent and its direct children. Grandchildren, siblings and the grandparent are denied.
  • Tree bot → local bot outside the tree (e.g. Chief): denied.
  • Local bot outside the tree → tree: allowed only to the root. Any other tree node is denied (e.g. Chief → child = deny, Chief → Root = pass).
  • Outside → outside (local): no opinion.
  • Remote traffic is deferred to social. Remote targets (target.remote === true, fallback bot.isRemote(to), i.e. { kind: 'remote' } / @handle) and remote senders get no opinion (undefined). The social plugin then applies its friendship checks and approval. Remote bots are never tree members, and their fields are not read.
  • Bypass: allowed tree edges return { action: 'allow', bypass: ['social'] } only when plugin config bypassSocial: true is set. The host honours it only if config.flybot.bypass.tree lists social.

Lifecycle

  • On bot.removed (fires after deletion), the parent is read from the event's last record, bot.fields['flybot-tree'].parent. The removed bot's children are re-attached to that parent, and the parent's system prompt is rebuilt with bot.setSystemPrompt.
  • Removal of a bot that is not in the tree is ignored. Subscribers get a removed event.

UI

  • The flybot.sidebar.section slot (id flybot-tree, order 100) shows a collapsible "Tree" section.
  • It is built in the browser from ctx.flybot.bots() (reading fields['flybot-tree']) and refreshed on fields.on('change') for parent/treeRole and on on('bot.removed').
  • Hand-written client.js in the window.__ModuleLoader__.load format, with no build step.

Tests

  • Plugin tests: 21/21 (node --test "test/*.test.js", Node 24), against test/mock-flybot.js. The mock mirrors the real API: scope(), async create, null removes a key, on(event, fn, { all }), the real send-target and bot.removed shapes, and schema keywords dropped by tools.register. Coverage:
    • scoped fields
    • the resolved id from create_child_bot
    • parent/child edges
    • denying sends to local bots outside the tree
    • outside → root only
    • remote pass-through
    • min/maxLength validation
    • limits
    • re-attaching children on removal
    • client forest
  • recursive-bot flybot-tree: ds-bot npm test 538/538, plus build-client.mjs --check and lint pass.
  • flybot-dev main (a730804) with the plugin copied in: npm test 561/561, plus build-client.mjs --check and lint pass.

Commit history (d0706c5..324fb59)

  • d0706c5 feat(tree): @flybot/tree plugin package (parent field, send middleware, tree tools, sidebar)
  • 1a4c9b7 feat(tree): move @flybot/tree into FlyBot layout at ds-bot/plugins/tree, wire into ds-bot npm test and lint
  • f3755cc feat(tree): match finalized flybot interface (sync fields, create->id, setSystemPrompt, bot.removed, sync visibleTo, client tree from bots())
  • 25b0663 feat(tree): flybot.on('bot.removed') only, namespaced fields, browser reads fields['flybot-tree'], parent from removal record
  • fdb4a3f feat(tree): pass remote send targets (target.remote, fallback bot.isRemote) to social; bot.created left to social
  • a7e51c5 fix(tree): align with real flybot API v1: scope('flybot-tree'), awaited bot.create, deny tree → outside local bots, validate min/maxLength in execute, mock matches the real API
  • 324fb59 fix(tree): local non-tree senders may message only the tree root

Open items

Activity

  1. added a commit that references this issue on Oct 14, 2026
  2. added
    enhancementNew feature or request
    area:automationScheduled tasks and automation
    holdA person froze this issue or PR; agents and automation leave it alone
    on Oct 19, 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 requestholdA person froze this issue or PR; agents and automation leave it alone

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions