@nbardy/oompa 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/README.md +197 -0
- package/agentnet/src/agentnet/agent.clj +293 -0
- package/agentnet/src/agentnet/cli.clj +436 -0
- package/agentnet/src/agentnet/core.clj +211 -0
- package/agentnet/src/agentnet/merge.clj +355 -0
- package/agentnet/src/agentnet/notes.clj +123 -0
- package/agentnet/src/agentnet/orchestrator.clj +367 -0
- package/agentnet/src/agentnet/review.clj +263 -0
- package/agentnet/src/agentnet/schema.clj +141 -0
- package/agentnet/src/agentnet/tasks.clj +198 -0
- package/agentnet/src/agentnet/worker.clj +427 -0
- package/agentnet/src/agentnet/worktree.clj +342 -0
- package/bin/oompa.js +37 -0
- package/config/prompts/cto.md +45 -0
- package/config/prompts/engineer.md +44 -0
- package/config/prompts/executor.md +32 -0
- package/config/prompts/planner.md +35 -0
- package/config/prompts/reviewer.md +49 -0
- package/config/prompts/worker.md +31 -0
- package/oompa.example.json +18 -0
- package/package.json +45 -0
- package/swarm.bb +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# oompa
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
*Getting your ralphs to work together*
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Minimal Idea
|
|
10
|
+
|
|
11
|
+
From the [Oompa Loompas blog post](notes/2025-01-instant_software_blog_draft.md) — the simplest multi-agent swarm:
|
|
12
|
+
|
|
13
|
+
**oompa_loompas.sh** (7 lines):
|
|
14
|
+
```bash
|
|
15
|
+
#!/bin/bash
|
|
16
|
+
for w in $(seq 1 ${WORKERS:-3}); do
|
|
17
|
+
(for i in $(seq 1 ${ITERATIONS:-5}); do
|
|
18
|
+
wt=".w${w}-i${i}"
|
|
19
|
+
git worktree add $wt -b $wt 2>/dev/null
|
|
20
|
+
{ echo "Worktree: $wt"; cat prompts/worker.md; } | claude -p -
|
|
21
|
+
done) &
|
|
22
|
+
done; wait
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**prompts/worker.md** (3 lines):
|
|
26
|
+
```
|
|
27
|
+
Goal: Match spec.md
|
|
28
|
+
Process: Create/claim tasks in tasks/{pending,in_progress,complete}.md
|
|
29
|
+
Method: Isolate changes to your worktree, commit and merge when complete
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That's it. Parallel agents with worktree isolation.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## The Full Version
|
|
37
|
+
|
|
38
|
+
This repo has a fleshed out version of the idea. The oompa loompas are organized by a more sophisticated Clojure harness, enabling advanced features:
|
|
39
|
+
|
|
40
|
+
- **Different worker types** — small models for fast execution, big models for planning
|
|
41
|
+
- **Separate review model** — use a smart model to check work before merging
|
|
42
|
+
- **Mixed harnesses** — combine Claude and Codex workers in one swarm
|
|
43
|
+
- **Self-directed tasks** — workers create and claim tasks from shared folders
|
|
44
|
+
|
|
45
|
+
### Architecture
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
49
|
+
│ SELF-DIRECTED WORKERS │
|
|
50
|
+
│ │
|
|
51
|
+
│ tasks/pending/*.edn ──→ Worker claims ──→ tasks/current/*.edn │
|
|
52
|
+
│ ▲ │ │
|
|
53
|
+
│ │ ▼ │
|
|
54
|
+
│ │ Execute in worktree │
|
|
55
|
+
│ │ │ │
|
|
56
|
+
│ │ ▼ │
|
|
57
|
+
│ │ Commit changes │
|
|
58
|
+
│ │ │ │
|
|
59
|
+
│ │ ▼ │
|
|
60
|
+
│ │ ┌─────────────────────┐ │
|
|
61
|
+
│ │ │ REVIEWER checks │ │
|
|
62
|
+
│ │ │ (review_model) │ │
|
|
63
|
+
│ │ └──────────┬──────────┘ │
|
|
64
|
+
│ │ ┌─────┴─────┐ │
|
|
65
|
+
│ │ ▼ ▼ │
|
|
66
|
+
│ │ Approved Rejected │
|
|
67
|
+
│ │ │ │ │
|
|
68
|
+
│ │ ▼ └──────┐ │
|
|
69
|
+
│ │ Merge to │ │
|
|
70
|
+
│ │ main │ │
|
|
71
|
+
│ │ │ ▼ │
|
|
72
|
+
│ │ │ Fix & retry ──→ Reviewer │
|
|
73
|
+
│ │ │ │
|
|
74
|
+
│ └─── Create tasks ◄──┘ │
|
|
75
|
+
│ │
|
|
76
|
+
│ Exit when: __DONE__ token emitted │
|
|
77
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configuration
|
|
81
|
+
|
|
82
|
+
**oompa.json**:
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"review_model": "codex:codex-5.2",
|
|
86
|
+
"workers": [
|
|
87
|
+
{"model": "claude:opus-4.5", "iterations": 5, "count": 1, "prompt": "config/prompts/planner.md"},
|
|
88
|
+
{"model": "codex:codex-5.2-mini", "iterations": 10, "count": 3, "prompt": "config/prompts/executor.md"}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This spawns:
|
|
94
|
+
- **1 planner** (opus) — creates tasks, doesn't write code
|
|
95
|
+
- **3 executors** (mini) — claims and executes tasks fast
|
|
96
|
+
- **Reviews** done by codex-5.2 before any merge
|
|
97
|
+
|
|
98
|
+
### Task System
|
|
99
|
+
|
|
100
|
+
Workers self-organize via filesystem:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
tasks/
|
|
104
|
+
├── pending/*.edn # unclaimed tasks
|
|
105
|
+
├── current/*.edn # in progress
|
|
106
|
+
└── complete/*.edn # done
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Workers can:
|
|
110
|
+
- **Claim tasks**: `mv pending/task.edn current/`
|
|
111
|
+
- **Complete tasks**: `mv current/task.edn complete/`
|
|
112
|
+
- **Create tasks**: write new `.edn` to `pending/`
|
|
113
|
+
|
|
114
|
+
### Prompts
|
|
115
|
+
|
|
116
|
+
Three built-in worker types:
|
|
117
|
+
|
|
118
|
+
| Prompt | Role | Creates Tasks? | Executes Tasks? |
|
|
119
|
+
|--------|------|----------------|-----------------|
|
|
120
|
+
| `worker.md` | Hybrid | ✓ | ✓ |
|
|
121
|
+
| `planner.md` | Planner | ✓ | ✗ |
|
|
122
|
+
| `executor.md` | Executor | ✗ | ✓ |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Quick Start
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Clone
|
|
130
|
+
git clone https://github.com/nbardy/oompa.git
|
|
131
|
+
cd oompa
|
|
132
|
+
|
|
133
|
+
# Check backends
|
|
134
|
+
./swarm.bb check
|
|
135
|
+
|
|
136
|
+
# Create a spec
|
|
137
|
+
echo "Build a simple todo API with CRUD endpoints" > spec.md
|
|
138
|
+
|
|
139
|
+
# Run the swarm
|
|
140
|
+
./swarm.bb swarm
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Install (npm)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Run without installing globally
|
|
147
|
+
npx @nbardy/oompa check
|
|
148
|
+
npx @nbardy/oompa swarm
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Or install globally
|
|
153
|
+
npm install -g @nbardy/oompa
|
|
154
|
+
oompa check
|
|
155
|
+
oompa swarm
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Commands
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
oompa swarm [file] # Run from oompa.json (default)
|
|
162
|
+
oompa tasks # Show task status
|
|
163
|
+
oompa check # Check available backends
|
|
164
|
+
oompa cleanup # Remove worktrees
|
|
165
|
+
oompa help # Show all commands
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`./swarm.bb ...` works the same when running from a source checkout.
|
|
169
|
+
|
|
170
|
+
## Worker Conversation Persistence
|
|
171
|
+
|
|
172
|
+
If `codex-persist` is available, each worker writes its prompt/response messages
|
|
173
|
+
to a per-worker session file for external UIs (for example worker panes in
|
|
174
|
+
`claude-web-view`).
|
|
175
|
+
|
|
176
|
+
- Session ID: random lowercase UUID per iteration (one file per iteration)
|
|
177
|
+
- First user message tag format: `[oompa:<swarmId>:<workerId>]`
|
|
178
|
+
- CWD passed to `codex-persist` is the worker worktree absolute path
|
|
179
|
+
- Codex workers use `codex-persist` writes; Claude workers use native `--session-id`
|
|
180
|
+
|
|
181
|
+
Resolution order for the CLI command:
|
|
182
|
+
1. `CODEX_PERSIST_BIN` (if set)
|
|
183
|
+
2. `codex-persist` on `PATH`
|
|
184
|
+
3. `node ~/git/codex-persist/dist/cli.js`
|
|
185
|
+
|
|
186
|
+
## Requirements
|
|
187
|
+
|
|
188
|
+
- Node.js 18+ (only for npm wrapper / npx usage)
|
|
189
|
+
- [Babashka](https://github.com/babashka/babashka) (bb)
|
|
190
|
+
- Git 2.5+ (for worktrees)
|
|
191
|
+
- One of:
|
|
192
|
+
- [Claude CLI](https://github.com/anthropics/claude-cli)
|
|
193
|
+
- [Codex CLI](https://github.com/openai/codex)
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
(ns agentnet.agent
|
|
2
|
+
"Unified agent execution abstraction.
|
|
3
|
+
|
|
4
|
+
Supports multiple agent backends (Codex, Claude) with consistent interface.
|
|
5
|
+
Each agent type has different CLI syntax but produces same output format.
|
|
6
|
+
|
|
7
|
+
Design:
|
|
8
|
+
- Agents are pure functions: Prompt -> AgentResult
|
|
9
|
+
- Side effects (file changes) happen in worktree
|
|
10
|
+
- Timeouts and retries handled at this layer
|
|
11
|
+
- Output parsing extracts structured feedback
|
|
12
|
+
|
|
13
|
+
Supported Backends:
|
|
14
|
+
:codex - OpenAI Codex CLI (codex exec)
|
|
15
|
+
:claude - Anthropic Claude CLI (claude -p)"
|
|
16
|
+
(:require [agentnet.schema :as schema]
|
|
17
|
+
[babashka.process :as process]
|
|
18
|
+
[clojure.java.io :as io]
|
|
19
|
+
[clojure.string :as str]))
|
|
20
|
+
|
|
21
|
+
;; =============================================================================
|
|
22
|
+
;; Function Specs
|
|
23
|
+
;; =============================================================================
|
|
24
|
+
|
|
25
|
+
;; invoke : AgentConfig, AgentRole, Prompt, Worktree -> AgentResult
|
|
26
|
+
;; Execute agent with prompt in worktree context
|
|
27
|
+
|
|
28
|
+
;; build-prompt : Task, Context, AgentRole -> Prompt
|
|
29
|
+
;; Construct prompt from task, context, and role template
|
|
30
|
+
|
|
31
|
+
;; parse-output : String, AgentRole -> ParsedOutput
|
|
32
|
+
;; Extract structured data from agent output
|
|
33
|
+
|
|
34
|
+
;; =============================================================================
|
|
35
|
+
;; Types (documentation)
|
|
36
|
+
;; =============================================================================
|
|
37
|
+
|
|
38
|
+
;; AgentResult:
|
|
39
|
+
;; {:exit int
|
|
40
|
+
;; :stdout string
|
|
41
|
+
;; :stderr string
|
|
42
|
+
;; :duration-ms int
|
|
43
|
+
;; :timed-out? boolean}
|
|
44
|
+
|
|
45
|
+
;; ParsedOutput:
|
|
46
|
+
;; {:verdict :approved|:needs-changes|:rejected
|
|
47
|
+
;; :comments [string]
|
|
48
|
+
;; :files-changed [string]
|
|
49
|
+
;; :error string}
|
|
50
|
+
|
|
51
|
+
;; =============================================================================
|
|
52
|
+
;; Agent Backend Implementations
|
|
53
|
+
;; =============================================================================
|
|
54
|
+
|
|
55
|
+
(defmulti build-command
|
|
56
|
+
"Build CLI command for agent type"
|
|
57
|
+
(fn [agent-type _config _prompt _cwd] agent-type))
|
|
58
|
+
|
|
59
|
+
(defmethod build-command :codex
|
|
60
|
+
[_ {:keys [model sandbox timeout-seconds]} prompt cwd]
|
|
61
|
+
(cond-> ["codex" "exec" "--full-auto" "--skip-git-repo-check"]
|
|
62
|
+
model (into ["--model" model])
|
|
63
|
+
cwd (into ["-C" cwd])
|
|
64
|
+
sandbox (into ["--sandbox" (name sandbox)])
|
|
65
|
+
true (conj "--" prompt)))
|
|
66
|
+
|
|
67
|
+
(defmethod build-command :claude
|
|
68
|
+
[_ {:keys [model timeout-seconds]} prompt cwd]
|
|
69
|
+
;; Claude uses stdin for prompt via -p flag
|
|
70
|
+
(cond-> ["claude" "-p"]
|
|
71
|
+
model (into ["--model" model])
|
|
72
|
+
true (conj "--dangerously-skip-permissions")))
|
|
73
|
+
|
|
74
|
+
(defmethod build-command :default
|
|
75
|
+
[agent-type _ _ _]
|
|
76
|
+
(throw (ex-info (str "Unknown agent type: " agent-type)
|
|
77
|
+
{:agent-type agent-type})))
|
|
78
|
+
|
|
79
|
+
;; =============================================================================
|
|
80
|
+
;; Process Execution
|
|
81
|
+
;; =============================================================================
|
|
82
|
+
|
|
83
|
+
(defn- now-ms []
|
|
84
|
+
(System/currentTimeMillis))
|
|
85
|
+
|
|
86
|
+
(defn- truncate [s limit]
|
|
87
|
+
(if (and s (> (count s) limit))
|
|
88
|
+
(str (subs s 0 limit) "...[truncated]")
|
|
89
|
+
s))
|
|
90
|
+
|
|
91
|
+
(defn- run-process
|
|
92
|
+
"Execute command with timeout, return AgentResult"
|
|
93
|
+
[{:keys [cmd cwd stdin timeout-ms]}]
|
|
94
|
+
(let [start (now-ms)
|
|
95
|
+
timeout (or timeout-ms 300000) ; 5 min default
|
|
96
|
+
opts (cond-> {:out :string
|
|
97
|
+
:err :string
|
|
98
|
+
:timeout timeout}
|
|
99
|
+
cwd (assoc :dir cwd)
|
|
100
|
+
stdin (assoc :in stdin))
|
|
101
|
+
result (try
|
|
102
|
+
(process/sh cmd opts)
|
|
103
|
+
(catch java.util.concurrent.TimeoutException _
|
|
104
|
+
{:exit -1 :out "" :err "Timeout exceeded" :timed-out true})
|
|
105
|
+
(catch Exception e
|
|
106
|
+
{:exit -1 :out "" :err (.getMessage e)}))]
|
|
107
|
+
{:exit (:exit result)
|
|
108
|
+
:stdout (truncate (:out result) 10000)
|
|
109
|
+
:stderr (truncate (:err result) 5000)
|
|
110
|
+
:duration-ms (- (now-ms) start)
|
|
111
|
+
:timed-out? (boolean (:timed-out result))}))
|
|
112
|
+
|
|
113
|
+
;; =============================================================================
|
|
114
|
+
;; Output Parsing
|
|
115
|
+
;; =============================================================================
|
|
116
|
+
|
|
117
|
+
(defn- extract-verdict
|
|
118
|
+
"Extract review verdict from agent output"
|
|
119
|
+
[output]
|
|
120
|
+
(cond
|
|
121
|
+
(re-find #"(?i)\bAPPROVED\b" output) :approved
|
|
122
|
+
(re-find #"(?i)\bREJECTED\b" output) :rejected
|
|
123
|
+
(re-find #"(?i)\bNEEDS[_-]?CHANGES\b" output) :needs-changes
|
|
124
|
+
(re-find #"(?i)\bFIX\s*:" output) :needs-changes
|
|
125
|
+
(re-find #"(?i)\bVIOLATION\s*:" output) :needs-changes
|
|
126
|
+
:else nil))
|
|
127
|
+
|
|
128
|
+
(defn done-signal?
|
|
129
|
+
"Check if output contains __DONE__ signal"
|
|
130
|
+
[output]
|
|
131
|
+
(boolean (re-find #"__DONE__" (or output ""))))
|
|
132
|
+
|
|
133
|
+
(defn- extract-comments
|
|
134
|
+
"Extract bullet-point comments from output"
|
|
135
|
+
[output]
|
|
136
|
+
(->> (str/split-lines output)
|
|
137
|
+
(filter #(re-find #"^\s*[-*]\s+" %))
|
|
138
|
+
(map #(str/replace % #"^\s*[-*]\s+" ""))
|
|
139
|
+
(map str/trim)
|
|
140
|
+
(remove str/blank?)
|
|
141
|
+
vec))
|
|
142
|
+
|
|
143
|
+
(defn- extract-files-changed
|
|
144
|
+
"Extract list of files that were modified"
|
|
145
|
+
[output]
|
|
146
|
+
(->> (re-seq #"(?m)^[AMD]\s+(.+)$" output)
|
|
147
|
+
(map second)
|
|
148
|
+
vec))
|
|
149
|
+
|
|
150
|
+
(defn parse-output
|
|
151
|
+
"Parse agent output into structured format"
|
|
152
|
+
[output role]
|
|
153
|
+
(case role
|
|
154
|
+
:reviewer
|
|
155
|
+
{:verdict (or (extract-verdict output) :needs-changes)
|
|
156
|
+
:comments (extract-comments output)}
|
|
157
|
+
|
|
158
|
+
:proposer
|
|
159
|
+
{:files-changed (extract-files-changed output)
|
|
160
|
+
:comments (extract-comments output)}
|
|
161
|
+
|
|
162
|
+
;; default
|
|
163
|
+
{:comments (extract-comments output)}))
|
|
164
|
+
|
|
165
|
+
;; =============================================================================
|
|
166
|
+
;; Main API
|
|
167
|
+
;; =============================================================================
|
|
168
|
+
|
|
169
|
+
(defn invoke
|
|
170
|
+
"Execute agent with prompt in worktree context.
|
|
171
|
+
|
|
172
|
+
Arguments:
|
|
173
|
+
config - AgentConfig with :type, :model, :sandbox, :timeout-seconds
|
|
174
|
+
role - :proposer, :reviewer, or :cto
|
|
175
|
+
prompt - String prompt to send to agent
|
|
176
|
+
worktree - Worktree map with :path
|
|
177
|
+
|
|
178
|
+
Returns AgentResult with :exit, :stdout, :stderr, :duration-ms, :timed-out?"
|
|
179
|
+
[{:keys [type] :as config} role prompt worktree]
|
|
180
|
+
(schema/assert-valid schema/valid-agent-config? config "AgentConfig")
|
|
181
|
+
(let [cwd (:path worktree)
|
|
182
|
+
cmd (build-command type config prompt cwd)
|
|
183
|
+
;; For Claude, prompt goes via stdin
|
|
184
|
+
stdin (when (= type :claude) prompt)
|
|
185
|
+
timeout-ms (* 1000 (or (:timeout-seconds config) 300))]
|
|
186
|
+
(run-process {:cmd cmd
|
|
187
|
+
:cwd cwd
|
|
188
|
+
:stdin stdin
|
|
189
|
+
:timeout-ms timeout-ms})))
|
|
190
|
+
|
|
191
|
+
(defn invoke-and-parse
|
|
192
|
+
"Execute agent and parse output into structured format"
|
|
193
|
+
[config role prompt worktree]
|
|
194
|
+
(let [result (invoke config role prompt worktree)
|
|
195
|
+
parsed (when (zero? (:exit result))
|
|
196
|
+
(parse-output (:stdout result) role))]
|
|
197
|
+
(assoc result :parsed parsed)))
|
|
198
|
+
|
|
199
|
+
;; =============================================================================
|
|
200
|
+
;; Prompt Building
|
|
201
|
+
;; =============================================================================
|
|
202
|
+
|
|
203
|
+
(defn- load-template
|
|
204
|
+
"Load prompt template from config/prompts/"
|
|
205
|
+
[role]
|
|
206
|
+
(let [filename (str "config/prompts/" (name role) ".md")
|
|
207
|
+
f (io/file filename)]
|
|
208
|
+
(when (.exists f)
|
|
209
|
+
(slurp f))))
|
|
210
|
+
|
|
211
|
+
(defn load-custom-prompt
|
|
212
|
+
"Load a custom prompt file. Returns content or nil."
|
|
213
|
+
[path]
|
|
214
|
+
(when path
|
|
215
|
+
(let [f (io/file path)]
|
|
216
|
+
(when (.exists f)
|
|
217
|
+
(slurp f)))))
|
|
218
|
+
|
|
219
|
+
(defn- tokenize
|
|
220
|
+
"Replace {tokens} in template with values from context map"
|
|
221
|
+
[template tokens]
|
|
222
|
+
(reduce (fn [acc [k v]]
|
|
223
|
+
(str/replace acc
|
|
224
|
+
(re-pattern (java.util.regex.Pattern/quote
|
|
225
|
+
(str "{" (name k) "}")))
|
|
226
|
+
(str v)))
|
|
227
|
+
template
|
|
228
|
+
tokens))
|
|
229
|
+
|
|
230
|
+
(defn build-prompt
|
|
231
|
+
"Build prompt from task, context, and role.
|
|
232
|
+
|
|
233
|
+
Arguments:
|
|
234
|
+
task - Task map with :id, :summary, :targets
|
|
235
|
+
context - Context map with :queue_md, :recent_files_md, etc.
|
|
236
|
+
role - :proposer, :reviewer, or :cto
|
|
237
|
+
opts - {:custom-prompt \"path/to/prompt.md\"}
|
|
238
|
+
|
|
239
|
+
Returns prompt string ready for agent"
|
|
240
|
+
([task context role] (build-prompt task context role {}))
|
|
241
|
+
([task context role {:keys [custom-prompt]}]
|
|
242
|
+
(let [template (or (load-custom-prompt custom-prompt)
|
|
243
|
+
(load-template role)
|
|
244
|
+
(load-template :engineer)) ; fallback
|
|
245
|
+
tokens (merge context
|
|
246
|
+
{:task_id (:id task)
|
|
247
|
+
:summary (:summary task)
|
|
248
|
+
:targets (str/join ", " (or (:targets task) ["*"]))
|
|
249
|
+
:mode_hint (if (= role :reviewer) "review" "propose")})]
|
|
250
|
+
(if template
|
|
251
|
+
(tokenize template tokens)
|
|
252
|
+
;; Fallback: simple prompt
|
|
253
|
+
(str "Task: " (:summary task) "\n"
|
|
254
|
+
"Targets: " (:targets task) "\n"
|
|
255
|
+
"Role: " (name role))))))
|
|
256
|
+
|
|
257
|
+
;; =============================================================================
|
|
258
|
+
;; Agent Health Check
|
|
259
|
+
;; =============================================================================
|
|
260
|
+
|
|
261
|
+
(defn check-available
|
|
262
|
+
"Check if agent backend is available"
|
|
263
|
+
[agent-type]
|
|
264
|
+
(let [cmd (case agent-type
|
|
265
|
+
:codex ["codex" "--version"]
|
|
266
|
+
:claude ["claude" "--version"]
|
|
267
|
+
["echo" "unknown"])]
|
|
268
|
+
(try
|
|
269
|
+
(let [{:keys [exit]} (process/sh cmd {:out :string :err :string})]
|
|
270
|
+
(zero? exit))
|
|
271
|
+
(catch Exception _
|
|
272
|
+
false))))
|
|
273
|
+
|
|
274
|
+
(defn select-backend
|
|
275
|
+
"Select first available backend from preference list"
|
|
276
|
+
[preferences]
|
|
277
|
+
(first (filter check-available preferences)))
|
|
278
|
+
|
|
279
|
+
;; =============================================================================
|
|
280
|
+
;; Convenience Wrappers
|
|
281
|
+
;; =============================================================================
|
|
282
|
+
|
|
283
|
+
(defn propose!
|
|
284
|
+
"Run proposer agent on task in worktree"
|
|
285
|
+
[config task context worktree]
|
|
286
|
+
(let [prompt (build-prompt task context :proposer)]
|
|
287
|
+
(invoke-and-parse config :proposer prompt worktree)))
|
|
288
|
+
|
|
289
|
+
(defn review!
|
|
290
|
+
"Run reviewer agent on changes in worktree"
|
|
291
|
+
[config task context worktree]
|
|
292
|
+
(let [prompt (build-prompt task context :reviewer)]
|
|
293
|
+
(invoke-and-parse config :reviewer prompt worktree)))
|