@nbardy/oompa 0.6.0 → 0.7.1
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/README.md +43 -16
- package/agentnet/src/agentnet/agent.clj +144 -7
- package/agentnet/src/agentnet/cli.clj +313 -60
- package/agentnet/src/agentnet/harness.clj +217 -0
- package/agentnet/src/agentnet/orchestrator.clj +3 -1
- package/agentnet/src/agentnet/runs.clj +190 -0
- package/agentnet/src/agentnet/schema.clj +4 -4
- package/agentnet/src/agentnet/tasks.clj +48 -0
- package/agentnet/src/agentnet/worker.clj +875 -339
- package/bin/test-models +1 -1
- package/config/prompts/_agent_scope_rules.md +7 -0
- package/config/prompts/_task_header.md +16 -48
- package/config/prompts/cto.md +2 -0
- package/config/prompts/engineer.md +2 -0
- package/config/prompts/executor.md +2 -0
- package/config/prompts/planner.md +3 -1
- package/config/prompts/reviewer.md +2 -0
- package/config/prompts/worker.md +7 -4
- package/oompa.example.json +17 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ This repo has a fleshed out version of the idea. The oompa loompas are organized
|
|
|
39
39
|
|
|
40
40
|
- **Different worker types** — small models for fast execution, big models for planning
|
|
41
41
|
- **Separate review model** — use a smart model to check work before merging
|
|
42
|
-
- **Mixed harnesses** — combine Claude and
|
|
42
|
+
- **Mixed harnesses** — combine Claude, Codex, and Opencode workers in one swarm
|
|
43
43
|
- **Self-directed tasks** — workers create and claim tasks from shared folders
|
|
44
44
|
|
|
45
45
|
### Architecture
|
|
@@ -84,20 +84,22 @@ This repo has a fleshed out version of the idea. The oompa loompas are organized
|
|
|
84
84
|
{
|
|
85
85
|
"workers": [
|
|
86
86
|
{"model": "claude:opus", "prompt": ["config/prompts/planner.md"], "iterations": 5, "count": 1},
|
|
87
|
-
{"model": "codex:gpt-5.3-codex:medium", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count":
|
|
87
|
+
{"model": "codex:gpt-5.3-codex:medium", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 2, "can_plan": false},
|
|
88
|
+
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 1, "can_plan": false}
|
|
88
89
|
]
|
|
89
90
|
}
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
This spawns:
|
|
93
94
|
- **1 planner** (opus) — reads spec, explores codebase, creates/refines tasks
|
|
94
|
-
- **
|
|
95
|
+
- **2 codex executors** (gpt-5.3-codex, medium reasoning) — claims and executes tasks fast
|
|
96
|
+
- **1 opencode executor** (opencode/kimi-k2.5-free) — same task loop via `opencode run`
|
|
95
97
|
|
|
96
98
|
#### Worker fields
|
|
97
99
|
|
|
98
100
|
| Field | Required | Description |
|
|
99
101
|
|-------|----------|-------------|
|
|
100
|
-
| `model` | yes | `harness:model` or `harness:model:reasoning` (e.g. `codex:gpt-5.3-codex:medium`, `claude:opus`) |
|
|
102
|
+
| `model` | yes | `harness:model` or `harness:model:reasoning` (e.g. `codex:gpt-5.3-codex:medium`, `claude:opus`, `opencode:opencode/kimi-k2.5-free`) |
|
|
101
103
|
| `prompt` | no | String or array of paths — concatenated into one prompt |
|
|
102
104
|
| `iterations` | no | Max iterations per worker (default: 10) |
|
|
103
105
|
| `count` | no | Number of workers with this config (default: 1) |
|
|
@@ -111,7 +113,7 @@ This spawns:
|
|
|
111
113
|
{
|
|
112
114
|
"workers": [
|
|
113
115
|
{"model": "claude:opus-4.5", "prompt": ["prompts/base.md", "prompts/architect.md"], "count": 1},
|
|
114
|
-
{"model": "
|
|
116
|
+
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["prompts/base.md", "prompts/frontend.md"], "count": 2},
|
|
115
117
|
{"model": "codex:codex-5.2-mini", "prompt": ["prompts/base.md", "prompts/backend.md"], "count": 2}
|
|
116
118
|
]
|
|
117
119
|
}
|
|
@@ -119,6 +121,23 @@ This spawns:
|
|
|
119
121
|
|
|
120
122
|
Every worker automatically gets task management instructions injected above your prompts. Your prompts just say *what* the worker should do — the framework handles *how* tasks work.
|
|
121
123
|
|
|
124
|
+
#### Prompt includes
|
|
125
|
+
|
|
126
|
+
Prompts support `#oompa_directive:include_file "path/to/file.md"` lines.
|
|
127
|
+
|
|
128
|
+
Use it to share common instructions across roles without copying content.
|
|
129
|
+
Paths are resolved relative to the prompt file containing the directive.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
|
|
133
|
+
```md
|
|
134
|
+
#oompa_directive:include_file "config/prompts/_agent_scope_rules.md"
|
|
135
|
+
|
|
136
|
+
You are an executor. Focus on minimal changes and complete tasks.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The included file is inlined during prompt load, with a short header noting the injected source.
|
|
140
|
+
|
|
122
141
|
### Task System
|
|
123
142
|
|
|
124
143
|
Workers self-organize via the filesystem. Tasks live at the project root and are shared across all worktrees:
|
|
@@ -202,21 +221,28 @@ oompa help # Show all commands
|
|
|
202
221
|
|
|
203
222
|
`./swarm.bb ...` works the same when running from a source checkout.
|
|
204
223
|
|
|
224
|
+
## Opencode Harness
|
|
225
|
+
|
|
226
|
+
`opencode` workers use one-shot `opencode run --format json` calls with the same worker prompt tagging:
|
|
227
|
+
|
|
228
|
+
- First prompt line still starts with `[oompa:<swarmId>:<workerId>]`
|
|
229
|
+
- `-m/--model` is passed when a worker model is configured
|
|
230
|
+
- First iteration starts without `--session`; the worker captures `sessionID` from that exact run output
|
|
231
|
+
- On resumed iterations, workers pass `-s/--session <captured-id> --continue`
|
|
232
|
+
- Oompa does not call `opencode session list` to guess a "latest" session
|
|
233
|
+
- Worker completion markers (`COMPLETE_AND_READY_FOR_MERGE`, `__DONE__`) are evaluated from extracted text events, preserving existing done/merge behavior
|
|
234
|
+
- Optional attach mode: set `OOMPA_OPENCODE_ATTACH` (or `OPENCODE_ATTACH`) to add `--attach <url>`
|
|
235
|
+
|
|
205
236
|
## Worker Conversation Persistence
|
|
206
237
|
|
|
207
|
-
|
|
208
|
-
to a per-worker session file for external UIs (for example worker panes in
|
|
209
|
-
`claude-web-view`).
|
|
238
|
+
Workers rely on each CLI's native session persistence (no custom mirror writer):
|
|
210
239
|
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
- Codex workers use `codex-persist` writes; Claude workers use native `--session-id`
|
|
240
|
+
- Codex: native rollouts under `~/.codex/sessions/YYYY/MM/DD/*.jsonl`
|
|
241
|
+
- Claude: native project sessions under `~/.claude/projects/*/*.jsonl`
|
|
242
|
+
- Opencode: native session store managed by `opencode`
|
|
215
243
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
2. `codex-persist` on `PATH`
|
|
219
|
-
3. `node ~/git/codex-persist/dist/cli.js`
|
|
244
|
+
Oompa still tags the first prompt line with `[oompa:<swarmId>:<workerId>]`
|
|
245
|
+
so downstream UIs can identify and group worker conversations.
|
|
220
246
|
|
|
221
247
|
## Requirements
|
|
222
248
|
|
|
@@ -226,6 +252,7 @@ Resolution order for the CLI command:
|
|
|
226
252
|
- One of:
|
|
227
253
|
- [Claude CLI](https://github.com/anthropics/claude-cli)
|
|
228
254
|
- [Codex CLI](https://github.com/openai/codex)
|
|
255
|
+
- [Opencode CLI](https://github.com/sst/opencode)
|
|
229
256
|
|
|
230
257
|
## License
|
|
231
258
|
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
Supported Backends:
|
|
14
14
|
:codex - OpenAI Codex CLI (codex exec)
|
|
15
|
-
:claude - Anthropic Claude CLI (claude -p)
|
|
15
|
+
:claude - Anthropic Claude CLI (claude -p)
|
|
16
|
+
:opencode - opencode CLI (opencode run)"
|
|
16
17
|
(:require [agentnet.schema :as schema]
|
|
17
18
|
[babashka.process :as process]
|
|
18
19
|
[clojure.java.io :as io]
|
|
@@ -71,6 +72,28 @@
|
|
|
71
72
|
model (into ["--model" model])
|
|
72
73
|
true (conj "--dangerously-skip-permissions")))
|
|
73
74
|
|
|
75
|
+
(defn- opencode-attach-url
|
|
76
|
+
"Optional opencode server URL for run --attach mode."
|
|
77
|
+
[]
|
|
78
|
+
(let [url (or (System/getenv "OOMPA_OPENCODE_ATTACH")
|
|
79
|
+
(System/getenv "OPENCODE_ATTACH"))]
|
|
80
|
+
(when (and url (not (str/blank? url)))
|
|
81
|
+
url)))
|
|
82
|
+
|
|
83
|
+
(defmethod build-command :opencode
|
|
84
|
+
[_ {:keys [model]} prompt cwd]
|
|
85
|
+
(let [attach (opencode-attach-url)]
|
|
86
|
+
(cond-> ["opencode" "run"]
|
|
87
|
+
model (into ["-m" model])
|
|
88
|
+
attach (into ["--attach" attach])
|
|
89
|
+
true (conj prompt))))
|
|
90
|
+
|
|
91
|
+
(defmethod build-command :gemini
|
|
92
|
+
[_ {:keys [model]} prompt cwd]
|
|
93
|
+
(cond-> ["gemini" "--yolo"]
|
|
94
|
+
model (into ["-m" model])
|
|
95
|
+
true (into ["-p" prompt])))
|
|
96
|
+
|
|
74
97
|
(defmethod build-command :default
|
|
75
98
|
[agent-type _ _ _]
|
|
76
99
|
(throw (ex-info (str "Unknown agent type: " agent-type)
|
|
@@ -108,7 +131,102 @@
|
|
|
108
131
|
:stdout (truncate (:out result) 10000)
|
|
109
132
|
:stderr (truncate (:err result) 5000)
|
|
110
133
|
:duration-ms (- (now-ms) start)
|
|
111
|
-
|
|
134
|
+
:timed-out? (boolean (:timed-out result))}))
|
|
135
|
+
|
|
136
|
+
;; =============================================================================
|
|
137
|
+
;; Prompt Loading Helpers
|
|
138
|
+
;; =============================================================================
|
|
139
|
+
|
|
140
|
+
(defn- file-canonical-path
|
|
141
|
+
"Resolve a path for cache keys and cycle detection."
|
|
142
|
+
[path]
|
|
143
|
+
(try
|
|
144
|
+
(.getCanonicalPath (io/file path))
|
|
145
|
+
(catch Exception _
|
|
146
|
+
path)))
|
|
147
|
+
|
|
148
|
+
(def ^:private prompt-file-cache
|
|
149
|
+
"Cache for prompt include expansion."
|
|
150
|
+
(atom {}))
|
|
151
|
+
|
|
152
|
+
(def ^:private include-directive-pattern
|
|
153
|
+
#"(?m)^\s*#oompa_directive:include_file\s+\"([^\"]+)\"\s*$")
|
|
154
|
+
|
|
155
|
+
(defn- read-file-cached
|
|
156
|
+
"Read a prompt file once and cache by canonical path."
|
|
157
|
+
[path]
|
|
158
|
+
(when path
|
|
159
|
+
(if-let [cached (get @prompt-file-cache path)]
|
|
160
|
+
cached
|
|
161
|
+
(let [f (io/file path)]
|
|
162
|
+
(when (.exists f)
|
|
163
|
+
(let [content (slurp f)]
|
|
164
|
+
(swap! prompt-file-cache assoc path content)
|
|
165
|
+
content))))))
|
|
166
|
+
|
|
167
|
+
(defn- resolve-include-path
|
|
168
|
+
"Resolve an include path relative to the file that declares it."
|
|
169
|
+
[source-path include-path]
|
|
170
|
+
(let [source-file (io/file source-path)
|
|
171
|
+
base-dir (.getParentFile source-file)]
|
|
172
|
+
(if (or (str/starts-with? include-path "/")
|
|
173
|
+
(and (> (count include-path) 1)
|
|
174
|
+
(= (nth include-path 1) \:)) ; Windows drive letter
|
|
175
|
+
(str/starts-with? include-path "~"))
|
|
176
|
+
include-path
|
|
177
|
+
(if base-dir
|
|
178
|
+
(str (io/file base-dir include-path))
|
|
179
|
+
include-path))))
|
|
180
|
+
|
|
181
|
+
(defn- expand-includes
|
|
182
|
+
"Expand #oompa_directive:include_file directives recursively.
|
|
183
|
+
|
|
184
|
+
Directive syntax:
|
|
185
|
+
#oompa_directive:include_file \"relative/or/absolute/path.md\"
|
|
186
|
+
|
|
187
|
+
Includes are resolved relative to the prompt file containing the directive.
|
|
188
|
+
Cycles are guarded by a simple visited-set."
|
|
189
|
+
([raw source-path]
|
|
190
|
+
(expand-includes raw source-path #{}))
|
|
191
|
+
([raw source-path visited]
|
|
192
|
+
(let [source-canonical (file-canonical-path source-path)
|
|
193
|
+
lines (str/split-lines (or raw ""))
|
|
194
|
+
visited' (conj visited source-canonical)]
|
|
195
|
+
(str/join
|
|
196
|
+
"\n"
|
|
197
|
+
(mapcat
|
|
198
|
+
(fn [line]
|
|
199
|
+
(if-let [match (re-matches include-directive-pattern line)]
|
|
200
|
+
(let [include-target (second match)
|
|
201
|
+
include-path (resolve-include-path source-canonical include-target)
|
|
202
|
+
include-canonical (file-canonical-path include-path)
|
|
203
|
+
included (and (not (str/blank? include-path))
|
|
204
|
+
(read-file-cached include-canonical))]
|
|
205
|
+
(cond
|
|
206
|
+
(str/blank? include-target)
|
|
207
|
+
["[oompa] Empty include target in prompt directive"]
|
|
208
|
+
|
|
209
|
+
(contains? visited' include-canonical)
|
|
210
|
+
[(format "[oompa] Skipping already-included file: \"%s\"" include-target)]
|
|
211
|
+
|
|
212
|
+
(not included)
|
|
213
|
+
[(format "[oompa] Could not include \"%s\"" include-target)]
|
|
214
|
+
|
|
215
|
+
:else
|
|
216
|
+
(cons (format "We have included the content of file: \"%s\" below"
|
|
217
|
+
include-target)
|
|
218
|
+
(str/split-lines
|
|
219
|
+
(expand-includes included include-canonical visited')))))
|
|
220
|
+
[line]))
|
|
221
|
+
lines)))))
|
|
222
|
+
|
|
223
|
+
(defn- load-prompt-file
|
|
224
|
+
"Load a prompt file and expand include directives."
|
|
225
|
+
[path]
|
|
226
|
+
(when path
|
|
227
|
+
(when-let [f (io/file path)]
|
|
228
|
+
(when (.exists f)
|
|
229
|
+
(expand-includes (slurp f) (file-canonical-path path))))))
|
|
112
230
|
|
|
113
231
|
;; =============================================================================
|
|
114
232
|
;; Output Parsing
|
|
@@ -135,6 +253,17 @@
|
|
|
135
253
|
[output]
|
|
136
254
|
(boolean (re-find #"COMPLETE_AND_READY_FOR_MERGE" (or output ""))))
|
|
137
255
|
|
|
256
|
+
(defn parse-claim-signal
|
|
257
|
+
"Extract task IDs from CLAIM(...) signal in output.
|
|
258
|
+
Returns vector of task ID strings, or nil if no CLAIM signal found.
|
|
259
|
+
Format: CLAIM(task-001, task-003, task-005)"
|
|
260
|
+
[output]
|
|
261
|
+
(when-let [match (re-find #"CLAIM\(([^)]+)\)" (or output ""))]
|
|
262
|
+
(->> (str/split (second match) #",")
|
|
263
|
+
(map str/trim)
|
|
264
|
+
(remove str/blank?)
|
|
265
|
+
vec)))
|
|
266
|
+
|
|
138
267
|
(defn- extract-comments
|
|
139
268
|
"Extract bullet-point comments from output"
|
|
140
269
|
[output]
|
|
@@ -211,7 +340,7 @@
|
|
|
211
340
|
(let [filename (str "config/prompts/" (name role) ".md")
|
|
212
341
|
f (io/file filename)]
|
|
213
342
|
(when (.exists f)
|
|
214
|
-
(
|
|
343
|
+
(load-prompt-file filename))))
|
|
215
344
|
|
|
216
345
|
(defn load-custom-prompt
|
|
217
346
|
"Load a custom prompt file. Returns content or nil."
|
|
@@ -219,10 +348,11 @@
|
|
|
219
348
|
(when path
|
|
220
349
|
(let [f (io/file path)]
|
|
221
350
|
(when (.exists f)
|
|
222
|
-
(
|
|
351
|
+
(load-prompt-file path)))))
|
|
223
352
|
|
|
224
|
-
(defn
|
|
225
|
-
"Replace {tokens} in template with values from context map
|
|
353
|
+
(defn tokenize
|
|
354
|
+
"Replace {tokens} in template with values from context map.
|
|
355
|
+
Keys can be keywords or strings; values are stringified."
|
|
226
356
|
[template tokens]
|
|
227
357
|
(reduce (fn [acc [k v]]
|
|
228
358
|
(str/replace acc
|
|
@@ -269,12 +399,19 @@
|
|
|
269
399
|
(let [cmd (case agent-type
|
|
270
400
|
:codex ["codex" "--version"]
|
|
271
401
|
:claude ["claude" "--version"]
|
|
402
|
+
:opencode ["opencode" "--version"]
|
|
403
|
+
:gemini ["gemini" "--version"]
|
|
272
404
|
["echo" "unknown"])]
|
|
273
405
|
(try
|
|
274
406
|
(let [{:keys [exit]} (process/sh cmd {:out :string :err :string})]
|
|
275
407
|
(zero? exit))
|
|
276
408
|
(catch Exception _
|
|
277
|
-
|
|
409
|
+
;; Some CLIs (like gemini) may error on --version due to config issues
|
|
410
|
+
;; but still exist on PATH. Fall back to `which`.
|
|
411
|
+
(try
|
|
412
|
+
(let [{:keys [exit]} (process/sh ["which" (first cmd)] {:out :string :err :string})]
|
|
413
|
+
(zero? exit))
|
|
414
|
+
(catch Exception _ false))))))
|
|
278
415
|
|
|
279
416
|
(defn select-backend
|
|
280
417
|
"Select first available backend from preference list"
|