@nbardy/oompa 0.2.0 → 0.3.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 +2 -1
- package/agentnet/src/agentnet/cli.clj +32 -27
- package/agentnet/src/agentnet/worker.clj +43 -14
- package/config/prompts/planner.md +2 -1
- package/oompa.example.json +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ This repo has a fleshed out version of the idea. The oompa loompas are organized
|
|
|
85
85
|
"review_model": "codex:codex-5.2",
|
|
86
86
|
"workers": [
|
|
87
87
|
{"model": "claude:opus-4.5", "prompt": ["config/prompts/planner.md"], "iterations": 5, "count": 1},
|
|
88
|
-
{"model": "codex:codex-5.2-mini", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 3}
|
|
88
|
+
{"model": "codex:codex-5.2-mini", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 3, "can_plan": false}
|
|
89
89
|
]
|
|
90
90
|
}
|
|
91
91
|
```
|
|
@@ -103,6 +103,7 @@ This spawns:
|
|
|
103
103
|
| `prompt` | no | String or array of paths — concatenated into one prompt |
|
|
104
104
|
| `iterations` | no | Max iterations per worker (default: 10) |
|
|
105
105
|
| `count` | no | Number of workers with this config (default: 1) |
|
|
106
|
+
| `can_plan` | no | If `false`, worker waits for tasks before starting (default: `true`) |
|
|
106
107
|
|
|
107
108
|
#### Composable prompts
|
|
108
109
|
|
|
@@ -132,34 +132,38 @@
|
|
|
132
132
|
;; Commands
|
|
133
133
|
;; =============================================================================
|
|
134
134
|
|
|
135
|
+
(declare cmd-swarm)
|
|
136
|
+
|
|
135
137
|
(defn cmd-run
|
|
136
|
-
"Run orchestrator
|
|
138
|
+
"Run orchestrator — uses oompa.json if present, otherwise simple mode"
|
|
137
139
|
[opts args]
|
|
138
|
-
(
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
(
|
|
145
|
-
(
|
|
146
|
-
(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
140
|
+
(if (.exists (io/file "oompa.json"))
|
|
141
|
+
(cmd-swarm opts (or (seq args) ["oompa.json"]))
|
|
142
|
+
(let [swarm-id (make-swarm-id)]
|
|
143
|
+
(if-let [specs (:worker-specs opts)]
|
|
144
|
+
;; Mixed worker specs: --workers claude:5 codex:4
|
|
145
|
+
(let [workers (mapcat
|
|
146
|
+
(fn [spec]
|
|
147
|
+
(let [{:keys [harness count]} spec]
|
|
148
|
+
(map-indexed
|
|
149
|
+
(fn [idx _]
|
|
150
|
+
(worker/create-worker
|
|
151
|
+
{:id (format "%s-%d" (name harness) idx)
|
|
152
|
+
:swarm-id swarm-id
|
|
153
|
+
:harness harness
|
|
154
|
+
:model (:model opts)
|
|
155
|
+
:iterations 1}))
|
|
156
|
+
(range count))))
|
|
157
|
+
specs)]
|
|
158
|
+
(println (format "Running once with mixed workers (swarm %s):" swarm-id))
|
|
159
|
+
(doseq [spec specs]
|
|
160
|
+
(println (format " %dx %s" (:count spec) (name (:harness spec)))))
|
|
161
|
+
(println)
|
|
162
|
+
(worker/run-workers! workers))
|
|
163
|
+
;; Simple mode
|
|
164
|
+
(do
|
|
165
|
+
(println (format "Swarm ID: %s" swarm-id))
|
|
166
|
+
(orchestrator/run-once! (assoc opts :swarm-id swarm-id)))))))
|
|
163
167
|
|
|
164
168
|
(defn cmd-loop
|
|
165
169
|
"Run orchestrator N times"
|
|
@@ -300,7 +304,7 @@
|
|
|
300
304
|
(println "{")
|
|
301
305
|
(println " \"review_model\": \"codex:codex-5.2\",")
|
|
302
306
|
(println " \"workers\": [")
|
|
303
|
-
(println " {\"model\": \"codex:codex-5.2-mini\", \"prompt\": \"prompts/executor.md\", \"iterations\": 10, \"count\": 3},")
|
|
307
|
+
(println " {\"model\": \"codex:codex-5.2-mini\", \"prompt\": \"prompts/executor.md\", \"iterations\": 10, \"count\": 3, \"can_plan\": false},")
|
|
304
308
|
(println " {\"model\": \"claude:opus-4.5\", \"prompt\": [\"prompts/base.md\", \"prompts/planner.md\"], \"count\": 1}")
|
|
305
309
|
(println " ]")
|
|
306
310
|
(println "}")
|
|
@@ -328,6 +332,7 @@
|
|
|
328
332
|
:model model
|
|
329
333
|
:iterations (or (:iterations wc) 10)
|
|
330
334
|
:prompts (:prompt wc)
|
|
335
|
+
:can-plan (:can_plan wc)
|
|
331
336
|
:review-harness (:harness review-model)
|
|
332
337
|
:review-model (:model review-model)})))
|
|
333
338
|
expanded-workers)]
|
|
@@ -105,8 +105,9 @@
|
|
|
105
105
|
|
|
106
106
|
(defn create-worker
|
|
107
107
|
"Create a worker config.
|
|
108
|
-
:prompts is a string or vector of strings — paths to prompt files.
|
|
109
|
-
|
|
108
|
+
:prompts is a string or vector of strings — paths to prompt files.
|
|
109
|
+
:can-plan when false, worker waits for tasks before starting (backpressure)."
|
|
110
|
+
[{:keys [id swarm-id harness model iterations prompts can-plan review-harness review-model]}]
|
|
110
111
|
{:id id
|
|
111
112
|
:swarm-id swarm-id
|
|
112
113
|
:harness (or harness :codex)
|
|
@@ -116,6 +117,7 @@
|
|
|
116
117
|
(vector? prompts) prompts
|
|
117
118
|
(string? prompts) [prompts]
|
|
118
119
|
:else [])
|
|
120
|
+
:can-plan (if (some? can-plan) can-plan true)
|
|
119
121
|
:review-harness review-harness
|
|
120
122
|
:review-model review-model
|
|
121
123
|
:completed 0
|
|
@@ -187,6 +189,7 @@
|
|
|
187
189
|
(process/sh cmd {:dir abs-worktree :in tagged-prompt :out :string :err :string})
|
|
188
190
|
(process/sh cmd {:dir abs-worktree :out :string :err :string}))
|
|
189
191
|
(catch Exception e
|
|
192
|
+
(println (format "[%s] Agent exception: %s" id (.getMessage e)))
|
|
190
193
|
{:exit -1 :out "" :err (.getMessage e)}))]
|
|
191
194
|
|
|
192
195
|
(when (= harness :codex)
|
|
@@ -280,18 +283,18 @@
|
|
|
280
283
|
|
|
281
284
|
(defn- merge-to-main!
|
|
282
285
|
"Merge worktree changes to main branch"
|
|
283
|
-
[wt-path wt-id worker-id]
|
|
286
|
+
[wt-path wt-id worker-id project-root]
|
|
284
287
|
(println (format "[%s] Merging changes to main" worker-id))
|
|
285
288
|
(let [;; Commit in worktree if needed
|
|
286
289
|
_ (process/sh ["git" "add" "-A"] {:dir wt-path})
|
|
287
290
|
_ (process/sh ["git" "commit" "-m" (str "Work from " wt-id) "--allow-empty"]
|
|
288
291
|
{:dir wt-path})
|
|
289
|
-
;; Checkout main and merge
|
|
292
|
+
;; Checkout main and merge (in project root, not worktree)
|
|
290
293
|
checkout-result (process/sh ["git" "checkout" "main"]
|
|
291
|
-
{:out :string :err :string})
|
|
294
|
+
{:dir project-root :out :string :err :string})
|
|
292
295
|
merge-result (when (zero? (:exit checkout-result))
|
|
293
296
|
(process/sh ["git" "merge" wt-id "--no-edit"]
|
|
294
|
-
{:out :string :err :string}))]
|
|
297
|
+
{:dir project-root :out :string :err :string}))]
|
|
295
298
|
(and (zero? (:exit checkout-result))
|
|
296
299
|
(zero? (:exit merge-result)))))
|
|
297
300
|
|
|
@@ -341,15 +344,17 @@
|
|
|
341
344
|
Returns {:status :done|:continue|:error, :task task-or-nil}"
|
|
342
345
|
[worker iteration total-iterations]
|
|
343
346
|
(let [worker-id (:id worker)
|
|
344
|
-
|
|
347
|
+
project-root (System/getProperty "user.dir")
|
|
348
|
+
wt-dir (format ".w%s-i%d" worker-id iteration)
|
|
349
|
+
wt-branch (format "oompa/%s-i%d" worker-id iteration)
|
|
345
350
|
|
|
346
351
|
;; Create worktree
|
|
347
352
|
_ (println (format "[%s] Starting iteration %d/%d" worker-id iteration total-iterations))
|
|
348
|
-
wt-path (str
|
|
353
|
+
wt-path (str project-root "/" wt-dir)]
|
|
349
354
|
|
|
350
355
|
(try
|
|
351
|
-
;; Setup worktree
|
|
352
|
-
(process/sh ["git" "worktree" "add" wt-
|
|
356
|
+
;; Setup worktree (in project root) — dir starts with . but branch name must be valid
|
|
357
|
+
(process/sh ["git" "worktree" "add" wt-dir "-b" wt-branch] {:dir project-root})
|
|
353
358
|
|
|
354
359
|
;; Build context
|
|
355
360
|
(let [context (build-context)
|
|
@@ -367,7 +372,7 @@
|
|
|
367
372
|
;; Agent errored
|
|
368
373
|
(not (zero? exit))
|
|
369
374
|
(do
|
|
370
|
-
(println (format "[%s] Agent error (exit %d)" worker-id exit))
|
|
375
|
+
(println (format "[%s] Agent error (exit %d): %s" worker-id exit (subs (or output "") 0 (min 200 (count (or output ""))))))
|
|
371
376
|
{:status :error :exit exit})
|
|
372
377
|
|
|
373
378
|
;; Success - run review loop before merge
|
|
@@ -375,7 +380,7 @@
|
|
|
375
380
|
(let [{:keys [approved?]} (review-loop! worker wt-path worker-id)]
|
|
376
381
|
(if approved?
|
|
377
382
|
(do
|
|
378
|
-
(merge-to-main! wt-path wt-
|
|
383
|
+
(merge-to-main! wt-path wt-branch worker-id project-root)
|
|
379
384
|
(println (format "[%s] Iteration %d/%d complete" worker-id iteration total-iterations))
|
|
380
385
|
{:status :continue})
|
|
381
386
|
(do
|
|
@@ -383,13 +388,33 @@
|
|
|
383
388
|
{:status :continue})))))
|
|
384
389
|
|
|
385
390
|
(finally
|
|
386
|
-
;; Cleanup worktree
|
|
387
|
-
(process/sh ["git" "worktree" "remove" wt-
|
|
391
|
+
;; Cleanup worktree (in project root)
|
|
392
|
+
(process/sh ["git" "worktree" "remove" wt-dir "--force"] {:dir project-root})))))
|
|
388
393
|
|
|
389
394
|
;; =============================================================================
|
|
390
395
|
;; Worker Loop
|
|
391
396
|
;; =============================================================================
|
|
392
397
|
|
|
398
|
+
(def ^:private max-wait-for-tasks 60)
|
|
399
|
+
(def ^:private wait-poll-interval 5)
|
|
400
|
+
|
|
401
|
+
(defn- wait-for-tasks!
|
|
402
|
+
"Wait up to 60s for pending/current tasks to appear. Used for backpressure
|
|
403
|
+
on workers that can't create their own tasks (can_plan: false)."
|
|
404
|
+
[worker-id]
|
|
405
|
+
(loop [waited 0]
|
|
406
|
+
(cond
|
|
407
|
+
(pos? (tasks/pending-count)) true
|
|
408
|
+
(pos? (tasks/current-count)) true
|
|
409
|
+
(>= waited max-wait-for-tasks)
|
|
410
|
+
(do (println (format "[%s] No tasks after %ds, proceeding anyway" worker-id waited))
|
|
411
|
+
false)
|
|
412
|
+
:else
|
|
413
|
+
(do (when (zero? (mod waited 15))
|
|
414
|
+
(println (format "[%s] Waiting for tasks... (%ds)" worker-id waited)))
|
|
415
|
+
(Thread/sleep (* wait-poll-interval 1000))
|
|
416
|
+
(recur (+ waited wait-poll-interval))))))
|
|
417
|
+
|
|
393
418
|
(defn run-worker!
|
|
394
419
|
"Run worker loop until done or iterations exhausted.
|
|
395
420
|
|
|
@@ -403,6 +428,10 @@
|
|
|
403
428
|
(or (:model worker) "default")
|
|
404
429
|
iterations))
|
|
405
430
|
|
|
431
|
+
;; Backpressure: workers that can't create tasks wait for tasks to exist
|
|
432
|
+
(when-not (:can-plan worker)
|
|
433
|
+
(wait-for-tasks! id))
|
|
434
|
+
|
|
406
435
|
(loop [iter 1
|
|
407
436
|
completed 0]
|
|
408
437
|
(if (> iter iterations)
|
|
@@ -6,5 +6,6 @@ You are a planner. You read the spec, explore the codebase, and create well-scop
|
|
|
6
6
|
- Set :difficulty on each task (:easy, :medium, :hard) so the right worker picks it up.
|
|
7
7
|
- Refine or split tasks that are too large or vague.
|
|
8
8
|
- Check ../tasks/complete/ before creating duplicates.
|
|
9
|
-
-
|
|
9
|
+
- Do NOT claim or complete tasks. Your job is to create them for other workers.
|
|
10
|
+
- Do NOT write application code. Only write task .edn files.
|
|
10
11
|
- Spend time thinking. Good task decomposition is the highest-leverage thing you can do.
|
package/oompa.example.json
CHANGED