@nbardy/oompa 0.7.0 → 0.7.2
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 +32 -6
- package/agentnet/src/agentnet/agent.clj +157 -14
- package/agentnet/src/agentnet/cli.clj +742 -148
- package/agentnet/src/agentnet/harness.clj +240 -0
- package/agentnet/src/agentnet/orchestrator.clj +2 -0
- package/agentnet/src/agentnet/runs.clj +78 -50
- package/agentnet/src/agentnet/schema.clj +9 -2
- package/agentnet/src/agentnet/tasks.clj +47 -0
- package/agentnet/src/agentnet/worker.clj +679 -395
- package/bin/test-models +1 -1
- package/config/prompts/_agent_scope_rules.md +7 -0
- package/config/prompts/_task_header.md +22 -47
- package/config/prompts/cto.md +2 -0
- package/config/prompts/engineer.md +2 -0
- package/config/prompts/executor.md +2 -0
- package/config/prompts/magicgenie-executor.md +15 -0
- package/config/prompts/magicgenie-planner.md +26 -0
- package/config/prompts/magicgenie-reviewer.md +44 -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 +10 -2
- 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
|
"workers": [
|
|
86
86
|
{"model": "claude:opus", "prompt": ["config/prompts/planner.md"], "iterations": 5, "count": 1},
|
|
87
87
|
{"model": "codex:gpt-5.3-codex:medium", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 2, "can_plan": false},
|
|
88
|
-
{"model": "opencode:
|
|
88
|
+
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["config/prompts/executor.md"], "iterations": 10, "count": 1, "can_plan": false}
|
|
89
89
|
]
|
|
90
90
|
}
|
|
91
91
|
```
|
|
@@ -93,17 +93,18 @@ This repo has a fleshed out version of the idea. The oompa loompas are organized
|
|
|
93
93
|
This spawns:
|
|
94
94
|
- **1 planner** (opus) — reads spec, explores codebase, creates/refines tasks
|
|
95
95
|
- **2 codex executors** (gpt-5.3-codex, medium reasoning) — claims and executes tasks fast
|
|
96
|
-
- **1 opencode executor** (
|
|
96
|
+
- **1 opencode executor** (opencode/kimi-k2.5-free) — same task loop via `opencode run`
|
|
97
97
|
|
|
98
98
|
#### Worker fields
|
|
99
99
|
|
|
100
100
|
| Field | Required | Description |
|
|
101
101
|
|-------|----------|-------------|
|
|
102
|
-
| `model` | yes | `harness:model` or `harness:model:reasoning` (e.g. `codex:gpt-5.3-codex:medium`, `claude:opus`, `opencode:
|
|
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`) |
|
|
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
106
|
| `can_plan` | no | If `false`, worker waits for tasks before starting (default: `true`) |
|
|
107
|
+
| `max_wait_for_tasks` | no | Max seconds a `can_plan: false` worker waits for queue work (default: `600`) |
|
|
107
108
|
|
|
108
109
|
#### Composable prompts
|
|
109
110
|
|
|
@@ -113,7 +114,7 @@ This spawns:
|
|
|
113
114
|
{
|
|
114
115
|
"workers": [
|
|
115
116
|
{"model": "claude:opus-4.5", "prompt": ["prompts/base.md", "prompts/architect.md"], "count": 1},
|
|
116
|
-
{"model": "opencode:
|
|
117
|
+
{"model": "opencode:opencode/kimi-k2.5-free", "prompt": ["prompts/base.md", "prompts/frontend.md"], "count": 2},
|
|
117
118
|
{"model": "codex:codex-5.2-mini", "prompt": ["prompts/base.md", "prompts/backend.md"], "count": 2}
|
|
118
119
|
]
|
|
119
120
|
}
|
|
@@ -121,6 +122,23 @@ This spawns:
|
|
|
121
122
|
|
|
122
123
|
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.
|
|
123
124
|
|
|
125
|
+
#### Prompt includes
|
|
126
|
+
|
|
127
|
+
Prompts support `#oompa_directive:include_file "path/to/file.md"` lines.
|
|
128
|
+
|
|
129
|
+
Use it to share common instructions across roles without copying content.
|
|
130
|
+
Paths are resolved relative to the prompt file containing the directive.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
```md
|
|
135
|
+
#oompa_directive:include_file "config/prompts/_agent_scope_rules.md"
|
|
136
|
+
|
|
137
|
+
You are an executor. Focus on minimal changes and complete tasks.
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The included file is inlined during prompt load, with a short header noting the injected source.
|
|
141
|
+
|
|
124
142
|
### Task System
|
|
125
143
|
|
|
126
144
|
Workers self-organize via the filesystem. Tasks live at the project root and are shared across all worktrees:
|
|
@@ -174,7 +192,10 @@ cd oompa
|
|
|
174
192
|
echo "Build a simple todo API with CRUD endpoints" > spec.md
|
|
175
193
|
|
|
176
194
|
# Run the swarm
|
|
177
|
-
./swarm.bb
|
|
195
|
+
./swarm.bb run
|
|
196
|
+
|
|
197
|
+
# Run detached with startup validation (fails fast if startup fails)
|
|
198
|
+
./swarm.bb run --detach --config oompa.json
|
|
178
199
|
```
|
|
179
200
|
|
|
180
201
|
## Install (npm)
|
|
@@ -195,7 +216,12 @@ oompa swarm
|
|
|
195
216
|
## Commands
|
|
196
217
|
|
|
197
218
|
```bash
|
|
198
|
-
oompa
|
|
219
|
+
oompa run [file] # Run from config (defaults: oompa.json, oompa/oompa.json)
|
|
220
|
+
oompa run --detach --config oompa.json
|
|
221
|
+
oompa swarm [file] # Direct swarm command (foreground)
|
|
222
|
+
oompa list # List 20 most recent swarms
|
|
223
|
+
oompa list --all # Full swarm history
|
|
224
|
+
oompa view [swarm-id] # Per-worker runtime status (default: latest swarm)
|
|
199
225
|
oompa tasks # Show task status
|
|
200
226
|
oompa check # Check available backends
|
|
201
227
|
oompa cleanup # Remove worktrees
|
|
@@ -88,10 +88,39 @@
|
|
|
88
88
|
attach (into ["--attach" attach])
|
|
89
89
|
true (conj prompt))))
|
|
90
90
|
|
|
91
|
+
(defn- gemini-cmd
|
|
92
|
+
[agent-type model prompt]
|
|
93
|
+
(cond-> [(name agent-type) "--yolo"]
|
|
94
|
+
model (into ["-m" model])
|
|
95
|
+
true (into ["-p" prompt])))
|
|
96
|
+
|
|
97
|
+
(defn- gemini-alias?
|
|
98
|
+
[agent-type]
|
|
99
|
+
(and (keyword? agent-type)
|
|
100
|
+
(re-matches #"^gemini\\d+$" (name agent-type))))
|
|
101
|
+
|
|
102
|
+
(defmethod build-command :gemini
|
|
103
|
+
[_ {:keys [model]} prompt cwd]
|
|
104
|
+
(gemini-cmd :gemini model prompt))
|
|
105
|
+
|
|
106
|
+
(defmethod build-command :gemini1
|
|
107
|
+
[agent-type {:keys [model]} prompt cwd]
|
|
108
|
+
(gemini-cmd agent-type model prompt))
|
|
109
|
+
|
|
110
|
+
(defmethod build-command :gemini2
|
|
111
|
+
[agent-type {:keys [model]} prompt cwd]
|
|
112
|
+
(gemini-cmd agent-type model prompt))
|
|
113
|
+
|
|
114
|
+
(defmethod build-command :gemini3
|
|
115
|
+
[agent-type {:keys [model]} prompt cwd]
|
|
116
|
+
(gemini-cmd agent-type model prompt))
|
|
117
|
+
|
|
91
118
|
(defmethod build-command :default
|
|
92
|
-
[agent-type
|
|
93
|
-
(
|
|
94
|
-
|
|
119
|
+
[agent-type {:keys [model]} prompt _]
|
|
120
|
+
(if (gemini-alias? agent-type)
|
|
121
|
+
(gemini-cmd agent-type model prompt)
|
|
122
|
+
(throw (ex-info (str "Unknown agent type: " agent-type)
|
|
123
|
+
{:agent-type agent-type}))))
|
|
95
124
|
|
|
96
125
|
;; =============================================================================
|
|
97
126
|
;; Process Execution
|
|
@@ -125,7 +154,102 @@
|
|
|
125
154
|
:stdout (truncate (:out result) 10000)
|
|
126
155
|
:stderr (truncate (:err result) 5000)
|
|
127
156
|
:duration-ms (- (now-ms) start)
|
|
128
|
-
|
|
157
|
+
:timed-out? (boolean (:timed-out result))}))
|
|
158
|
+
|
|
159
|
+
;; =============================================================================
|
|
160
|
+
;; Prompt Loading Helpers
|
|
161
|
+
;; =============================================================================
|
|
162
|
+
|
|
163
|
+
(defn- file-canonical-path
|
|
164
|
+
"Resolve a path for cache keys and cycle detection."
|
|
165
|
+
[path]
|
|
166
|
+
(try
|
|
167
|
+
(.getCanonicalPath (io/file path))
|
|
168
|
+
(catch Exception _
|
|
169
|
+
path)))
|
|
170
|
+
|
|
171
|
+
(def ^:private prompt-file-cache
|
|
172
|
+
"Cache for prompt include expansion."
|
|
173
|
+
(atom {}))
|
|
174
|
+
|
|
175
|
+
(def ^:private include-directive-pattern
|
|
176
|
+
#"(?m)^\s*#oompa_directive:include_file\s+\"([^\"]+)\"\s*$")
|
|
177
|
+
|
|
178
|
+
(defn- read-file-cached
|
|
179
|
+
"Read a prompt file once and cache by canonical path."
|
|
180
|
+
[path]
|
|
181
|
+
(when path
|
|
182
|
+
(if-let [cached (get @prompt-file-cache path)]
|
|
183
|
+
cached
|
|
184
|
+
(let [f (io/file path)]
|
|
185
|
+
(when (.exists f)
|
|
186
|
+
(let [content (slurp f)]
|
|
187
|
+
(swap! prompt-file-cache assoc path content)
|
|
188
|
+
content))))))
|
|
189
|
+
|
|
190
|
+
(defn- resolve-include-path
|
|
191
|
+
"Resolve an include path relative to the file that declares it."
|
|
192
|
+
[source-path include-path]
|
|
193
|
+
(let [source-file (io/file source-path)
|
|
194
|
+
base-dir (.getParentFile source-file)]
|
|
195
|
+
(if (or (str/starts-with? include-path "/")
|
|
196
|
+
(and (> (count include-path) 1)
|
|
197
|
+
(= (nth include-path 1) \:)) ; Windows drive letter
|
|
198
|
+
(str/starts-with? include-path "~"))
|
|
199
|
+
include-path
|
|
200
|
+
(if base-dir
|
|
201
|
+
(str (io/file base-dir include-path))
|
|
202
|
+
include-path))))
|
|
203
|
+
|
|
204
|
+
(defn- expand-includes
|
|
205
|
+
"Expand #oompa_directive:include_file directives recursively.
|
|
206
|
+
|
|
207
|
+
Directive syntax:
|
|
208
|
+
#oompa_directive:include_file \"relative/or/absolute/path.md\"
|
|
209
|
+
|
|
210
|
+
Includes are resolved relative to the prompt file containing the directive.
|
|
211
|
+
Cycles are guarded by a simple visited-set."
|
|
212
|
+
([raw source-path]
|
|
213
|
+
(expand-includes raw source-path #{}))
|
|
214
|
+
([raw source-path visited]
|
|
215
|
+
(let [source-canonical (file-canonical-path source-path)
|
|
216
|
+
lines (str/split-lines (or raw ""))
|
|
217
|
+
visited' (conj visited source-canonical)]
|
|
218
|
+
(str/join
|
|
219
|
+
"\n"
|
|
220
|
+
(mapcat
|
|
221
|
+
(fn [line]
|
|
222
|
+
(if-let [match (re-matches include-directive-pattern line)]
|
|
223
|
+
(let [include-target (second match)
|
|
224
|
+
include-path (resolve-include-path source-canonical include-target)
|
|
225
|
+
include-canonical (file-canonical-path include-path)
|
|
226
|
+
included (and (not (str/blank? include-path))
|
|
227
|
+
(read-file-cached include-canonical))]
|
|
228
|
+
(cond
|
|
229
|
+
(str/blank? include-target)
|
|
230
|
+
["[oompa] Empty include target in prompt directive"]
|
|
231
|
+
|
|
232
|
+
(contains? visited' include-canonical)
|
|
233
|
+
[(format "[oompa] Skipping already-included file: \"%s\"" include-target)]
|
|
234
|
+
|
|
235
|
+
(not included)
|
|
236
|
+
[(format "[oompa] Could not include \"%s\"" include-target)]
|
|
237
|
+
|
|
238
|
+
:else
|
|
239
|
+
(cons (format "We have included the content of file: \"%s\" below"
|
|
240
|
+
include-target)
|
|
241
|
+
(str/split-lines
|
|
242
|
+
(expand-includes included include-canonical visited')))))
|
|
243
|
+
[line]))
|
|
244
|
+
lines)))))
|
|
245
|
+
|
|
246
|
+
(defn- load-prompt-file
|
|
247
|
+
"Load a prompt file and expand include directives."
|
|
248
|
+
[path]
|
|
249
|
+
(when path
|
|
250
|
+
(when-let [f (io/file path)]
|
|
251
|
+
(when (.exists f)
|
|
252
|
+
(expand-includes (slurp f) (file-canonical-path path))))))
|
|
129
253
|
|
|
130
254
|
;; =============================================================================
|
|
131
255
|
;; Output Parsing
|
|
@@ -152,6 +276,17 @@
|
|
|
152
276
|
[output]
|
|
153
277
|
(boolean (re-find #"COMPLETE_AND_READY_FOR_MERGE" (or output ""))))
|
|
154
278
|
|
|
279
|
+
(defn parse-claim-signal
|
|
280
|
+
"Extract task IDs from CLAIM(...) signal in output.
|
|
281
|
+
Returns vector of task ID strings, or nil if no CLAIM signal found.
|
|
282
|
+
Format: CLAIM(task-001, task-003, task-005)"
|
|
283
|
+
[output]
|
|
284
|
+
(when-let [match (re-find #"CLAIM\(([^)]+)\)" (or output ""))]
|
|
285
|
+
(->> (str/split (second match) #",")
|
|
286
|
+
(map str/trim)
|
|
287
|
+
(remove str/blank?)
|
|
288
|
+
vec)))
|
|
289
|
+
|
|
155
290
|
(defn- extract-comments
|
|
156
291
|
"Extract bullet-point comments from output"
|
|
157
292
|
[output]
|
|
@@ -228,7 +363,7 @@
|
|
|
228
363
|
(let [filename (str "config/prompts/" (name role) ".md")
|
|
229
364
|
f (io/file filename)]
|
|
230
365
|
(when (.exists f)
|
|
231
|
-
(
|
|
366
|
+
(load-prompt-file filename))))
|
|
232
367
|
|
|
233
368
|
(defn load-custom-prompt
|
|
234
369
|
"Load a custom prompt file. Returns content or nil."
|
|
@@ -236,10 +371,11 @@
|
|
|
236
371
|
(when path
|
|
237
372
|
(let [f (io/file path)]
|
|
238
373
|
(when (.exists f)
|
|
239
|
-
(
|
|
374
|
+
(load-prompt-file path)))))
|
|
240
375
|
|
|
241
|
-
(defn
|
|
242
|
-
"Replace {tokens} in template with values from context map
|
|
376
|
+
(defn tokenize
|
|
377
|
+
"Replace {tokens} in template with values from context map.
|
|
378
|
+
Keys can be keywords or strings; values are stringified."
|
|
243
379
|
[template tokens]
|
|
244
380
|
(reduce (fn [acc [k v]]
|
|
245
381
|
(str/replace acc
|
|
@@ -283,16 +419,23 @@
|
|
|
283
419
|
(defn check-available
|
|
284
420
|
"Check if agent backend is available"
|
|
285
421
|
[agent-type]
|
|
286
|
-
(let [cmd (
|
|
287
|
-
:codex ["codex" "--version"]
|
|
288
|
-
:claude ["claude" "--version"]
|
|
289
|
-
:opencode ["opencode" "--version"]
|
|
290
|
-
["
|
|
422
|
+
(let [cmd (cond
|
|
423
|
+
(= :codex agent-type) ["codex" "--version"]
|
|
424
|
+
(= :claude agent-type) ["claude" "--version"]
|
|
425
|
+
(= :opencode agent-type) ["opencode" "--version"]
|
|
426
|
+
(= :gemini agent-type) ["gemini" "--version"]
|
|
427
|
+
(gemini-alias? agent-type) [(name agent-type) "--version"]
|
|
428
|
+
:else ["echo" "unknown"])]
|
|
291
429
|
(try
|
|
292
430
|
(let [{:keys [exit]} (process/sh cmd {:out :string :err :string})]
|
|
293
431
|
(zero? exit))
|
|
294
432
|
(catch Exception _
|
|
295
|
-
|
|
433
|
+
;; Some CLIs (like gemini) may error on --version due to config issues
|
|
434
|
+
;; but still exist on PATH. Fall back to `which`.
|
|
435
|
+
(try
|
|
436
|
+
(let [{:keys [exit]} (process/sh ["which" (first cmd)] {:out :string :err :string})]
|
|
437
|
+
(zero? exit))
|
|
438
|
+
(catch Exception _ false))))))
|
|
296
439
|
|
|
297
440
|
(defn select-backend
|
|
298
441
|
"Select first available backend from preference list"
|