@irythill/orq 0.5.0 → 0.6.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 +59 -38
- package/README.pt-BR.md +60 -38
- package/dist/agents.js +80 -6
- package/dist/ci.js +6 -6
- package/dist/cli.js +3 -1
- package/dist/commands/agent.js +146 -20
- package/dist/commands/board.js +90 -20
- package/dist/commands/claim.js +2 -0
- package/dist/commands/completion.js +1 -1
- package/dist/commands/conductor.js +104 -0
- package/dist/commands/gc.js +57 -21
- package/dist/commands/handoff.js +1 -0
- package/dist/commands/quota.js +1 -1
- package/dist/commands/rounds.js +45 -18
- package/dist/commands/wait.js +5 -1
- package/dist/config.js +5 -1
- package/dist/install.js +5 -0
- package/dist/issue.js +47 -0
- package/dist/notice.js +33 -5
- package/dist/pick.js +105 -17
- package/dist/process.js +7 -0
- package/dist/quota.js +85 -19
- package/dist/stats.js +2 -1
- package/package.json +3 -2
- package/skills/issue/SKILL.md +41 -41
- package/templates/conductor.instructions.md +31 -0
- package/templates/workspace.CLAUDE.md +6 -5
package/dist/quota.js
CHANGED
|
@@ -24,6 +24,7 @@ import { writeAtomic } from "./fsutil.js";
|
|
|
24
24
|
import { readSecret } from "./keyring.js";
|
|
25
25
|
import { stateDir } from "./paths.js";
|
|
26
26
|
import { m } from "./i18n.js";
|
|
27
|
+
import { modelKey } from "./pick.js";
|
|
27
28
|
const show = (p) => (typeof p === "number" ? `${Math.floor(p)}%` : p);
|
|
28
29
|
// reset5h/resetweek (epoch seconds) sit between the windows and the note: parseQuota/parseWindows only look at the
|
|
29
30
|
// first three words, so old cached lines (no reset fields) and lines with them both parse the same way.
|
|
@@ -50,9 +51,9 @@ const resetOf = (w) => {
|
|
|
50
51
|
};
|
|
51
52
|
// `acct` identifies the login behind a reading (antigravity: one login per machine, swapped by the user): a cache
|
|
52
53
|
// written under another login must never stand in for this one.
|
|
53
|
-
function remember(name, five, week, acct) {
|
|
54
|
+
function remember(name, five, week, acct, buckets) {
|
|
54
55
|
try {
|
|
55
|
-
writeAtomic(cacheFile(name), JSON.stringify({ five, week, at: Date.now(), ...(acct ? { acct } : {}) }));
|
|
56
|
+
writeAtomic(cacheFile(name), JSON.stringify({ five, week, at: Date.now(), ...(acct ? { acct } : {}), ...(buckets ? { buckets } : {}) }));
|
|
56
57
|
}
|
|
57
58
|
catch { /* best effort */ }
|
|
58
59
|
const r5 = resetOf(five), rw = resetOf(week);
|
|
@@ -195,23 +196,83 @@ export function codexQuota() {
|
|
|
195
196
|
return { name: "codex", ...pcts, ...(span5 ? { span5h: span5 } : {}), ...(span ? { spanWeek: span } : {}), note: (d ? m(`${d}-day window in the week slot; `, `janela de ${d} dias no lugar da semana; `) : "")
|
|
196
197
|
+ m(`from the last session, ${age} min ago`, `da última sessão, há ${age} min`) };
|
|
197
198
|
}
|
|
198
|
-
/**
|
|
199
|
+
/** Per-model buckets, and the agent's own pair: its least used bucket (the agent still has quota while any
|
|
200
|
+
* model does; `orq pick` checks the model it is about to choose against that model's own bucket). */
|
|
199
201
|
export function antigravityFromModels(models, now = Date.now()) {
|
|
200
202
|
const five = { pct: "-" }, week = { pct: "-" };
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
const buckets = {};
|
|
204
|
+
for (const [id, { quotaInfo: q } = {}] of Object.entries(models ?? {})) {
|
|
205
|
+
if (!q?.resetTime)
|
|
203
206
|
continue;
|
|
204
207
|
const at = Date.parse(q.resetTime);
|
|
205
208
|
if (Number.isNaN(at))
|
|
206
209
|
continue;
|
|
207
|
-
const
|
|
208
|
-
const used = Math.round((1 -
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
const left = typeof q.remainingFraction === "number" ? q.remainingFraction : 0;
|
|
211
|
+
const used = Math.min(100, Math.max(0, Math.round((1 - left) * 100)));
|
|
212
|
+
const isWeek = at - now > 6 * 3600_000;
|
|
213
|
+
buckets[id] = { used, resetsAt: q.resetTime, week: isWeek };
|
|
214
|
+
}
|
|
215
|
+
// The agent's pair is its least used bucket overall, in that bucket's window (the other stays "-"): a weekly cap on
|
|
216
|
+
// one model must not read as the whole agent being out.
|
|
217
|
+
const best = Object.values(buckets).sort((x, y) => x.used - y.used || Number(x.week) - Number(y.week))[0];
|
|
218
|
+
if (best)
|
|
219
|
+
Object.assign(best.week ? week : five, { pct: best.used, resetsAt: best.resetsAt });
|
|
220
|
+
return { five, week, buckets };
|
|
221
|
+
}
|
|
222
|
+
/** One quota line per model, named "<agent>/<model>", with its bucket in the 5h or the week slot and "-" in the other. */
|
|
223
|
+
export function modelLines(name, buckets, now = Date.now(), note) {
|
|
224
|
+
const out = [];
|
|
225
|
+
for (const [id, b] of Object.entries(buckets)) {
|
|
226
|
+
const at = Date.parse(b.resetsAt);
|
|
227
|
+
if (Number.isNaN(at) || at <= now)
|
|
228
|
+
continue; // reset since the reading: no figure to stand on
|
|
229
|
+
const reset = Math.floor(at / 1000);
|
|
230
|
+
out.push({ name: `${name}/${id}`, five: b.week ? "-" : b.used, week: b.week ? b.used : "-",
|
|
231
|
+
...(b.week ? { resetWeek: reset } : { reset5h: reset }), ...(note ? { note } : {}) });
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/** Learned exhausted buckets (from a run that hit the limit) outlive API readings until their reset, unless the API
|
|
236
|
+
* returns that model with something left. The API's own bucket for the model stands in when it also says exhausted. */
|
|
237
|
+
export function mergeLearned(api, learned, now = Date.now()) {
|
|
238
|
+
const out = { ...api };
|
|
239
|
+
for (const [id, b] of Object.entries(learned)) {
|
|
240
|
+
if (!b.learned || Date.parse(b.resetsAt) <= now)
|
|
241
|
+
continue;
|
|
242
|
+
const key = modelKey(id, Object.keys(api));
|
|
243
|
+
if (key && api[key].used < 100)
|
|
244
|
+
continue; // the API says it has quota again
|
|
245
|
+
out[id] = b;
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
/** Record that a run found `model` out of quota for `secs` more seconds (best effort; keeps the cached reading). */
|
|
250
|
+
export function learnExhausted(name, model, secs, acct, now = Date.now()) {
|
|
251
|
+
const at = now + secs * 1000;
|
|
252
|
+
const b = { used: 100, resetsAt: new Date(at).toISOString(), week: at - now > 6 * 3600_000, learned: true };
|
|
253
|
+
try {
|
|
254
|
+
let c = { five: { pct: "-" }, week: { pct: "-" }, at: now };
|
|
255
|
+
try {
|
|
256
|
+
c = JSON.parse(readFileSync(cacheFile(name), "utf8"));
|
|
212
257
|
}
|
|
258
|
+
catch { /* none yet */ }
|
|
259
|
+
if (acct !== undefined && c.acct !== undefined && c.acct !== acct)
|
|
260
|
+
c = { five: { pct: "-" }, week: { pct: "-" }, at: now };
|
|
261
|
+
const buckets = { ...(c.buckets ?? {}), [model]: b };
|
|
262
|
+
writeAtomic(cacheFile(name), JSON.stringify({ ...c, ...(acct ? { acct } : {}), buckets }));
|
|
263
|
+
}
|
|
264
|
+
catch { /* best effort */ }
|
|
265
|
+
return b;
|
|
266
|
+
}
|
|
267
|
+
/** The per-model buckets of the last good reading, when it was this login's. */
|
|
268
|
+
function cachedBuckets(name, acct) {
|
|
269
|
+
try {
|
|
270
|
+
const c = JSON.parse(readFileSync(cacheFile(name), "utf8"));
|
|
271
|
+
return acct !== undefined && c.acct !== acct ? {} : c.buckets ?? {};
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return {};
|
|
213
275
|
}
|
|
214
|
-
return { five, week };
|
|
215
276
|
}
|
|
216
277
|
export function agyToken() {
|
|
217
278
|
// agy (go-keyring) keeps {"token": {access_token, expiry, ...}} under service "gemini", user "antigravity"
|
|
@@ -242,13 +303,17 @@ export async function antigravityQuota() {
|
|
|
242
303
|
const name = "antigravity";
|
|
243
304
|
const t = agyToken();
|
|
244
305
|
if (!t?.access)
|
|
245
|
-
return noData(name, m("no agy login in the keyring", "sem login do agy no chaveiro"));
|
|
306
|
+
return [noData(name, m("no agy login in the keyring", "sem login do agy no chaveiro"))];
|
|
246
307
|
// Never refresh it here (that is agy's login). The access token lives 1h and agy renews it only when it runs, so a
|
|
247
308
|
// passed expiry with a refresh token is just idle (a note, not an expired login); without one, "run agy once".
|
|
248
309
|
const expired = m("login expired: run `agy models`", "login expirado: rode `agy models`");
|
|
310
|
+
const stale = (why) => {
|
|
311
|
+
const line = fromCache(name, why, undefined, t.acct);
|
|
312
|
+
return [line, ...modelLines(name, cachedBuckets(name, t.acct), Date.now(), line.note)];
|
|
313
|
+
};
|
|
249
314
|
const note = agyNote(t);
|
|
250
315
|
if (note)
|
|
251
|
-
return
|
|
316
|
+
return stale(note);
|
|
252
317
|
try {
|
|
253
318
|
const r = await fetch("https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", {
|
|
254
319
|
method: "POST", body: "{}",
|
|
@@ -257,15 +322,16 @@ export async function antigravityQuota() {
|
|
|
257
322
|
signal: AbortSignal.timeout(15_000),
|
|
258
323
|
});
|
|
259
324
|
if (r.status === 401)
|
|
260
|
-
return
|
|
325
|
+
return stale(expired);
|
|
261
326
|
if (!r.ok)
|
|
262
|
-
return
|
|
327
|
+
return stale(`HTTP ${r.status}`);
|
|
263
328
|
const j = await r.json();
|
|
264
|
-
const { five, week } = antigravityFromModels(j.models ?? {});
|
|
265
|
-
|
|
329
|
+
const { five, week, buckets: api } = antigravityFromModels(j.models ?? {});
|
|
330
|
+
const buckets = mergeLearned(api, cachedBuckets(name, t.acct));
|
|
331
|
+
return [remember(name, five, week, t.acct, buckets), ...modelLines(name, buckets)];
|
|
266
332
|
}
|
|
267
333
|
catch (e) {
|
|
268
|
-
return
|
|
334
|
+
return stale(m("collector failed", "coletor falhou") + `: ${e.message}`);
|
|
269
335
|
}
|
|
270
336
|
}
|
|
271
337
|
// --- copilot --------------------------------------------------------------------------------------------------------
|
|
@@ -385,7 +451,7 @@ export async function collect(sources, want = []) {
|
|
|
385
451
|
if (name === "codex")
|
|
386
452
|
return wanted(name) ? [format(codexQuota())] : [];
|
|
387
453
|
if (name === "antigravity")
|
|
388
|
-
return wanted(name) ?
|
|
454
|
+
return wanted(name) ? (await antigravityQuota()).map(format) : [];
|
|
389
455
|
if (name === "copilot")
|
|
390
456
|
return wanted(name) ? [format(copilotQuota())] : [];
|
|
391
457
|
if (name === "opencode" || name === "minimax")
|
package/dist/stats.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { basename, join } from "node:path";
|
|
9
|
+
import { doneStatus } from "./process.js";
|
|
9
10
|
const ROLE = /^(impl|fix\d*|review\d*|[a-z]+-review\d*|watchdog\d*)-?/;
|
|
10
11
|
const read = (f) => { try {
|
|
11
12
|
return readFileSync(f, "utf8");
|
|
@@ -120,7 +121,7 @@ export function collectRuns(ws, days) {
|
|
|
120
121
|
: name.startsWith("impl") ? "impl" : "other";
|
|
121
122
|
const out = read(`${pre}.out.md`);
|
|
122
123
|
const [neu, tot] = tokens(agent, log, read(`${pre}.id`).trim());
|
|
123
|
-
runs.push({ issue, name, agent, model, role, ok: read(done)
|
|
124
|
+
runs.push({ issue, name, agent, model, role, ok: doneStatus(read(done)) === "ok" && out.trim() !== "",
|
|
124
125
|
secs: Math.max(0, Math.floor((statSync(done).mtimeMs - rs.mtimeMs) / 1000)), new: neu, tot,
|
|
125
126
|
repo: name.includes("-") ? name.slice(name.indexOf("-") + 1) : "" });
|
|
126
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irythill/orq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Orchestrate coding agents (Claude Code, Codex, OpenCode, Antigravity, Copilot) from one conductor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"!dist/**/*.test.js",
|
|
13
13
|
"skills",
|
|
14
|
-
"templates/workspace.CLAUDE.md"
|
|
14
|
+
"templates/workspace.CLAUDE.md",
|
|
15
|
+
"templates/conductor.instructions.md"
|
|
15
16
|
],
|
|
16
17
|
"engines": {
|
|
17
18
|
"node": ">=20"
|
package/skills/issue/SKILL.md
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: issue
|
|
3
|
-
description: Runs a Plane issue end to end by orchestrating coding agents in herdr or any terminal (
|
|
3
|
+
description: Runs a Plane issue end to end by orchestrating coding agents in herdr or any terminal (orq agent + orq wait) — implementer and cross-vendor reviewer picked by orq pick, tests by orq verify, comment and status in Plane. Use when the user says "/issue <ID>", "take issue X", "run ABC-123", or asks to check/create issues in the conductor's ecosystem.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /issue <ID> — conductor playbook
|
|
6
|
+
# /issue <ID> — conductor playbook
|
|
7
7
|
|
|
8
8
|
You are the **orchestrator**: you plan, dispatch, follow up and report. **Do not implement anything yourself** (it
|
|
9
9
|
burns the conductor's model). Talk to the user in the user's language.
|
|
10
10
|
|
|
11
11
|
**Ecosystem configuration:** the **workspace** `CLAUDE.md` (the folder named in your conductor instructions) defines
|
|
12
|
-
the repositories and their groups (= herdr workspace, `-g`), **each repo's base branch**, verification commands,
|
|
12
|
+
the repositories and their groups (= herdr workspace or just a label, `-g`), **each repo's base branch**, verification commands,
|
|
13
13
|
Plane projects (identifier → project_id), default assignee, labels/modules and MCP limitations. Read it before you
|
|
14
14
|
start. Below, `$WS` = workspace.
|
|
15
|
-
**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
15
|
+
**Terminal:** the flow is the same in herdr, tmux or a plain terminal, on any OS. Children open in herdr tabs inside
|
|
16
|
+
herdr (`HERDR_ENV=1`), in tmux windows inside tmux, else in the background (their screen goes to a log; the answer is
|
|
17
|
+
still `<prefix>.out.md`; `-T`, the agent's own screen, needs herdr or tmux). Set `ORQ_CONDUCTOR_ID` to a fixed name per
|
|
18
|
+
conductor outside herdr.
|
|
19
|
+
- **Completion signal:** in herdr, one line `[HERDR-AGENT DONE] <title> status=ok|fail ... answer: <file>` is typed into
|
|
20
|
+
your pane after each child. Anywhere else no line arrives: after dispatching, run `orq wait <prefix>` **as a background
|
|
21
21
|
task** (Claude Code: Bash with `run_in_background`); its exit wakes you and its output is the line
|
|
22
|
-
`[ORQ AGENT DONE] <title> status=ok|fail ... answer: <file
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- `
|
|
26
|
-
|
|
27
|
-
- Peeking at a child: `<prefix>.log` (
|
|
22
|
+
`[ORQ AGENT DONE] <title> status=ok|fail ... answer: <file>`. Treat both lines the same. Several children in parallel:
|
|
23
|
+
`orq wait --any <prefix1> <prefix2>` (one wake-up per finish; wait again for the rest). No background tasks in your
|
|
24
|
+
harness → run `orq wait` in the foreground; that is the one allowed blocking wait.
|
|
25
|
+
- `orq verify` and `orq baseline` run like children (herdr tab, tmux window or background) and you wait for them the same
|
|
26
|
+
way, e.g. `orq wait $O/verify-<repo>`.
|
|
27
|
+
- Peeking at a child: `<prefix>.log` (in herdr also `herdr pane read $(cat <prefix>.pane) --source recent`).
|
|
28
28
|
Issue artifacts (prompts, notes) live in `$WS/.orquestra/<ID>/`.
|
|
29
29
|
|
|
30
30
|
## 1. Read the issue
|
|
31
|
-
- **First, claim it:** `
|
|
31
|
+
- **First, claim it:** `orq claim -w $WS <ID>`. Exit 1 = another conductor owns it: **stop** and tell the user
|
|
32
32
|
(don't read it, don't dispatch anything). Taking it over (e.g. switching accounts) only with the user's OK:
|
|
33
|
-
`
|
|
33
|
+
`orq claim -w $WS -f <ID>`. Several conductors may share a workspace; `orq claim -w $WS -l` lists who has what,
|
|
34
34
|
with each conductor's name (its herdr workspace label, e.g. "emp: orq-cms-dsgn"; outside herdr, `ORQ_CONDUCTOR_ID`).
|
|
35
35
|
- **The user names another conductor** ("take over orq-cms-dsgn's work"): never guess a pane id. Run
|
|
36
36
|
`orq claim -w $WS --from "<what they said>"` (any part of the name works). It lists that conductor's issues; exit 3 =
|
|
@@ -71,19 +71,19 @@ orq pick opinion --impl-agent <agent that implemented> # second opin
|
|
|
71
71
|
```
|
|
72
72
|
More than one agent eligible for `review`/`opinion` (declared, not excluded or blocked, with a model for the role; quota is not considered) and neither `--impl-agent` nor `--impl-vendor` given → exit 2:
|
|
73
73
|
always pass one of them (`--no-record`/`--explain` do not change this).
|
|
74
|
-
The output goes straight into `
|
|
74
|
+
The output goes straight into `orq agent` (`orq agent $(orq pick ...) -g ... -t ... -d ... -o ... -- prompt.md`).
|
|
75
75
|
- Exit 3 = no candidate (quota exhausted, no agent with that role): **stop and ask the user**.
|
|
76
76
|
- Implementer failed review twice (step 6) → pick again without it: `orq pick impl --level <level> --exclude-agent <agent>`.
|
|
77
77
|
- `--exclude-agent` never "sticks": every `orq pick` call decides again, with only that dispatch's exclusions.
|
|
78
78
|
- The conductor's own model (e.g. Opus) as implementer: only with the user's OK, never through `orq pick`.
|
|
79
79
|
- Want to understand a choice? `orq pick ... --explain --no-record` (score of each candidate; not counted in rotation).
|
|
80
80
|
|
|
81
|
-
**Every child runs through `
|
|
82
|
-
`opencode run` / `agy -p` / `copilot -p`) in a herdr tab (workspace = group `-g`, tab = title)
|
|
83
|
-
When it finishes it writes `<prefix>.done` and
|
|
84
|
-
|
|
81
|
+
**Every child runs through `orq agent`** (agent from `orq pick`): headless (`claude -p` / `codex exec` /
|
|
82
|
+
`opencode run` / `agy -p` / `copilot -p`) in a herdr tab (workspace = group `-g`, tab = title), a tmux window or the
|
|
83
|
+
background, with a log. When it finishes it writes `<prefix>.done` and signals you (see the completion signal above:
|
|
84
|
+
`[HERDR-AGENT DONE] <title> status=ok|fail ...` in herdr, `orq wait` elsewhere); no MCP, and a **4 GB memory cap per agent** (own cgroup: if it blows up, only it dies). NEVER `herdr agent start`
|
|
85
85
|
for children (no memory cap, no answer file). **Screen mode (`-T`, or `HERDR_AGENT_TUI=1`; herdr or tmux):** the tab
|
|
86
|
-
shows the agent's own TUI instead of the log; same cap, same files, same signal (end-of-turn hook). The screen stays
|
|
86
|
+
or window shows the agent's own TUI instead of the log; same cap, same files, same signal (end-of-turn hook). The screen stays
|
|
87
87
|
open after the turn (it holds memory until the tab closes); `-R` with the tab open types the follow-up into it. Use it
|
|
88
88
|
when the user wants to watch. The repo must already be trusted in Codex/Claude (otherwise the TUI stops at the "trust this
|
|
89
89
|
folder?" dialog — tell the user, don't approve it for them).
|
|
@@ -115,7 +115,7 @@ git -C $WT log -1 --format='%h %s' # check HEAD == origin/$BA
|
|
|
115
115
|
on a modest disk that saturates I/O and memory and freezes the machine). The worktree uses a **link** to the main
|
|
116
116
|
repo's `node_modules`:
|
|
117
117
|
```bash
|
|
118
|
-
|
|
118
|
+
orq deps -r $R -b $BASE -w $WT # link; stale deps → fast-forward + ONE install in $R (per-repo lock across conductors)
|
|
119
119
|
```
|
|
120
120
|
Exit 3 = stale deps and `$R` dirty or not on `$BASE` → **ask the user**. If another conductor is installing in the
|
|
121
121
|
same repo, the command waits its turn (never install by hand).
|
|
@@ -124,39 +124,39 @@ tell me".
|
|
|
124
124
|
**Dispatch** (no MCP; memory cap; writes in automatic mode — that is why implementation only happens in a worktree):
|
|
125
125
|
```bash
|
|
126
126
|
orq pick impl --level <level> > $O/impl-<repo>.pick # the fix rounds reuse it (step 6)
|
|
127
|
-
|
|
127
|
+
orq agent $(cat $O/impl-<repo>.pick) -g <group> -t "<ID> impl <repo>" -d $WT -o $O/impl-<repo> -- $O/impl-<repo>.md
|
|
128
128
|
```
|
|
129
129
|
- The Claude account (`-c`) already comes from `orq pick` (one under the limit whose pace stays within it, then the least used).
|
|
130
130
|
- Full final answer in `$O/impl-<repo>.out.md`; log in `.log`; the agent's session id in `.id`.
|
|
131
131
|
- Codex's sandbox has no network: `git fetch` is yours, beforehand (above). Push/PR: yours (step 7).
|
|
132
|
-
- `
|
|
133
|
-
(`herdr pane read $(cat <prefix>.pane) --source recent`
|
|
134
|
-
- **No baseline in the issue flow:** don't run `
|
|
135
|
-
the full suite blocks every conductor's test queue). `
|
|
132
|
+
- `orq agent` returns immediately (no need to background it); the child's pane is in `<prefix>.pane`
|
|
133
|
+
(to peek: `<prefix>.log`; in herdr, `herdr pane read $(cat <prefix>.pane) --source recent`).
|
|
134
|
+
- **No baseline in the issue flow:** don't run `orq baseline` before dispatching (the base moves with every merge and
|
|
135
|
+
the full suite blocks every conductor's test queue). `orq verify` checks the base on demand (step 4b).
|
|
136
136
|
- **Children start WITHOUT MCP** (Plane is yours; each MCP costs ~150–200 MB per session and `systemd-oomd` kills the
|
|
137
|
-
whole herdr server when memory runs out) — `
|
|
137
|
+
whole herdr server when memory runs out) — `orq agent` takes care of it.
|
|
138
138
|
- Every child prompt ends with: "Don't try to talk to the conductor or use herdr; finish with your complete final
|
|
139
139
|
answer (that is what I read)."
|
|
140
140
|
- **Verification in the child: lint and tsc only, one command at a time** (the cap is 4 GB per agent and jest/vitest
|
|
141
141
|
blow it; tests are yours, step 4b): use the Verification column of the workspace `CLAUDE.md` and put in the prompt
|
|
142
142
|
"verify sequentially, never in parallel or watch mode: (1) lint only the changed files (`git diff --name-only
|
|
143
143
|
origin/<BASE>...HEAD`); (2) `NODE_OPTIONS=--max-old-space-size=2048 tsc --noEmit` for the project. Don't run tests".
|
|
144
|
-
Full suite only in CI (or a manual `
|
|
144
|
+
Full suite only in CI (or a manual `orq baseline` if the user asks).
|
|
145
145
|
- An issue that changes an API contract: **API first**; consumers afterwards, with the new contract in the prompt.
|
|
146
146
|
- **A red board line / `✗` pane title = a failure you may have missed.** If you see one (or the user says notices aren't
|
|
147
147
|
arriving), run `orq notice` (delivery log) and `<prefix>.done` for that issue, then handle the failure; running
|
|
148
148
|
`orq wait`, `orq verify` or `orq agent` for the issue acknowledges it.
|
|
149
149
|
- **Never block waiting** (`herdr agent wait`, `pane wait-output`, `sleep`, `timeout`): completion arrives as a
|
|
150
|
-
`[HERDR-AGENT DONE]` line in your pane
|
|
150
|
+
`[HERDR-AGENT DONE]` line in your pane (herdr) or through the background `orq wait`. Dispatch, report in one line and end your turn. "Done yet?" = `<prefix>.done`
|
|
151
151
|
exists (never `pgrep`).
|
|
152
|
-
- Investigation only (no code): "READ ONLY" prompt, in the repo itself, no worktree. `
|
|
152
|
+
- Investigation only (no code): "READ ONLY" prompt, in the repo itself, no worktree. `orq agent -r` (Claude:
|
|
153
153
|
`--permission-mode plan`; the others: throwaway worktree, no editing).
|
|
154
154
|
|
|
155
155
|
## 4b. Tests (yours, not the child's)
|
|
156
156
|
Child with `status=ok` → run the tests related to the diff, in their own tab, one at a time on the machine (queue):
|
|
157
|
-
`
|
|
157
|
+
`orq verify -w $WS -r <repo> -d $WT -b <BASE> -o $O/verify-<repo> -g <group>` → `$O/verify-<repo>.txt` with
|
|
158
158
|
**new failures** and pre-existing ones: if any test fails, it runs the same test files on an updated
|
|
159
|
-
`origin/<BASE>` (whatever fails there is pre-existing). You get `[HERDR-AGENT DONE] verify ... new=N`.
|
|
159
|
+
`origin/<BASE>` (whatever fails there is pre-existing). You get the done line (`[HERDR-AGENT DONE]` / `[ORQ AGENT DONE]`) `verify ... new=N`.
|
|
160
160
|
`new>0` → fix round with the list (step 6), no review. `new=0` → review.
|
|
161
161
|
**Pre-existing failures get fixed too.** Any failure — tests listed as "already failing on origin/<BASE>", or a CI job
|
|
162
162
|
(lint, biome, typecheck, shard, regression) failing on the PR or on the base — goes into a fix round on the issue's
|
|
@@ -165,12 +165,12 @@ something is red, new or old, and never widen a baseline or allowlist to silence
|
|
|
165
165
|
outside the issue's domain: ask the user first (then it ships as its own PR, not as a note).
|
|
166
166
|
**Mandatory order every round (impl and each fix): child `ok` → verify → review.** Never dispatch the review (or an
|
|
167
167
|
"early" fix) while this round's verify is pending, even if it is queued behind another verify: wait for
|
|
168
|
-
|
|
168
|
+
the `... verify ...` done line and end your turn until then.
|
|
169
169
|
|
|
170
170
|
## 5. Cross review (always another vendor)
|
|
171
171
|
```bash
|
|
172
172
|
orq pick review --level <level> --impl-agent <who implemented> > $O/review-<repo>.pick # round 2+ reuses it
|
|
173
|
-
|
|
173
|
+
orq agent $(cat $O/review-<repo>.pick) -b $BASE \
|
|
174
174
|
-g <group> -t "<ID> review <repo>" -d $WT -o $O/review-<repo> -r -i $O/impl-<repo>.md -i $O/impl-<repo>.out.md \
|
|
175
175
|
-i $O/verify-<repo>.txt \
|
|
176
176
|
-- $O/review-<repo>.md
|
|
@@ -211,7 +211,7 @@ repo checklist.
|
|
|
211
211
|
only restates a `## Decisions` choice, counts as a suggestion; when you downgrade one, say why in the Plane comment.
|
|
212
212
|
No blockers left → APPROVED, even with suggestions.
|
|
213
213
|
Blocking problems → write the list (blockers only) to `$O/fix-<n>.md` (always this name; reviews `$O/review-<repo>-<n>`, never `fix<n>-<repo>` or other variants — a later conductor counts rounds by these files) and continue the implementer's session (any agent):
|
|
214
|
-
`
|
|
214
|
+
`orq agent $(orq pick fix --same $O/impl-<repo>.pick) -g <group> -t "<ID> fix<n> <repo>" -d $WT -o $O/impl-<repo> -R $(cat $O/impl-<repo>.id) -- $O/fix-<n>.md`.
|
|
215
215
|
A fix is a continuation: same agent, model, account and session, effort one level up (`--same` reads the implementer's
|
|
216
216
|
pick; never a new `orq pick`, never `-a` alone — without `-m` the session resumes on the CLI's default model, which
|
|
217
217
|
loses the prompt cache and hands the work to a weaker model). `fix-<n>.md` = the numbered blockers + these rules: "fix only
|
|
@@ -219,7 +219,7 @@ these items, with the smallest diff that does it; don't refactor, rename or refo
|
|
|
219
219
|
each blocker add or adjust a test that fails without the fix, when the code is testable (say so when it isn't); if an
|
|
220
220
|
item is wrong or conflicts with a criterion, don't fix it: say why"; and it ends with "update the `## Decisions`
|
|
221
221
|
section of your final answer for the whole branch". **Before dispatching the fix:** `git -C $WT rev-parse HEAD > $O/round-<n>.sha`.
|
|
222
|
-
Then a new `
|
|
222
|
+
Then a new `orq verify` (4b) and the **round review** (step 5 with these changes):
|
|
223
223
|
- Same reviewer as the round before: `$(cat $O/review-<repo>.pick)`. Only if it is over quota, blocked or fails with
|
|
224
224
|
no verdict → a new `orq pick review` (saved to the same file).
|
|
225
225
|
- Extra attachments: `git -C $WT diff $(cat $O/round-<n>.sha)..HEAD > $O/fix-<n>.diff` → `-i $O/fix-<n>.md -i $O/fix-<n>.diff`
|
|
@@ -258,9 +258,9 @@ rules live) commits and writes the PR. Find and cite in the prompt, by path, wha
|
|
|
258
258
|
Red → `gh run view <id> --log-failed | tail -40`: error from the diff → fix round; error already on the base → create
|
|
259
259
|
a Plane issue (workspace standard) and tell the user. "Ready to merge" only with green checks or an explained failure
|
|
260
260
|
+ issue.
|
|
261
|
-
- Move it to **In Review** (finished child tabs close by themselves; close any leftover with
|
|
261
|
+
- Move it to **In Review** (finished child tabs close by themselves; in herdr, close any leftover with
|
|
262
262
|
`herdr tab list | jq -r '.result.tabs[]|select(.label|startswith("<ID> "))|.tab_id' | xargs -rn1 herdr tab close`)
|
|
263
|
-
and **release the issue**: `
|
|
263
|
+
and **release the issue**: `orq claim -w $WS -r <ID>`. The worktree stays until the merge.
|
|
264
264
|
- **After the merge** (when the user confirms): run `orq gc -w $WS` and show the list. If this issue's worktree is
|
|
265
265
|
listed as removable, run `orq gc -w $WS --yes`; anything else → ask the user.
|
|
266
266
|
- **Never** push, PR, merge or deploy unless the user asks. Report to the user in 3–5 lines.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Conductor "{{NAME}}" — {{NAME}} ecosystem
|
|
2
|
+
|
|
3
|
+
You orchestrate agents in the `{{WORKSPACE}}` ecosystem, in herdr (`HERDR_ENV=1`) or in any other terminal.
|
|
4
|
+
Don't write code yourself. Opened by `orq conductor`. Talk to the user in the user's language.
|
|
5
|
+
1. Read `{{WORKSPACE}}/CLAUDE.md` (repos, groups, base branch, Plane, issue standard, MCP limitations).
|
|
6
|
+
2. For every Plane issue, follow the `/issue <ID>` skill.
|
|
7
|
+
3. Never push, open a PR, merge or deploy unless the user asks. When unsure about scope or cost, ask.
|
|
8
|
+
4. **Children only through `orq agent`** (implementer, fix, review), with the agent chosen by `orq pick`; tests through
|
|
9
|
+
`orq verify` (it checks the base by itself; no `orq baseline` in the flow), as the skill says. NEVER
|
|
10
|
+
`herdr agent start` for children (no memory cap, no answer file); to watch the agent's own screen, `orq agent -T` (herdr or tmux).
|
|
11
|
+
5. **Completion = one single signal:** in herdr, the line `[HERDR-AGENT DONE] <title> status=ok|fail ... answer: <file>`
|
|
12
|
+
arriving in your pane; elsewhere, `orq wait <prefix>` (as a background task) printing
|
|
13
|
+
`[ORQ AGENT DONE] <title> status=ok|fail ... answer: <file>` (plus the `<prefix>.done` file either way). When it
|
|
14
|
+
arrives: read the answer file and act. Nothing else means done.
|
|
15
|
+
6. **"Is it still running?"** is answered by the file only: `<prefix>.done` exists → finished; it doesn't → running;
|
|
16
|
+
check `<prefix>.log` (last write time) or, in herdr, peek at the pane: `herdr pane read $(cat <prefix>.pane) --source recent`.
|
|
17
|
+
NEVER `pgrep`/`ps` (they match their own command line).
|
|
18
|
+
7. **Never block waiting** (`herdr agent wait`, `pane wait-output`, `sleep`): dispatch, report in one line and end
|
|
19
|
+
your turn (outside herdr: `orq wait` in the background, never in the foreground unless your harness has no background tasks).
|
|
20
|
+
8. Worktrees always from `origin/<repo base>`, created with `git worktree add` (never `herdr worktree create`).
|
|
21
|
+
9. Between batches of unrelated issues, suggest `/clear` to the user: state lives in Plane and in `.orquestra/`.
|
|
22
|
+
|
|
23
|
+
## How you are used
|
|
24
|
+
- **Dispatcher, not consultant.** Your job: issue → dispatch → report → PR/cleanup. Long questions about architecture,
|
|
25
|
+
infrastructure (DNS, email, deploy, webhooks) or business decisions: answer in at most 3 lines and suggest "take it
|
|
26
|
+
to a design session; come back with the decision recorded in the issue".
|
|
27
|
+
- **The issue is the source.** A decision made in chat → record it in Plane (description or comment) before
|
|
28
|
+
dispatching; the child's prompt comes from the issue, not from the conversation.
|
|
29
|
+
- **Context:** past ~150k tokens or after closing a batch of related issues → suggest `/clear` (state in Plane and in
|
|
30
|
+
`.orquestra/`).
|
|
31
|
+
- 1–3 line answers. Final issue report: 3–5 lines + the PR link.
|
|
@@ -5,11 +5,12 @@ rules — read the repo's own before touching it. This file only holds the map a
|
|
|
5
5
|
|
|
6
6
|
## Orchestration (read by the `/issue` skill)
|
|
7
7
|
- **Workspace:** `{{WORKSPACE}}`
|
|
8
|
-
- **Conductor
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
- **Conductor:** opened with `orq conductor [account] [model] [-r <session-id>]` in this folder, in any terminal (inside a
|
|
9
|
+
herdr pane too; `herdr-conductor` still works there). Instructions in `.orquestra-conductor.md`; the **Group** in the
|
|
10
|
+
table below is the herdr workspace (`-g`) or just a label outside herdr.
|
|
11
|
+
Tests from the Verification column (jest/vitest) **don't run in the child**: the conductor runs `orq verify`
|
|
12
|
+
(one at a time on the machine); the child does lint + `tsc` (`NODE_OPTIONS=--max-old-space-size=2048`).
|
|
13
|
+
- **Agents:** chosen by `orq pick` from each developer's `~/.config/orq/config.toml` — nothing about models here.
|
|
13
14
|
|
|
14
15
|
## Scope
|
|
15
16
|
- Before dispatching, split big issues into sub-issues or sequential batches (one PR, one implementer each) when they touch
|