@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.
- package/.idea/bot-farm.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +21 -0
- package/README.md +584 -0
- package/package.json +13 -0
- package/src/api.mjs +567 -0
- package/src/cli.mjs +241 -0
- package/src/craft.mjs +172 -0
- package/src/db.mjs +93 -0
- package/src/farmhands.mjs +111 -0
- package/src/git.mjs +578 -0
- package/src/identity.mjs +199 -0
- package/src/legacy-defaults.mjs +44 -0
- package/src/mcp.mjs +681 -0
- package/src/mesh.mjs +222 -0
- package/src/packets.mjs +125 -0
- package/src/patches.mjs +141 -0
- package/src/pipelines.mjs +2145 -0
- package/src/pricing.mjs +91 -0
- package/src/projects.mjs +148 -0
- package/src/public/classic.html +1810 -0
- package/src/public/index.html +3284 -0
- package/src/questions.mjs +69 -0
- package/src/quota.mjs +76 -0
- package/src/registry.mjs +88 -0
- package/src/replay.mjs +151 -0
- package/src/rooms.mjs +320 -0
- package/src/server.mjs +765 -0
- package/src/store.mjs +546 -0
- package/src/supervisor.mjs +1803 -0
- package/src/tasks.mjs +191 -0
- package/src/template.mjs +86 -0
- package/src/tools.mjs +241 -0
- package/src/workspaces.mjs +605 -0
- package/src/ws.mjs +130 -0
- package/src/yaml.mjs +266 -0
- package/test/board.mjs +108 -0
- package/test/craft.mjs +281 -0
- package/test/extend.mjs +145 -0
- package/test/flow.mjs +165 -0
- package/test/handoff.mjs +228 -0
- package/test/linked.mjs +83 -0
- package/test/loop.mjs +168 -0
- package/test/mock-opencode.mjs +217 -0
- package/test/parallel.mjs +135 -0
- package/test/patches.mjs +93 -0
- package/test/repos.mjs +118 -0
- package/test/roles.mjs +157 -0
- package/test/shot.mjs +65 -0
- package/test/smoke.mjs +70 -0
- package/test/transport.mjs +131 -0
- package/test/workspaces.mjs +228 -0
- 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>
|
package/.idea/vcs.xml
ADDED
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
|
+
}
|