@dirwin517/bot-farm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.idea/bot-farm.iml +9 -0
  2. package/.idea/misc.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/LICENSE +21 -0
  6. package/README.md +584 -0
  7. package/package.json +13 -0
  8. package/src/api.mjs +567 -0
  9. package/src/cli.mjs +241 -0
  10. package/src/craft.mjs +172 -0
  11. package/src/db.mjs +93 -0
  12. package/src/farmhands.mjs +111 -0
  13. package/src/git.mjs +578 -0
  14. package/src/identity.mjs +199 -0
  15. package/src/legacy-defaults.mjs +44 -0
  16. package/src/mcp.mjs +681 -0
  17. package/src/mesh.mjs +222 -0
  18. package/src/packets.mjs +125 -0
  19. package/src/patches.mjs +141 -0
  20. package/src/pipelines.mjs +2145 -0
  21. package/src/pricing.mjs +91 -0
  22. package/src/projects.mjs +148 -0
  23. package/src/public/classic.html +1810 -0
  24. package/src/public/index.html +3284 -0
  25. package/src/questions.mjs +69 -0
  26. package/src/quota.mjs +76 -0
  27. package/src/registry.mjs +88 -0
  28. package/src/replay.mjs +151 -0
  29. package/src/rooms.mjs +320 -0
  30. package/src/server.mjs +765 -0
  31. package/src/store.mjs +546 -0
  32. package/src/supervisor.mjs +1803 -0
  33. package/src/tasks.mjs +191 -0
  34. package/src/template.mjs +86 -0
  35. package/src/tools.mjs +241 -0
  36. package/src/workspaces.mjs +605 -0
  37. package/src/ws.mjs +130 -0
  38. package/src/yaml.mjs +266 -0
  39. package/test/board.mjs +108 -0
  40. package/test/craft.mjs +281 -0
  41. package/test/extend.mjs +145 -0
  42. package/test/flow.mjs +165 -0
  43. package/test/handoff.mjs +228 -0
  44. package/test/linked.mjs +83 -0
  45. package/test/loop.mjs +168 -0
  46. package/test/mock-opencode.mjs +217 -0
  47. package/test/parallel.mjs +135 -0
  48. package/test/patches.mjs +93 -0
  49. package/test/repos.mjs +118 -0
  50. package/test/roles.mjs +157 -0
  51. package/test/shot.mjs +65 -0
  52. package/test/smoke.mjs +70 -0
  53. package/test/transport.mjs +131 -0
  54. package/test/workspaces.mjs +228 -0
  55. package/test/workspaces.mjs.tmp +0 -0
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="JAVA_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="inheritedJdk" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ </component>
9
+ </module>
package/.idea/misc.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="graalvm-jdk-21" project-jdk-type="JavaSDK">
4
+ <output url="file://$PROJECT_DIR$/out" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/bot-farm.iml" filepath="$PROJECT_DIR$/.idea/bot-farm.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arupex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,584 @@
1
+ # botfarm
2
+
3
+ A kanban style process manager for [opencode](https://opencode.ai) sessions: run several agents at once,
4
+ watch what they are burning, steer them without entering them, and — if you allow it — let them talk
5
+ to each other.
6
+
7
+ ```
8
+ npm link # or: node src/cli.mjs up
9
+ botfarm up # starts opencode serve if needed, opens the dashboard
10
+ ```
11
+
12
+ The dashboard is at `http://127.0.0.1:4777`. No dependencies; Node 20+ and `git`.
13
+
14
+ ---
15
+
16
+ ## BotFarm: workspaces and workstreams
17
+
18
+ The dashboard is organised around two things:
19
+
20
+ - **Workspace** — a folder and its opencode config. The first one is the folder you start BotFarm in
21
+ (change it with `"defaultWorkspace"` in `~/.botfarm/config.json` or `botfarm up --workspace <dir>`); open
22
+ more with the folder browser under the workspace name. A workspace's bots are defined at
23
+ its root, created with the built-in set if missing — a `botfarm/` folder with one file per definition,
24
+ named after its id:
25
+ - `botfarm/<id>.agent.botfarm.yml` — one agent: `{ title, role, prompt, model?, variant?, tools?, tiers?, may_spawn? }`
26
+ - `botfarm/<id>.pipeline.botfarm.yml` — one pipeline: `{ title, description?, limits?, stages: [{ id, title?, persona | human: true, receives: [stage ids], after?: [stage ids], split?: tasks, parallel?, difficulty?, limits?: { minutes, tokens, usd }, prompt }] }`
27
+ - `botfarm/routing.botfarm.yml` — model routing by difficulty (off until `enabled: true`)
28
+ - `botfarm/mcps/*.js` — your own tools, served to every bot in the workspace (see below)
29
+
30
+ Older `botfarm-agents.yml` / `botfarm-pipeline.yml` files are split into these on first start and kept as
31
+ `.bak`. Edit them in the app (Pipelines / Agents in the sidebar, as a form or as that one file's YAML) or
32
+ by hand; hand edits are picked up without a restart.
33
+ - **Workstream** — one story going through one pipeline. Starting a pipeline always makes a new
34
+ workstream with its own branch and worktree, its own team of bots, its own channel and its own board.
35
+ Two DoUserStory runs are two workstreams side by side. A workstream keeps the pipeline definition it
36
+ started with, so editing the pipeline later changes only new workstreams.
37
+
38
+ **Parallel work.** A stage normally starts when the one before it hands off. Give it
39
+ `after: [stage ids]` to start as soon as those are done instead — dev and QA both `after: [analyse]`
40
+ build and write tests side by side (TDD), and a stage `after: [build, tests]` waits for both.
41
+ `split: tasks` on a stage fans it out: the stage it waits on is asked for a `tasks` list
42
+ (`[{ title, detail }]`) on its handoff, each item becomes its own card, and up to `parallel: N` bots of
43
+ that kind (default 3, max 10; overridable when you start the workstream) work them at once in the same
44
+ worktree — extra bots are added to the team as needed. The next stage waits for every piece and gets
45
+ their handoffs joined. Use `{{#item}}{{title}} {{detail}}{{/item}}` in the split stage's prompt, or let
46
+ BotFarm add "Your part — n of N" itself. See the `story-parallel` example.
47
+
48
+ **Limits.** `limits: { minutes, tokens, usd }` on a stage applies to each of its cards (each piece of a
49
+ split); `limits:` on a pipeline, or the Limits box when you start one, applies to the whole workstream.
50
+ When a card reaches a limit its bot is stopped and the card waits in Needs you (Continue gives half as
51
+ much again, Set limits… for exact numbers, or Stop); when the workstream reaches one every bot on it
52
+ stops until you raise it. Limits can be changed any time from the workstream header.
53
+
54
+ The workstream page has the team down the left (grouped by kind, live status and spend), the board
55
+ with the chat under it, and a **Farm** view of the same board: cards are crops moving from the shed to
56
+ the field to the silo, and each bot walks to the card it is working on.
57
+
58
+ **BotFarm's MCP server** is called `botfarm`: bots see `botfarm_task_complete`, `botfarm_room_post`,
59
+ `botfarm_notes_read` and so on (it used to be `botfarm`; old worktrees are renamed automatically).
60
+
61
+ **Your own tools.** Every `.js` file in `botfarm/mcps/` exports `name`, `description`, `inputSchema` and
62
+ `execute(args, ctx)` and becomes `botfarm_<name>` for the workspace's bots. `ctx.repoRoot` is the calling
63
+ bot's worktree, `ctx.exec(cmd, args)` runs a program there, `ctx.log(...)` writes to the test bench. Saving
64
+ a file reloads it; bots get the new list on their next idle moment. **Tools** in the sidebar is the test
65
+ bench: a form from the schema (or raw JSON), where to run it, the result and log, and the source to edit.
66
+
67
+ **Switching a bot's model.** Model… in a bot's panel picks another model and reasoning level for that bot
68
+ from its next turn (Opus for a hard fix, Sonnet or Haiku to save money), kept across restarts, without
69
+ touching the agent file.
70
+
71
+ **Keeping it cheap — no paid model involved in any of these:**
72
+ - *Routing by difficulty*: each card is sized easy / normal / hard from its words, length and criteria
73
+ (or `difficulty:` on a stage or split piece) and gets the model from `routing.botfarm.yml` or the agent's
74
+ `tiers:`. The card shows its size and model. All of it is editable in the app: **Model routing** in
75
+ the sidebar, **Difficulty** on each stage in the pipeline editor, **Model by difficulty** in the agent editor.
76
+ A tier can cap its model — *Quota and fallback*: e.g. up to $5 a day on Opus, then Sonnet (also tokens or
77
+ minutes, per day / week / month / workstream). Spend is counted per workspace per model; once it runs out,
78
+ new cards take the fallback and bots already on it move over, at once or on their next turn.
79
+ *Per agent*: an agent's `tiers:` override the workspace rules size by size (give only the reviewer its
80
+ own `hard:` and it still uses the workspace's easy/normal), and apply even with workspace routing off.
81
+ An agent's own quota counts only that agent's spend (e.g. "the reviewer gets $3/day of Opus"); a
82
+ workspace quota counts everyone's. **Model routing → What each agent gets** shows the effective model,
83
+ reasoning, quota and where each came from, per agent and size. One-line YAML maps
84
+ (`quota: { usd: 3, per: day }`) work in hand-edited files.
85
+ - *Apply to my repos*: from the harvest (**Apply to my repos…**) or any time after (**Apply to repos…** in the
86
+ workstream header), every repo in the workstream — parent, services, linked repos — becomes a patch of what
87
+ its worktree has that your checkout does not (edits, new files, commits on the branch, from the merge-base),
88
+ leaving out BotFarm's own files. The dialog shows, per repo, where it goes (path and current branch), the
89
+ files, whether it applies cleanly / needs a 3-way merge / will conflict / is already there, and warns about
90
+ your own uncommitted edits to the same files. Applying never commits or stages a clean patch: it lands as
91
+ working-tree changes to review and commit yourself. Otherwise it falls back to a 3-way apply (conflict
92
+ markers) and then `--reject` (.rej files). **Undo last apply** reverses the clean ones. Patches are kept in
93
+ `.botfarm/workstreams/<id>.patches/` and can be downloaded. API: `GET /api/projects/:id/patches`,
94
+ `…/patches/file?rel=`, `POST …/patches/apply {repos?}`, `POST …/patches/undo`.
95
+ - *Repos from anywhere*: a workstream's worktree can hold repos from outside the workspace. **Add repo**
96
+ in the new-workstream dialog (e.g. `~/workspace/spt`, optional folder name) links it to the workspace;
97
+ each workstream it is ticked for gets a worktree of it on the same branch, mounted at `<worktree>/spt/`
98
+ (hidden from the parent's git status via `info/exclude`). **+ Repo** on a running workstream adds one after
99
+ the fact; the bots are told in the chat, the team notes and every card ("Repos in this worktree").
100
+ Card diffs, snapshots, PR stats and deleting the workstream (worktrees and `botfarm/` branches) cover them.
101
+ API: `POST /api/workspaces/:id/repos {path, as?, running?}`, `DELETE …/repos?path=`, `POST /api/projects/:id/repos {path, as?}`.
102
+ Docker MCPs see it only if the path is under a mounted folder (`~/workspace`, `~/worktrees`).
103
+ - *Send back (fix loops)*: Plan → Code → Verify → Fail → Code → Verify → Pass. A checker that finds a
104
+ failing test, broken build or missed criterion calls `botfarm_send_back { task_id, to, reason, failures, files }`
105
+ instead of fixing it. The earlier card goes back to To do with what failed and its last handoff kept
106
+ ("↩ round 2"), preferring the bot that did it (another bot of that kind takes it after ~90s if that one is
107
+ busy or gone); the checker's card waits and comes back to the same checker to verify ("↻ verify 2"). For a
108
+ split stage only the pieces that touched the named files go back, or a new "Fix:" piece is added. Any
109
+ earlier stage is a valid target unless the stage says `send_back: [build]` or `send_back: false`;
110
+ `max_rounds` (per stage or pipeline, default 3, also in the pipeline editor as *Fix rounds*) caps it, and
111
+ the same failure twice stops too — then you choose *One more round*, *Accept as is* or *Stop*. You can
112
+ send a done card back yourself: drag it to To do, or *Send back…* on the card. Fix rounds earn little XP;
113
+ the checker gets a little for catching them.
114
+ - *Loop detection* counts a call as repeated only when the tool **and every argument** match (a fingerprint of
115
+ the whole input, key order ignored) — three greps for different things are work. An edit in between resets
116
+ the count, so build → fix → build is fine.
117
+ - *Trimmed handoffs*: the next bot reads a brief (long code blocks become pointers, repeats go, cut at
118
+ ~1800 characters); `botfarm_handoff(stage)` fetches the full text. For a local model instead, set
119
+ `"handoffs": { "local": { "url": "http://localhost:11434", "model": "qwen2.5:3b" } }` in
120
+ `~/.botfarm/config.json` (falls back to the rules).
121
+ - *Team notes*: `botfarm_notes_write` / `botfarm_notes_read`; every card lists the notes and the files
122
+ teammates already read.
123
+ - *Loop stop*: the same call three times, five failures in a row, or ~250k tokens without an edit pauses
124
+ the card and asks you (`"loops": { "tokensWithoutEdits": N }` to tune).
125
+
126
+ **Card events in the chat.** Every finished card posts a card to the channel: who did it, how long it
127
+ took, tokens, cost, model, XP, every file that changed on disk while it ran, with the diff from where the file stood when the card
128
+ began — whatever changed it: opencode's tools, an MCP server, a script (files it did not list in its
129
+ handoff are marked) — open *Changes*, and any screenshots (images among its files, or image results from its tools; click to
130
+ enlarge). Split stages post one more when all their pieces are in. These never wake the other bots.
131
+
132
+ **Pause / Resume** in a workstream's header stops every bot on it (mid-card) until you resume; resuming
133
+ tells each stopped bot to pick its card up again.
134
+
135
+ **When a workstream finishes**: a *PR packet* (`.botfarm/workstreams/<id>.pr.md` — criteria with the tests
136
+ behind them, files, commits, how to test, open questions, cost; **Open PR…** pushes and runs `gh pr
137
+ create`), a *harvest* (a small celebration and a line in the home page's harvest log) and XP.
138
+
139
+ **Levels.** Each kind of bot earns XP per workspace for clean work only — a handoff, no pauses, under
140
+ budget, nothing left open. Levels bring hats on the farm and small perks: level 3 +10% card limits,
141
+ level 5 the first limit hit extends itself once, level 8 +20%.
142
+
143
+ **Replays.** Replay on a workstream scrubs through everything that happened (farm, team, chat). Save
144
+ replay file gives one portable JSON; open it from the home page of any BotFarm.
145
+
146
+ A stage with open questions still hands off: the questions go to the next stage with its card. Set
147
+ `hold_on_questions: true` on a stage (or "wait for me" in the editor) to stop there until you press
148
+ **Continue** on the card, optionally with answers that every later stage receives.
149
+
150
+ Questions for you look like the ones Claude and opencode ask: a bot's `botfarm_ask_human` (or a held
151
+ stage's open questions) can offer choices per question — pick one, or several — and every question can
152
+ also be answered in your own words; a question without choices is open-ended. They appear at the bottom
153
+ of the chat and on the card, answerable in place.
154
+
155
+ An agent's `tools:` list is enforced on every turn (everything else built in is switched off; the botfarm
156
+ tools stay on). The built-in product agent is read-only and told to polish the criteria it is given,
157
+ not to study the code — that is the dev's job. Unedited built-ins in an existing workspace are moved to
158
+ the current ones on start; anything you changed is left alone.
159
+
160
+ A workstream's page is a kanban (To do, Doing, Needs you, Done — drag cards between them) next to its
161
+ group chat. Posting there reaches the whole team, or only the bots you @mention; bots whose stage has
162
+ not started read it with their first card. Click a bot to watch its transcript live, or a card for its
163
+ handoff, criteria and open questions. `+ Card` and `+ Bot` add work and teammates by hand; Archive
164
+ stops the bots and puts the workstream away.
165
+
166
+ Each workstream also writes `.botfarm/workstreams/<id>.yml` in the workspace (excluded from git
167
+ locally): its branch, worktree, story, stage statuses, and the opencode session id of every bot — plus
168
+ the ones they replaced. On start botfarm re-adopts any bot its own state has forgotten, and recreates a
169
+ workstream it has no record of, so a lost `~/.botfarm` or a new machine gets the team back.
170
+
171
+ The previous dashboard is still at `/classic` for everything else (adopting sessions, rooms, the
172
+ global board).
173
+
174
+ ## The board is a registry, not a listing
175
+
176
+ opencode accumulates every session you have ever opened. Listing them all turns the dashboard into an
177
+ archive browser — 250 cards, none of them what you are working on. botfarm shows only sessions it
178
+ manages: ones it started, plus ones you explicitly add with **Add existing…**. The rest sit behind a
179
+ one-line banner offering them. `Remove` puts a session back in that pool; it never deletes anything.
180
+
181
+ The registry is also where botfarm keeps the metadata opencode has no place for: group, label, mesh
182
+ policy, lineage, disabled tools. It lives in `~/.botfarm/registry.json`.
183
+
184
+ ## Projects
185
+
186
+ A project is the thing you actually work on: a checkout, the sessions working in it, the tasks they
187
+ are working through, and the conversation they are having about it. Those used to be four separate
188
+ ideas here — a group, a worktree, a board and a room — which meant four things to create before
189
+ anything could happen. They are one object now.
190
+
191
+ Opening a project gives you all of it on one screen: who is working and what they are doing, that
192
+ project's kanban board, and that project's chat with a composer, side by side. Creating a project
193
+ creates its chat; moving a session into a project moves it into that chat; the project's board is
194
+ its tasks. The global board is still there as a view across every project, rather than as the
195
+ primary one.
196
+
197
+ A pipeline run creates its project, so starting one is the only step.
198
+
199
+ ## The board
200
+
201
+ Every session is a card: identity, worktree, two sparklines (tokens/min and tools/min over the last
202
+ hour), the closing sentences of the last message so you can see what you would be continuing, and the
203
+ controls you actually reach for. Click one and you get a centred modal: the full scrollable
204
+ transcript with a composer on the left, stats, cost, tools and mesh settings on the right.
205
+
206
+ - **Continue without entering** — type into the box on the card and press enter. The prompt is queued
207
+ on that session; you never leave the board.
208
+ - **Abort** — one session, or all running sessions from the header.
209
+ - **Inspect** — slides open the usage breakdown, tool timeline with durations, changed files, peers
210
+ and transcript.
211
+ - Cards sort by attention: running, then waiting on a permission, then errored, then idle.
212
+
213
+ ### Transport
214
+
215
+ The dashboard is pushed to over a websocket (`/api/socket`), falling back to server-sent events if
216
+ the socket will not connect. Neither polls. Actions stay on plain HTTP: they are one-shot, they want
217
+ status codes and retries, and multiplexing them over the socket would mean reinventing request ids
218
+ and error handling that `fetch` already has.
219
+
220
+ botfarm's own traffic to opencode is event-driven too. Session status comes from the event stream, and
221
+ the periodic checks are reconciliation rather than the mechanism — they run when the stream goes
222
+ quiet or a session looks stale. A board with nothing happening makes **zero** requests per second;
223
+ with the stream down the polling loop comes back automatically.
224
+
225
+ Request bodies are negotiated rather than pinned. opencode's prompt endpoint moved from
226
+ `{parts: [...]}` to `{prompt: {text}}`, so botfarm tries the known shapes, keeps the index of the one
227
+ that worked, and reports clearly if none is accepted.
228
+
229
+ Metrics come from each session's message list, not from event payloads. opencode's HTTP surface is
230
+ experimental and event names have already changed once; assistant messages always carry their own
231
+ usage and tool calls are always parts with stable ids. The event stream only says *something changed
232
+ in session X* — the message diff does the counting. The client also sniffs the v1/v2 path dialect at
233
+ connect time, so a rename of `/session` to `/api/session` doesn't break it.
234
+
235
+ ## Worktrees and multi-repo projects
236
+
237
+ A session is a pod; a pod can own a git worktree so parallel agents never fight over one checkout.
238
+
239
+ Paths are expanded the way a shell would: `~/workspace/app`, `$HOME/app` and relative paths all work.
240
+ Before anything is created, the dialogs probe the path and tell you what is there — a missing
241
+ directory and a directory that is not a repository are different problems and say so.
242
+
243
+ **Projects that contain other repositories** — a parent repo with the services cloned into a
244
+ gitignored folder — get one worktree per repository, laid out exactly like the original tree: a
245
+ worktree of the parent, and a worktree of each selected service at the same relative path inside it.
246
+ Every repo ends up on its own branch and the agent sees the directory structure it expects. The
247
+ dialog lists the nested repositories it found (git will not mention them, since the parent ignores
248
+ them) and you tick the ones in scope; leaving them all unticked gives you the parent alone.
249
+
250
+ Changed-files and diffs aggregate across all of them, each file tagged with the repo it belongs to,
251
+ and removing a session removes every worktree it created without touching your original checkouts.
252
+
253
+ ```
254
+ botfarm new ~/code/app --branch fix/auth-redirect --task "Fix the redirect loop after SSO login"
255
+ ```
256
+
257
+ That creates the worktree (under `~/worktrees/<repo>/<branch>` unless you set `BOT_FARM_WORKTREE_ROOT`),
258
+ opens a session located there, and sends the first instruction. Each card shows the branch and a live
259
+ `+142 −18 · 7 files` from `git status`. `botfarm rm <id> --worktree` removes both.
260
+
261
+ ## The mesh
262
+
263
+ Sessions are isolated by default. **Nothing is shared until you turn it on, per session.**
264
+
265
+ Each session has an identity: a stable handle like `@prying-heron` and an avatar that *is* the
266
+ creature in the handle, on a colour drawn from the adjective. **The adjective carries the job** —
267
+ testers draw from a suspicious vocabulary (prying, nervous, squinting, needling), builders from a
268
+ making one (stacking, moulding, polishing, welding), product from a shaping one (framing, scoping,
269
+ sketching), reviewers from a weighing one (tallying, auditing, squaring). `@prying-heron` reads as a
270
+ tester before you have looked at anything else on the card, and in a four-bot room the handle is
271
+ doing the work a job title would. A persona can supply its own `adjectives:` list. The handle isn't decoration — it's the
272
+ addressing scheme. A model copies a three-syllable handle reliably and a 26-character ulid
273
+ unreliably, and you can hold six handles in your head at once.
274
+
275
+ Avatars are two axes a person can name out loud ("the orange badger"), which is what makes forty
276
+ cards scannable — sixty-four creatures against sixteen backgrounds, so a thousand sessions come and
277
+ go before two of them wear the same face. They are generated locally, instantly, and
278
+ deterministically — no model, no download, no network.
279
+
280
+ In the inspector, each session gets two switches:
281
+
282
+ | Setting | Effect |
283
+ | --- | --- |
284
+ | `Off` (default) | Invisible to other sessions and unreachable by them. |
285
+ | `Receive only` | Appears in other sessions' rosters and can be messaged; cannot initiate. |
286
+ | `Send and receive` | Full participant. |
287
+ | `Can open sessions` | May create new sessions for unrelated topics. Off by default. |
288
+
289
+ ### How a session gets the tools
290
+
291
+ botfarm exposes an MCP server at `/mcp/<token>`, with **a distinct token per session**. That token is
292
+ the whole authentication story: a tool call arrives already bound to one caller, so an agent cannot
293
+ claim to be a peer. When you enable the mesh for a session, botfarm registers the server with opencode
294
+ at runtime and also writes it into `<worktree>/.opencode/opencode.json`:
295
+
296
+ ```json
297
+ {
298
+ "mcp": {
299
+ "botfarm": { "type": "remote", "url": "http://127.0.0.1:4777/mcp/<token>", "enabled": true }
300
+ }
301
+ }
302
+ ```
303
+
304
+ Runtime registration lets an already-open session pick the tools up immediately; the project config
305
+ is the reliable fallback (restart that session). `.opencode/` is added to `.git/info/exclude` so
306
+ flipping a toggle never dirties the branch the agent is about to commit from.
307
+
308
+ ### Tools an agent sees
309
+
310
+ `botfarm_whoami`, `botfarm_roster`, `botfarm_send`, `botfarm_ask`, `botfarm_reply`, `botfarm_inbox`, `botfarm_spawn`,
311
+ `botfarm_notify`. Tools you have switched off are not listed at all — a session with messaging off does
312
+ not see a `send` tool and then get refused, it simply has no such tool.
313
+
314
+ `spawn` is the "I found a defect, this doesn't belong in this conversation" case:
315
+
316
+ ```
317
+ botfarm_spawn(title: "flaky date test",
318
+ task: "tests/date.spec.ts fails on the first of the month. Fix it.",
319
+ branch: "fix/flaky-date")
320
+ ```
321
+
322
+ A new pod appears on the board with its own worktree, marked *opened by @velvet-shrew*. The child
323
+ starts empty — it cannot see the parent's conversation — so the tool refuses a spawn without a brief
324
+ that stands on its own.
325
+
326
+ ### What stops it going wrong
327
+
328
+ Agent-to-agent messaging fails in four specific ways, and each has a guard:
329
+
330
+ - **Smuggled instructions.** A peer message arrives as a *synthetic* message, framed with its
331
+ provenance and an explicit caution that it is untrusted input from another agent, not from the
332
+ operator. Without that framing, one compromised session drives all the others.
333
+ - **Ping-pong.** Two agents will happily trade "thanks, and one more thing" forever on your money.
334
+ After six consecutive exchanges with no operator input, botfarm pauses the channel and says so on the
335
+ board. Typing into either session resets the counter; you can resume the channel explicitly.
336
+ - **Fork bombs.** Delegation is capped at depth 2, three children per session, eight agent-created
337
+ sessions per hour across the mesh, and a spawned session cannot itself spawn.
338
+ - **Volume.** 24 messages per ordered pair per hour.
339
+
340
+ Everything that crosses between sessions is logged to the activity feed and to both mailboxes, so the
341
+ mesh is never doing something you cannot see afterwards.
342
+
343
+ ## Group chats
344
+
345
+ Two sessions coordinating once should use `send`. Work that genuinely spans several — a migration, a
346
+ contract between two services — gets a room.
347
+
348
+ Rooms live on the board as a strip above the sessions. Open one and you get the conversation, the
349
+ member list, and a composer: **the operator is a full member**, so you can drop one line into
350
+ `#auth-refactor` and every agent in it sees it, without visiting three sessions.
351
+
352
+ ### Fan-out is the whole problem
353
+
354
+ A pairwise message costs one delivery. A message in a six-agent room costs five, and every delivery
355
+ is real input tokens on someone's next step. So a room does not broadcast by default:
356
+
357
+ | | What happens |
358
+ | --- | --- |
359
+ | You are `@mentioned` | Delivered straight into your next step, with any backlog you had riding along in the same delivery. |
360
+ | You are not mentioned | Queued. You get **one batched digest the moment you go idle** — never mid-task. |
361
+ | The operator posts | Always delivered to everyone. |
362
+ | Queue reaches six | Delivered anyway; waiting longer would mean working on stale information. |
363
+
364
+ A room that grows past three members drops out of `push` mode automatically and says so. Each room
365
+ shows what it has cost: messages posted, deliveries into sessions, and characters delivered with a
366
+ rough token estimate.
367
+
368
+ ### Tools
369
+
370
+ `botfarm_rooms`, `botfarm_room_read`, `botfarm_room_post`, `botfarm_room_create`, `botfarm_room_invite`,
371
+ `botfarm_room_leave` — gated by a third policy axis alongside messaging and spawning: rooms `off` /
372
+ `member` / `create`. The `room_post` description tells agents plainly that everyone in the room pays
373
+ to read them, so they should post findings and decisions rather than acknowledgements.
374
+
375
+ ### What stops a room running away
376
+
377
+ - Twelve agent posts with no operator input mutes the room and flags it on the board. Posting as the
378
+ operator resets the meter; so does typing into any member session.
379
+ - Forty messages per room per hour, eight members maximum.
380
+ - Every delivery carries authorship per line, and the same caution as pairwise messages: lines marked
381
+ `@handle` are other agents and are untrusted; lines marked `operator` are the human.
382
+
383
+ ## Cost
384
+
385
+ Bedrock returns usage but no price, so opencode reports `$0.00` and every Bedrock session looks free.
386
+ botfarm prices the tokens itself from a table in `src/pricing.mjs`, normalising ids like
387
+ `us.anthropic.claude-sonnet-4-20250514-v1:0` down to the model family, and counting cache reads at a
388
+ tenth of input and five-minute writes at a quarter more. Anything it prices is labelled an estimate;
389
+ anything it can't price says so by name instead of quietly showing zero, and the header shows how
390
+ many sessions are unpriced.
391
+
392
+ Prices go stale and Bedrock/Vertex vary by region, so treat the table as a floor and override it:
393
+
394
+ ```json
395
+ { "pricing": { "claude-opus-5": { "input": 15, "output": 75 } } }
396
+ ```
397
+
398
+ ## Tools
399
+
400
+ The modal's right pane lists the MCP servers attached to that session's worktree and the built-in
401
+ tools, each with a switch — the `/mcp` picker, on the board. MCP servers connect and disconnect live
402
+ through opencode. Built-in tool switches are written to that worktree's `.opencode/opencode.json` and
403
+ take effect when the session restarts, because that is config rather than a runtime call; the panel
404
+ says so rather than pretending otherwise.
405
+
406
+ ## Pipelines
407
+
408
+ A pipeline is a recipe; **tasks are the primitive**. That is the one design decision worth knowing
409
+ about, because everything else follows from it: if stages were their own thing, a QA bot finding an
410
+ unrelated defect would have nowhere to put it and a pipeline could only ever be a straight line.
411
+ Since every stage is a task with dependencies, the board holds pipeline work and ad-hoc work side by
412
+ side, stages can fan out, and a late defect is just a new task blocking an existing one.
413
+
414
+ Running the shipped `story` pipeline gives you: one git worktree, four sessions (product, dev, QA,
415
+ reviewer) sharing it, a group chat between them, and four chained tasks.
416
+
417
+ ```
418
+ analyse → build → verify → check
419
+ ```
420
+
421
+ Only the first is queued; the rest are blocked until a handoff lands.
422
+
423
+ ### Handoffs
424
+
425
+ A stage ends with `botfarm_task_complete(task_id, summary, acceptance_criteria, artifacts,
426
+ open_questions)`. Structured on purpose — prose alone gives the next bot nothing to template against,
427
+ and "what did you actually change" is the first question every downstream stage asks. Listing an open
428
+ question parks the task in **review** instead of `done`, so it reaches you before it reaches the next
429
+ bot.
430
+
431
+ ### Which handoffs a stage receives
432
+
433
+ Each stage declares `receives`, and its prompt is a mustache template over those handoffs. Dropping
434
+ QA's write-up from the reviewer's prompt is a one-line YAML edit:
435
+
436
+ ```yaml
437
+ - id: verify
438
+ persona: qa
439
+ receives: [analyse, build]
440
+ prompt: |
441
+ {{#handoffs.analyse}}
442
+ Acceptance criteria:
443
+ {{#acceptance_criteria}} - {{.}}
444
+ {{/acceptance_criteria}}
445
+ {{/handoffs.analyse}}
446
+ {{#handoffs.build}}
447
+ What was built, per {{persona}}: {{summary}}
448
+ {{#artifacts}} changed: {{.}}
449
+ {{/artifacts}}
450
+ {{/handoffs.build}}
451
+ ```
452
+
453
+ A stage that receives a nonexistent stage, or whose prompt uses handoffs it never gets, is reported
454
+ on load rather than silently rendering empty.
455
+
456
+ ### Tasks are yours too
457
+
458
+ `New task` on any board does exactly what a bot's `botfarm_task_create` does: title, brief, project,
459
+ assignee. Work you raise and work a bot raises land in the same column, and clicking any card opens
460
+ the task next to the assignee's live conversation — what was asked on the left, what is happening on
461
+ the right, with a composer, rather than one being a click away from the other.
462
+
463
+ ### Tasks are enqueued, never interrupting
464
+
465
+ A queued task is handed to its assignee **when that session next goes idle** — the same rule as room
466
+ digests. Two exceptions, because waiting for an idle event is not always right: the first stage of a
467
+ fresh pipeline goes out immediately (its bot is only "busy" because it was just briefed), and a task
468
+ nobody has picked up for 45 seconds is delivered anyway, since opencode queues prompts durably and a
469
+ missed idle event should not strand a run. An agent mid-thought is never derailed by new work. `botfarm_task_create` lets a bot raise work
470
+ for someone else (by handle, or by persona name within its own run) and optionally block an existing
471
+ task on it, which is how a defect found at QA reaches the dev bot and holds up the reviewer.
472
+
473
+ ### Config is YAML, state is a database
474
+
475
+ Definitions live in `~/.botfarm/personas/*.yaml` and `~/.botfarm/pipelines/*.yaml` — config that people
476
+ edit and share in git should not live inside a binary. Runtime state (runs, tasks, handoffs) goes
477
+ into `~/.botfarm/botfarm.db` via `node:sqlite`, falling back to a JSON file on older runtimes. The split is
478
+ by kind, so there is never a question of which copy is authoritative. A finished run exports back to
479
+ pipeline YAML for sharing.
480
+
481
+ Four personas and one pipeline are written on first run and never overwritten. The YAML parser is a
482
+ deliberate subset — maps, lists, scalars, and `|` block scalars for prompts — and throws with a line
483
+ number on anything else rather than misreading it.
484
+
485
+ ## Colour
486
+
487
+ Status is carried by amber, violet and blue — never red against green, the one pair a red-green
488
+ colour blind operator cannot separate. Red appears only for failure, where it never has to be told
489
+ apart from success. Diffs use `+`/`-` prefixes with blue and amber rather than green and red, toggles
490
+ say "on"/"off" beside the switch, and every status is spelled out in words next to its colour.
491
+
492
+ ## Who owns a task
493
+
494
+ Ownership has three shapes, and conflating them is what makes agent boards brittle:
495
+
496
+ | | |
497
+ | --- | --- |
498
+ | **session** | one named session does this |
499
+ | **role** | whoever is playing that part picks it up — a dev task does not die because one dev session was closed |
500
+ | **human** | you do it, or you answer it; it is never dispatched |
501
+
502
+ A role task becomes a session's task the moment it is picked up, so two bots playing the same part
503
+ cannot both take it.
504
+
505
+ ## Needs you
506
+
507
+ `botfarm_ask_human(question, options, wait_seconds)` puts a question at the top of your board with the
508
+ asker's face on it and the choices it offered as buttons. Answering delivers the answer straight back
509
+ into that session, marked as coming from the human running it rather than from a peer. By default
510
+ the bot does not block: it asks, carries on with what it can, and the answer arrives as a message.
511
+
512
+ Pipelines can have stages that are yours — `human: true` on a stage, like the sign-off that ships at
513
+ the end of the `story` pipeline. They land in Needs you and the run waits.
514
+
515
+ ## Editing pipelines and personas
516
+
517
+ The pipeline dialog has an editor: add, remove and reorder phases, change who does each one, tick
518
+ which earlier handoffs it receives, and edit its prompt. Personas have their own form — model, tools
519
+ it may use, mesh permissions, handle vocabulary, and the brief it gets before any task.
520
+
521
+ Both write back to the same YAML file you could have edited by hand, and a **YAML tab** sits next to
522
+ the form for anything the form does not cover, so the form never becomes a ceiling. Problems (a stage
523
+ receiving a phase that does not exist, a persona that is not defined) are reported on save rather
524
+ than swallowed.
525
+
526
+ ## The task board
527
+
528
+ The kanban board holds every task: pipeline stages and ad-hoc work together. Columns are backlog,
529
+ blocked, queued, active, review, done, cancelled. Clicking a card shows the brief, the handoff it
530
+ received, the files changed in its worktree with clickable diffs, and controls to move it between
531
+ columns. Moving something to queued offers it to the assignee at their next idle moment.
532
+
533
+ ## CLI
534
+
535
+ ```
536
+ botfarm up [--port 4777] [--server URL] [--repo PATH]... dashboard (default)
537
+ botfarm ls list sessions
538
+ botfarm new <repo> [task] --branch NAME [--agent A] new session in a worktree
539
+ botfarm send <id> <text...> queue a prompt
540
+ botfarm stop [id...] interrupt (all busy if omitted)
541
+ botfarm rm <id> [--worktree] [--force] delete session and its worktree
542
+ botfarm attach <id> open the session in the opencode TUI
543
+ ```
544
+
545
+ Config lives in `~/.botfarm/config.json`:
546
+
547
+ ```json
548
+ { "server": "http://127.0.0.1:4096", "port": 4777, "repos": ["~/code/app"] }
549
+ ```
550
+
551
+ Rolling metrics are snapshotted to `~/.botfarm/metrics.json` every 30s, so restarting botfarm keeps the
552
+ hour of history.
553
+
554
+ ## Tests
555
+
556
+ ```
557
+ node test/smoke.mjs # discovery, metrics, abort, continue, SSE, persistence
558
+ node test/mesh.mjs # MCP handshake, policy gating, messaging, loop limits, delegation
559
+ node test/rooms.mjs # rooms: membership, mention vs digest delivery, operator posts, mute
560
+ node test/board.mjs # registry, adoption, groups, cost estimation, tool toggles
561
+ node test/pipelines.mjs # YAML, templates, runs, handoffs, dispatch, defects flowing backwards
562
+ node test/repos.mjs # path expansion, nested repositories, multi-repo worktrees
563
+ node test/transport.mjs # websocket handshake, pushes, and idle traffic to opencode
564
+ node test/projects.mjs # a run starts its bots; projects own sessions, board and chat
565
+ node test/roles.mjs # role-flavoured handles, role and human ownership, editing definitions
566
+ node test/flow.mjs # late briefing, held mentions, handoff watchdog, shared-worktree callers, live transcripts
567
+ node test/workspaces.mjs # workspace files, editing definitions, one workstream per run, archive, folder browser
568
+ node test/handoff.mjs # open questions travel with the handoff, holding stages, continue with answers
569
+ node test/shot.mjs # renders the dashboard in headless chrome (needs CHROME_PATH)
570
+ ```
571
+
572
+ Both suites run against `test/mock-opencode.mjs`, a fake server that streams plausible sessions, so
573
+ you can develop without burning tokens — `node test/mock-opencode.mjs` then `botfarm up` gives you a
574
+ populated board.
575
+
576
+ ## Known limits
577
+
578
+ - **Identity is per location.** The MCP token is registered against a directory, so two sessions
579
+ sharing one directory share a token; botfarm resolves the caller to whichever of them is running.
580
+ Pods with their own worktrees — the intended setup — are cleanly separated.
581
+ - **Tested against a mock**, not yet against a live opencode server. Field names in the usage object
582
+ are checked in several shapes before giving up, but the first real run may need a tweak.
583
+ - `botfarm attach` assumes `opencode --session <id>` is the right incantation for your version.
584
+ - Cost is only as good as what the provider reports back in the message.
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@dirwin517/bot-farm",
3
+ "version": "0.1.0",
4
+ "description": "kanban style process manager and dashboard for opencode agent sessions",
5
+ "type": "module",
6
+ "bin": { "bot-farm": "src/cli.mjs" },
7
+ "engines": { "node": ">=20" },
8
+ "scripts": {
9
+ "start": "node src/cli.mjs up"
10
+ },
11
+ "dependencies": {},
12
+ "license": "MIT"
13
+ }