@irythill/orq 0.8.0 → 0.9.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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # orq — orchestrating coding agents
6
6
 
7
- You describe the work in a Plane issue. A **conductor** (Claude, ideally Opus) reads it, picks the right agent for
7
+ You describe the work in a Plane issue. A **conductor** (any agent that can run commands) reads it, picks the right agent for
8
8
  each step, dispatches the **implementer**, the **tests** and a **cross-vendor review** (each in its own tab or window
9
9
  in [herdr](https://herdr.dev) or tmux, or in the background), and hands the issue back *In Review* with a summary in
10
10
  Plane. Merge, PR and deploy stay with you.
@@ -20,7 +20,7 @@ that lives in your config.
20
20
  ## How it works
21
21
 
22
22
  ```
23
- You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
23
+ You ── "/issue DEMO-123" ──► conductor (any agent CLI, in any terminal)
24
24
  │ orq pick impl ──► which agent/model implements (config + history + quota)
25
25
  ├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
26
26
  ├─► tests orq verify only the diff's tests · queue of one suite at a time
@@ -47,9 +47,9 @@ You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
47
47
 
48
48
  | Agent | CLI | Possible roles | Notes |
49
49
  |---|---|---|---|
50
- | Claude Code | `claude` | conductor, implement, review | several accounts (`claude-accounts`); Opus as conductor |
51
- | Codex | `codex` | implement, review | sandbox without network: orq runs `git fetch` before a write run (`network = false`); quota from its session files (the free plan's 30-day window included) |
52
- | OpenCode | `opencode` | implement, review | any provider (e.g. MiniMax); on MiniMax, quota from the Token Plan API with your key |
50
+ | Claude Code | `claude` | conductor, implement, review | several accounts (`claude-accounts`); Claude as conductor |
51
+ | Codex | `codex` | conductor, implement, review | sandbox without network: orq runs `git fetch` before a write run (`network = false`); quota from its session files (the free plan's 30-day window included) |
52
+ | OpenCode | `opencode` | conductor, implement, review | any provider (e.g. MiniMax); on MiniMax, quota from the Token Plan API with your key |
53
53
  | Antigravity | `agy` | review (implement with an allowlist) | headless denies commands outside its allowlist: it reviews by reading the attached diff; it can't commit, so orq commits its write runs (`commit = false`) |
54
54
  | GitHub Copilot | `copilot` | second opinion | small allowances on some plans: occasional use |
55
55
 
@@ -70,7 +70,7 @@ orq setup
70
70
  `orq setup` shows what it found (git, terminal, how memory is capped, keyring, your shell), links orq's skills into
71
71
  `~/.claude/skills/` and each account folder in `claude-accounts` (a junction on Windows; never over a folder of
72
72
  yours), asks about your agents and plans, writes `~/.config/orq/config.toml` (`%APPDATA%\orq\config.toml` on Windows)
73
- and prepares the folder that groups your repos for the conductor: `.orquestra/`, the map `CLAUDE.md` and the
73
+ and prepares the folder that groups your repos for the conductor: `.orquestra/`, the map `ORQ.md` and the
74
74
  `/issue` playbook in the conductor's format. Pieces on their own: `orq setup --install` (no questions),
75
75
  `orq setup --workspace <folder> --conductor <agent>`. Inside Claude, **`/orq-setup`** does the same as a
76
76
  conversation and also fills in the repo table. Plane: [docs/plane.md](docs/plane.md).
@@ -172,7 +172,7 @@ issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Bra
172
172
  quota_week = 95 # ≥ 95% of the week → agent out
173
173
  quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
174
174
  pacing_penalty = 0.3 # score penalty when usage at the current pace would pass the pick limit (5h by level, week: quota_week) by the reset (0 = off)
175
- # [agents.claude] headroom = 25 # points kept for conductors: children leave the account at limit − 25
175
+ # [conductor] headroom = 25 # points kept for the active conductor: children stop at limit − 25
176
176
  [policy.weights]
177
177
  quality = 0.40 # did it deliver? (impl: approved on the first review)
178
178
  tokens = 0.45 # did it spend little?
@@ -187,7 +187,7 @@ rotation = 0.20 # penalty for having been picked recent
187
187
  the least used.
188
188
  2. **Scores:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. The numbers come
189
189
  from the history (`orq stats`); an agent with few runs uses the config's `prior` and gets an exploration bonus.
190
- 3. **`reserve = true` agents** (e.g. Claude, which shares quota with the conductor) only come in when nothing else fits.
190
+ 3. **Reserved agents** (`reserve = true`, or the active conductor by default) receive a score penalty to keep quota for orchestration.
191
191
 
192
192
  ```bash
193
193
  orq pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
@@ -215,8 +215,37 @@ Whatever is already running keeps its language. Conductors always talk to you in
215
215
 
216
216
  Open the conductor in any terminal, in your workspace folder (the one `orq setup` prepared):
217
217
  ```bash
218
- cd ~/Code/<workspace> && orq conductor [account] [model] # any terminal: loads the conductor instructions, the workspace CLAUDE.md and the /issue skill
218
+ cd ~/Code/<workspace> && orq conductor [agent] [account|-] [model] # any terminal: loads the conductor instructions, the workspace ORQ.md and the /issue skill
219
219
  ```
220
+ Choose the conductor with `orq setup --workspace <folder> --conductor codex`, then run `orq conductor` in that folder.
221
+ `orq conductor codex - <model>` overrides the choice for one session; `-r <id>` resumes it. Claude Code is also a
222
+ tested conductor; the old `orq conductor <Claude account> <model>` syntax still works. If an account and an agent
223
+ have the same name, use `--agent <name>` to select the agent explicitly. The `herdr-conductor` wrapper keeps opening Claude.
224
+
225
+ `ORQ.md` holds the workspace map. Setup preserves an existing `CLAUDE.md`, copying it into `ORQ.md` on migration;
226
+ otherwise it creates a `CLAUDE.md` pointer. `AGENTS.md` points to the map and holds the generated issue playbook,
227
+ preserving text outside the generated block. Edit `ORQ.md` after migration. With no background commands, the conductor
228
+ uses `orq wait` in the foreground. `orq handoff` still moves Claude accounts only.
229
+
230
+ Defaults live in `[roles.conductor]` (`agent`, `account`, `model`). Interactive templates are separate from worker
231
+ templates, keeping MCPs and normal permission prompts enabled. Override any built-in or add another CLI:
232
+
233
+ ```toml
234
+ [agents.codex.conductor]
235
+ run = "codex [-m {model}] -C {dir} {prompt_text}"
236
+ resume = "codex resume {session} [-m {model}] -C {dir} {prompt_text}"
237
+ models = { fast = "your-model-id" }
238
+ accounts = { work = { CODEX_HOME = "/path/to/codex-work" } }
239
+
240
+ [conductor]
241
+ headroom = 25 # percentage points kept for whichever agent conducts
242
+ ```
243
+
244
+ Templates accept `{dir}`, `{instructions}` (file path), `{prompt_text}` (read-instructions prompt), `{model}`,
245
+ `{account}`, `{session}` and `{name}`. Account entries and `env` are environment-variable maps. The running conductor
246
+ exports `ORQ_CONDUCTOR_AGENT`, so `orq pick` applies the reserve penalty and `[conductor].headroom` to that agent,
247
+ even after a session override. Explicit `agents.<name>.reserve` and `headroom` take precedence.
248
+
220
249
  With several conductors, give each a fixed name (`export ORQ_CONDUCTOR_ID=<name>`; in herdr, the workspace label is
221
250
  used) so `orq claim` can tell them apart: the Claude session is named `conductor-<workspace>:<name>`, and
222
251
  `orq claim -l` ends each line with that `session=`, so conductors can message each other.
@@ -279,7 +308,7 @@ Day to day, shortcuts and several issues at once: [docs/herdr.md](docs/herdr.md)
279
308
  | `orq wait <prefix>...` | Blocks until those agents finish; prints `status=` and the answer file (any conductor, any terminal) |
280
309
  | `orq verify ...` (`herdr-verify`) | Tests related to the diff, queued and memory-capped; failures that also fail on the base count as pre-existing |
281
310
  | `orq board` (`herdr-board`) | Live board: queue, agents, conductors, quota, blocks, scoreboard |
282
- | `orq conductor [account] [model] [-r <id>]` (`herdr-conductor`) | Opens the conductor (Claude) in the current terminal with its instructions, account and model; `-r` resumes a session |
311
+ | `orq conductor [agent] [account|-] [model] [-r <id>]` (`herdr-conductor`) | Opens the configured conductor in the current terminal with its instructions, account and model; `-r` resumes a session |
283
312
  | `orq claim` (`herdr-claim`) | Owner of each issue among conductors |
284
313
  | `orq deps` (`herdr-deps`) | Links the worktree's dependencies to the main repo's (no reinstall) |
285
314
  | `orq baseline ...` (`herdr-baseline`) | Manual snapshot of the tests failing on the base (outside the flow) |
package/README.pt-BR.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # orq — orquestrando agentes de código
6
6
 
7
- Você descreve o trabalho numa issue do Plane. Um **conductor** (Claude, de preferência Opus) lê a issue, escolhe o
7
+ Você descreve o trabalho numa issue do Plane. Um **conductor** (qualquer agente que execute comandos) lê a issue, escolhe o
8
8
  agente certo para cada etapa, despacha o **implementador**, os **testes** e uma **review cross-vendor** (cada um
9
9
  em sua própria aba ou janela no [herdr](https://herdr.dev) ou no tmux, ou em segundo plano), e devolve a issue
10
10
  *In Review* com um resumo no Plane. Merge, PR e deploy continuam com você.
@@ -20,7 +20,7 @@ trabalhar. Tudo isso fica na sua config.
20
20
  ## Como funciona
21
21
 
22
22
  ```
23
- You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
23
+ You ── "/issue DEMO-123" ──► conductor (any agent CLI, in any terminal)
24
24
  │ orq pick impl ──► which agent/model implements (config + history + quota)
25
25
  ├─► implementer orq agent own tab/window or background · worktree from origin/<base> · memory cap
26
26
  ├─► tests orq verify only the diff's tests · queue of one suite at a time
@@ -49,9 +49,9 @@ You ── "/issue DEMO-123" ──► conductor (Claude, in any terminal)
49
49
 
50
50
  | Agente | CLI | Papéis possíveis | Observações |
51
51
  |---|---|---|---|
52
- | Claude Code | `claude` | conductor, implement, review | várias contas (`claude-accounts`); Opus como conductor |
53
- | Codex | `codex` | implement, review | sandbox sem rede: o orq roda o `git fetch` antes de uma execução de escrita (`network = false`); cota lida dos arquivos de sessão (inclusive a janela de 30 dias do plano free) |
54
- | OpenCode | `opencode` | implement, review | qualquer provider (ex.: MiniMax); na MiniMax, cota pela API do Token Plan com a sua chave |
52
+ | Claude Code | `claude` | conductor, implement, review | várias contas (`claude-accounts`); Claude como conductor |
53
+ | Codex | `codex` | conductor, implement, review | sandbox sem rede: o orq roda o `git fetch` antes de uma execução de escrita (`network = false`); cota lida dos arquivos de sessão (inclusive a janela de 30 dias do plano free) |
54
+ | OpenCode | `opencode` | conductor, implement, review | qualquer provider (ex.: MiniMax); na MiniMax, cota pela API do Token Plan com a sua chave |
55
55
  | Antigravity | `agy` | review (implement com allowlist) | em modo headless nega comandos fora da allowlist: faz a review lendo o diff anexado; não consegue commitar, então o orq commita as execuções de escrita dele (`commit = false`) |
56
56
  | GitHub Copilot | `copilot` | segunda opinião | cota pequena em alguns planos: uso ocasional |
57
57
 
@@ -73,7 +73,7 @@ O `orq setup` mostra o que encontrou (git, terminal, como a memória é limitada
73
73
  do orq em `~/.claude/skills/` e em cada pasta de conta do `claude-accounts` (uma junction no Windows; nunca por cima
74
74
  de uma pasta sua), pergunta sobre seus agentes e planos, escreve o `~/.config/orq/config.toml`
75
75
  (`%APPDATA%\orq\config.toml` no Windows) e prepara a pasta que agrupa seus repos para o conductor: `.orquestra/`, o
76
- mapa `CLAUDE.md` e o playbook do `/issue` no formato do conductor. Partes avulsas: `orq setup --install` (sem
76
+ mapa `ORQ.md` e o playbook do `/issue` no formato do conductor. Partes avulsas: `orq setup --install` (sem
77
77
  perguntas), `orq setup --workspace <pasta> --conductor <agente>`. Dentro do Claude, o **`/orq-setup`** faz o mesmo
78
78
  como conversa e ainda preenche a tabela de repos. Plane: [docs/pt-BR/plane.md](docs/pt-BR/plane.md).
79
79
 
@@ -175,7 +175,7 @@ issue_pattern = "(?<![a-z0-9])(?<issue>[a-z][a-z0-9]*-\\d+)(?![a-z0-9])" # Nom
175
175
  quota_week = 95 # ≥ 95% of the week → agent out
176
176
  quota_5h = { simple = 90, medium = 85, heavy = 80 } # same for the 5h window, by task level
177
177
  pacing_penalty = 0.3 # score penalty when usage at the current pace would pass the pick limit (5h by level, week: quota_week) by the reset (0 = off)
178
- # [agents.claude] headroom = 25 # points kept for conductors: children leave the account at limit − 25
178
+ # [conductor] headroom = 25 # pontos reservados para o conductor ativo: filhos param em limite − 25
179
179
  [policy.weights]
180
180
  quality = 0.40 # did it deliver? (impl: approved on the first review)
181
181
  tokens = 0.45 # did it spend little?
@@ -190,7 +190,7 @@ rotation = 0.20 # penalty for having been picked recent
190
190
  desempate, a menos usada.
191
191
  2. **Pontuação:** `quality × delivery + tokens × economy + speed × quickness − rotation × recent picks`. Os números vêm
192
192
  do histórico (`orq stats`); um agente com poucas execuções usa o `prior` da config e ganha um bônus de exploração.
193
- 3. **Agentes com `reserve = true`** (ex.: Claude, que divide quota com o conductor) só entram quando nada mais serve.
193
+ 3. **Agentes reservados** (`reserve = true`, ou o conductor ativo por padrão) recebem uma penalidade na pontuação para preservar cota para a orquestração.
194
194
 
195
195
  ```bash
196
196
  orq pick review --impl-agent codex --explain # every candidate's score and who was left out, and why
@@ -217,8 +217,37 @@ O que já está rodando continua no idioma em que começou. Os conductors sempre
217
217
 
218
218
  Abra o conductor em qualquer terminal, na pasta do seu workspace (a que o `orq setup` preparou):
219
219
  ```bash
220
- cd ~/Code/<workspace> && orq conductor [conta] [modelo] # qualquer terminal: carrega as instruções do conductor, o CLAUDE.md do workspace e a skill /issue
220
+ cd ~/Code/<workspace> && orq conductor [agente] [conta|-] [modelo] # qualquer terminal: carrega as instruções do conductor, o ORQ.md do workspace e a skill /issue
221
221
  ```
222
+ Escolha o conductor com `orq setup --workspace <pasta> --conductor codex` e rode `orq conductor` nessa pasta.
223
+ `orq conductor codex - <modelo>` troca o agente só nessa sessão; `-r <id>` a retoma. Claude Code também é um
224
+ conductor testado; a sintaxe antiga `orq conductor <conta Claude> <modelo>` continua funcionando. Se uma conta e um
225
+ agente têm o mesmo nome, use `--agent <nome>` para selecionar o agente explicitamente. O wrapper `herdr-conductor` continua abrindo Claude.
226
+
227
+ `ORQ.md` guarda o mapa do workspace. O setup preserva um `CLAUDE.md` existente e copia seu conteúdo para `ORQ.md`
228
+ na migração; se ele não existe, cria um `CLAUDE.md` apontando para o mapa. `AGENTS.md` aponta para o mapa e contém o
229
+ playbook gerado, preservando o texto fora do bloco gerado. Depois da migração, edite `ORQ.md`. Sem comandos em segundo
230
+ plano, o conductor usa `orq wait` em primeiro plano. `orq handoff` continua transferindo apenas contas Claude.
231
+
232
+ Os padrões ficam em `[roles.conductor]` (`agent`, `account`, `model`). Os templates interativos são separados dos
233
+ templates dos filhos, mantendo MCPs e os pedidos normais de permissão. Sobrescreva um agente embutido ou adicione outra CLI:
234
+
235
+ ```toml
236
+ [agents.codex.conductor]
237
+ run = "codex [-m {model}] -C {dir} {prompt_text}"
238
+ resume = "codex resume {session} [-m {model}] -C {dir} {prompt_text}"
239
+ models = { fast = "id-do-seu-modelo" }
240
+ accounts = { work = { CODEX_HOME = "/caminho/codex-work" } }
241
+
242
+ [conductor]
243
+ headroom = 25 # pontos percentuais reservados para o agente que estiver conduzindo
244
+ ```
245
+
246
+ Os templates aceitam `{dir}`, `{instructions}` (caminho do arquivo), `{prompt_text}` (prompt para ler as instruções),
247
+ `{model}`, `{account}`, `{session}` e `{name}`. Contas e `env` são mapas de variáveis de ambiente. O conductor exporta
248
+ `ORQ_CONDUCTOR_AGENT`, então o `orq pick` aplica a penalidade de reserva e `[conductor].headroom` ao agente em uso,
249
+ inclusive após uma troca só nessa sessão. Valores explícitos de `agents.<nome>.reserve` e `headroom` têm precedência.
250
+
222
251
  Com vários conductors, dê a cada um um nome fixo (`export ORQ_CONDUCTOR_ID=<nome>`; no herdr, vale o label do
223
252
  workspace) para o `orq claim` distingui-los: a sessão do Claude se chama `conductor-<workspace>:<nome>`, e o
224
253
  `orq claim -l` termina cada linha com esse `session=`, para os conductors conseguirem se falar.
@@ -283,7 +312,7 @@ Dia a dia, atalhos e várias issues ao mesmo tempo: [docs/pt-BR/herdr.md](docs/p
283
312
  | `orq wait <prefixo>...` | Bloqueia até esses agentes terminarem; imprime `status=` e o arquivo da resposta (qualquer conductor, qualquer terminal) |
284
313
  | `orq verify ...` (`herdr-verify`) | Testes relacionados ao diff, em fila e com limite de memória; falhas que também falham na base contam como pré-existentes |
285
314
  | `orq board` (`herdr-board`) | Painel ao vivo: fila, agentes, conductors, cota, bloqueios, placar |
286
- | `orq conductor [conta] [modelo] [-r <id>]` (`herdr-conductor`) | Abre o conductor (Claude) no terminal atual com as instruções, conta e modelo; `-r` retoma uma sessão |
315
+ | `orq conductor [agente] [conta|-] [modelo] [-r <id>]` (`herdr-conductor`) | Abre o conductor configurado no terminal atual com as instruções, conta e modelo; `-r` retoma uma sessão |
287
316
  | `orq claim` (`herdr-claim`) | Dono de cada issue entre os conductors |
288
317
  | `orq deps` (`herdr-deps`) | Liga as dependências do worktree às do repo principal (sem reinstalar) |
289
318
  | `orq baseline ...` (`herdr-baseline`) | Snapshot manual dos testes que falham na base (fora do fluxo) |
@@ -141,7 +141,9 @@ export async function claim(argv) {
141
141
  const kind = process.env.HERDR_PANE_ID ? "herdr" : "id";
142
142
  const me = process.env.HERDR_PANE_ID || process.env.ORQ_CONDUCTOR_ID || `${userInfo().username}@${hostname()}`;
143
143
  const myName = (kind === "herdr" ? herdrName(me) : "") || process.env.ORQ_CONDUCTOR_ID || "";
144
- const account = basename(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
144
+ const account = process.env.ORQ_CONDUCTOR_AGENT && process.env.ORQ_CONDUCTOR_AGENT !== "claude"
145
+ ? `${process.env.ORQ_CONDUCTOR_AGENT}:${process.env.ORQ_CONDUCTOR_ACCOUNT || "default"}`
146
+ : basename(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
145
147
  const ownerLine = () => `pane=${me} kind=${kind} account=${account} at=${new Date().toISOString()}`
146
148
  + (myName ? ` name=${myName}` : "") + "\n";
147
149
  if (mode === "register") {
@@ -11,12 +11,13 @@ const DESC = {
11
11
  quota: "quota per agent/account", pick: "pick agent, model and account for a role", stats: "scoreboard per agent", rounds: "rounds, time and repeated findings of an issue",
12
12
  board: "live board", lock: "test queue", run: "run a command under a memory cap", agent: "run a child agent",
13
13
  wait: "wait for child agents", verify: "tests of a diff", baseline: "tests already failing on the base",
14
- instructions: "write the /issue playbook", conductor: "open the conductor (Claude) in this terminal", claim: "issue owner among conductors", deps: "link node_modules into a worktree", gc: "remove merged issue worktrees", ci: "CI/CD status per claimed issue", handoff: "move a conductor low on quota to another account",
14
+ instructions: "write the /issue playbook", conductor: "open the configured conductor in this terminal", claim: "issue owner among conductors", deps: "link node_modules into a worktree", gc: "remove merged issue worktrees", ci: "CI/CD status per claimed issue", handoff: "move a conductor low on quota to another account",
15
15
  block: "block an agent or account", completion: "shell completion script",
16
16
  };
17
17
  /** Fixed words after a subcommand. */
18
18
  export const WORDS = {
19
- setup: ["--preset", "--detect", "--answers", "--write", "--install", "--workspace"],
19
+ setup: ["--preset", "--detect", "--answers", "--write", "--install", "--workspace", "--conductor"],
20
+ conductor: ["claude", "codex", "opencode", "antigravity", "copilot", "--agent", "-r", "--help"],
20
21
  config: ["get", "files", "-w"], lang: ["pt", "en", "auto", "toggle"], lock: ["wait", "release", "run", "status"],
21
22
  pick: ["impl", "fix", "worker", "review", "opinion", "watchdog"], instructions: ["claude", "agents"], board: ["--once", "--json", "-w"],
22
23
  completion: ["bash", "zsh", "fish", "powershell"], stats: ["--days", "--json", "-w"], wait: ["--any", "--timeout", "--answer"],
@@ -1,32 +1,62 @@
1
- // orq conductor — opens the conductor (Claude Code) in the current terminal, with the workspace's conductor
1
+ // orq conductor — opens the configured agent in the current terminal, with the workspace's conductor
2
2
  // instructions, the chosen account and model (port of setup/herdr-conductor, for any terminal and OS).
3
- // orq conductor [account|-] [model] [-r <session>]
3
+ // orq conductor [agent] [account|-] [model] [-r <session>]
4
4
  // Workspace: the current folder if it has .orquestra-conductor.md, else $CONDUCTOR_WS. $CONDUCTOR_FILE overrides the
5
5
  // instructions file. The session is named conductor-<workspace folder>:<herdr label or ORQ_CONDUCTOR_ID> (see sessionName).
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { existsSync } from "node:fs";
8
8
  import { join, resolve } from "node:path";
9
9
  import { claudeAccounts } from "../accounts.js";
10
+ import { ALIASES, expandTemplate, expandString, onPath } from "../agents.js";
11
+ import { getPath, loadConfig } from "../config.js";
10
12
  import { m } from "../i18n.js";
11
13
  import { runCapped } from "../process.js";
12
14
  import { claim, herdrName, sessionName } from "./claim.js";
13
- export const USAGE = `orq conductor [account|-] [model] [-r <session>] open the conductor (Claude) here: instructions, account, model
14
- model: sonnet | opus | haiku | a full id; "-" as account = default account; -r resumes that conversation`;
15
+ import { instructions } from "./instructions.js";
16
+ export const USAGE = `orq conductor [agent] [account|-] [model] [-r <session>] open the configured conductor here
17
+ defaults: roles.conductor.agent/account/model; without config: claude
18
+ legacy: orq conductor <Claude account|-> [model]; -r resumes that conversation
19
+ --agent <name> disambiguates an agent from a Claude account with the same name
20
+ templates: agents.<agent>.conductor.run/resume/models/accounts/env`;
15
21
  export const MODELS = {
16
22
  sonnet: "claude-sonnet-5",
17
23
  opus: "claude-opus-5-5",
18
24
  haiku: "claude-haiku-4-5-20251001",
19
25
  };
20
26
  export const modelId = (alias) => MODELS[alias] ?? alias;
21
- /** [account|-] [model] [-r|--resume <id>]; further positionals are ignored (as herdr-conductor did). */
22
- export function parseArgs(argv) {
27
+ /** Interactive launch templates retain the conductor's MCPs and normal permission prompts. */
28
+ export const CONDUCTORS = {
29
+ claude: {
30
+ run: "claude -n {name} [--model {model}] --append-system-prompt-file {instructions}",
31
+ resume: "claude --resume {session} -n {name} [--model {model}] --append-system-prompt-file {instructions}",
32
+ models: MODELS,
33
+ },
34
+ codex: { run: "codex [-m {model}] -C {dir} {prompt_text}",
35
+ resume: "codex resume {session} [-m {model}] -C {dir} {prompt_text}" },
36
+ opencode: { run: "opencode [-m {model}] --prompt {prompt_text} {dir}",
37
+ resume: "opencode -s {session} [-m {model}] --prompt {prompt_text} {dir}" },
38
+ copilot: { run: "copilot -C {dir} [--model {model}] -i {prompt_text}",
39
+ resume: "copilot -C {dir} --resume={session} [--model {model}] -i {prompt_text}" },
40
+ antigravity: { run: "agy [--model {model}] -i {prompt_text}",
41
+ resume: "agy --conversation {session} [--model {model}] -i {prompt_text}" },
42
+ };
43
+ /** Optional agent, then [account|-] [model] [-r|--resume <id>]; old Claude positionals still parse. */
44
+ export function parseArgs(argv, names = Object.keys(CONDUCTORS), accounts = []) {
23
45
  const out = { account: "", model: "", resume: "", help: false };
24
46
  const pos = [];
25
47
  for (let i = 0; i < argv.length; i++) {
26
48
  const a = argv[i];
27
- if (a === "-r" || a === "--resume") {
49
+ if (a === "--agent") {
28
50
  const v = argv[++i];
29
- if (!v) {
51
+ if (!v || v.startsWith("-")) {
52
+ out.error = USAGE;
53
+ return out;
54
+ }
55
+ out.agent = ALIASES[v] ?? v;
56
+ }
57
+ else if (a === "-r" || a === "--resume") {
58
+ const v = argv[++i];
59
+ if (!v || v.startsWith("-")) {
30
60
  out.error = m("give the session id", "informe o id da sessão");
31
61
  return out;
32
62
  }
@@ -37,9 +67,53 @@ export function parseArgs(argv) {
37
67
  else
38
68
  pos.push(a);
39
69
  }
70
+ if (!out.agent && pos.length === 2 && pos[1] !== "-" && accounts.includes(pos[0])) {
71
+ out.agent = "claude";
72
+ }
73
+ else if (!out.agent && pos.length && names.includes(ALIASES[pos[0]] ?? pos[0])) {
74
+ const agent = pos.shift();
75
+ out.agent = ALIASES[agent] ?? agent;
76
+ }
40
77
  [out.account = "", out.model = ""] = pos;
78
+ if (pos.length > 2)
79
+ out.error = USAGE;
41
80
  return out;
42
81
  }
82
+ /** Resolve a conductor without reusing worker templates (which disable MCPs and permissions). */
83
+ export function launch(cfg, a, ws, file, name = "") {
84
+ const agent = a.agent ?? String(getPath(cfg, "roles.conductor.agent") ?? "claude");
85
+ const user = (getPath(cfg, `agents.${agent}.conductor`) ?? {});
86
+ const base = CONDUCTORS[agent];
87
+ const def = { ...base, ...user, models: { ...base?.models, ...user.models } };
88
+ const template = a.resume ? def.resume : def.run;
89
+ if (!template)
90
+ throw new Error(`orq conductor: ${agent} needs agents.${agent}.conductor.${a.resume ? "resume" : "run"}`);
91
+ const same = !a.agent || a.agent === getPath(cfg, "roles.conductor.agent");
92
+ const account = a.account || (same ? String(getPath(cfg, "roles.conductor.account") ?? "") : "");
93
+ const model = a.model || (same ? String(getPath(cfg, "roles.conductor.model") ?? "") : "");
94
+ const vars = { dir: ws, instructions: file, name: sessionName(ws, name), session: a.resume,
95
+ account: account === "-" ? "" : account, model: def.models[model] ?? model,
96
+ prompt_text: `Read ${JSON.stringify(file)} for your conductor instructions and follow them. Read AGENTS.md for the issue playbook. Read ORQ.md for the workspace map; in an older workspace without ORQ.md, read CLAUDE.md.` };
97
+ const env = { ...process.env };
98
+ for (const [k, v] of Object.entries(def.env ?? {}))
99
+ env[k] = expandString(v, vars);
100
+ if (account && account !== "-") {
101
+ const custom = def.accounts?.[account];
102
+ if (custom)
103
+ Object.assign(env, custom);
104
+ else if (agent === "claude") {
105
+ const accounts = claudeAccounts(), found = accounts.find((x) => x.name === account);
106
+ if (!found)
107
+ throw new Error(`account '${account}' does not exist. Accounts: ${accounts.map((x) => `${x.name} ${x.dir}`).join(", ")}`);
108
+ env.CLAUDE_CONFIG_DIR = found.dir;
109
+ }
110
+ else
111
+ throw new Error(`orq conductor: account '${account}' needs agents.${agent}.conductor.accounts.${account}`);
112
+ }
113
+ env.ORQ_CONDUCTOR_AGENT = agent;
114
+ env.ORQ_CONDUCTOR_ACCOUNT = account && account !== "-" ? account : "default";
115
+ return { agent, argv: expandTemplate(template, vars), env };
116
+ }
43
117
  /** The workspace folder: cwd when it holds .orquestra-conductor.md, else $CONDUCTOR_WS (resolved); undefined if neither. */
44
118
  export function findWorkspace(cwd, env, exists = existsSync) {
45
119
  if (exists(join(cwd, ".orquestra-conductor.md")))
@@ -53,7 +127,13 @@ export function claudeArgs(ws, a, file, name = "") {
53
127
  ...(a.model ? ["--model", modelId(a.model)] : []), "--append-system-prompt-file", file];
54
128
  }
55
129
  export async function conductor(argv) {
56
- const a = parseArgs(argv);
130
+ if (argv.includes("--help") || argv.includes("-h")) {
131
+ console.log(USAGE);
132
+ return 0;
133
+ }
134
+ const ws = findWorkspace(process.cwd(), process.env);
135
+ const { cfg } = loadConfig(ws);
136
+ const a = parseArgs(argv, [...Object.keys(CONDUCTORS), ...Object.keys((cfg.agents ?? {}))], claudeAccounts().map((a) => a.name));
57
137
  if (a.help) {
58
138
  console.log(USAGE);
59
139
  return 0;
@@ -62,7 +142,6 @@ export async function conductor(argv) {
62
142
  console.error(a.error);
63
143
  return 2;
64
144
  }
65
- const ws = findWorkspace(process.cwd(), process.env);
66
145
  if (!ws) {
67
146
  console.error(m("no .orquestra-conductor.md here: cd into a workspace (orq setup --workspace <dir>) or set CONDUCTOR_WS", "não há .orquestra-conductor.md aqui: entre num workspace (orq setup --workspace <pasta>) ou defina CONDUCTOR_WS"));
68
147
  return 2;
@@ -72,34 +151,45 @@ export async function conductor(argv) {
72
151
  console.error(`${m("conductor instructions not found", "instruções do conductor não encontradas")}: ${file}`);
73
152
  return 2;
74
153
  }
75
- const env = { ...process.env };
76
- if (a.account && a.account !== "-") {
77
- const accounts = claudeAccounts(), found = accounts.find((x) => x.name === a.account);
78
- if (!found) {
79
- console.error(m(`account '${a.account}' does not exist. Accounts:`, `a conta '${a.account}' não existe. Contas:`));
80
- for (const x of accounts)
81
- console.error(`${x.name} ${x.dir}`);
82
- return 1;
83
- }
84
- env.CLAUDE_CONFIG_DIR = found.dir;
154
+ const name = (process.env.HERDR_PANE_ID ? herdrName(process.env.HERDR_PANE_ID) : "") || process.env.ORQ_CONDUCTOR_ID || "";
155
+ let command;
156
+ try {
157
+ command = launch(cfg, a, ws, file, name);
158
+ }
159
+ catch (e) {
160
+ console.error(e.message);
161
+ return 1;
85
162
  }
163
+ const { env } = command;
164
+ if (!command.argv.length || !onPath(command.argv[0])) {
165
+ console.error(`orq conductor: executable not found: ${command.argv[0] ?? "(empty template)"}`);
166
+ return 1;
167
+ }
168
+ if (command.agent !== "claude")
169
+ await instructions(["agents", "-w", ws]);
86
170
  if (process.env.HERDR_ENV === "1" && process.env.HERDR_PANE_ID)
87
171
  spawnSync("herdr", ["pane", "rename", process.env.HERDR_PANE_ID, "conductor"], { stdio: "ignore", timeout: 5_000 });
88
172
  process.chdir(ws);
89
173
  // Registration (.orquestra/.conductors) is a herdr thing (keyed by pane id; `claim --register` does nothing elsewhere),
90
174
  // so only inside herdr. claim reads the account from this process's env (best effort, silent).
91
- const saved = process.env.CLAUDE_CONFIG_DIR;
92
- if (env.CLAUDE_CONFIG_DIR)
93
- process.env.CLAUDE_CONFIG_DIR = env.CLAUDE_CONFIG_DIR;
94
- if (process.env.HERDR_PANE_ID)
95
- try {
96
- await claim(["-w", ws, "--register"]);
175
+ const keys = ["CLAUDE_CONFIG_DIR", "ORQ_CONDUCTOR_AGENT", "ORQ_CONDUCTOR_ACCOUNT"];
176
+ const saved = keys.map((k) => process.env[k]);
177
+ try {
178
+ for (const k of keys) {
179
+ if (env[k] === undefined)
180
+ delete process.env[k];
181
+ else
182
+ process.env[k] = env[k];
97
183
  }
98
- catch { /* best effort */ }
99
- if (saved === undefined)
100
- delete process.env.CLAUDE_CONFIG_DIR;
101
- else
102
- process.env.CLAUDE_CONFIG_DIR = saved;
103
- const name = (process.env.HERDR_PANE_ID ? herdrName(process.env.HERDR_PANE_ID) : "") || process.env.ORQ_CONDUCTOR_ID || "";
104
- return runCapped("claude", claudeArgs(ws, a, file, name), { mode: "off", env, cwd: ws, quote: true, tty: true });
184
+ if (process.env.HERDR_PANE_ID)
185
+ await claim(["-w", ws, "--register"]);
186
+ }
187
+ catch { /* best effort */ }
188
+ finally {
189
+ keys.forEach((k, i) => { if (saved[i] === undefined)
190
+ delete process.env[k];
191
+ else
192
+ process.env[k] = saved[i]; });
193
+ }
194
+ return runCapped(command.argv[0], command.argv.slice(1), { mode: "off", env, cwd: ws, quote: true, tty: true });
105
195
  }
@@ -7,6 +7,7 @@ import { m } from "../i18n.js";
7
7
  import { issueFromName, issuePattern, issuesWithLiveAgent } from "../issue.js";
8
8
  import { alive } from "../lock.js";
9
9
  import { stateDir } from "../paths.js";
10
+ import { describe, stopWorktreeProcesses, worktreeProcesses } from "../worktree-processes.js";
10
11
  import { aliveOwner } from "./claim.js";
11
12
  export const USAGE = "orq gc [-w <workspace>] [--yes] [--days N] [--json] remove merged issue worktrees and stale agent state";
12
13
  const git = (cwd, ...args) => spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8" });
@@ -168,20 +169,24 @@ export async function gc(argv, clock = Date.now) {
168
169
  if (ghUnavailable)
169
170
  console.error(m("NOTE: gh unavailable or failed; PR signals were skipped", "NOTA: gh indisponível ou falhou; sinais de PR ignorados"));
170
171
  let failed = false;
171
- if (yes)
172
- for (const row of rows.filter((r) => r.verdict === "REMOVABLE")) {
173
- const removed = git(join(ws, row.repo), "worktree", "remove", row.path);
174
- if (removed.status !== 0) {
175
- failed = true;
176
- row.reason += `; ${m("worktree removal failed", "falha ao remover worktree")}`;
177
- continue;
178
- }
179
- if (git(join(ws, row.repo), "branch", "-D", row.branch).status !== 0) {
180
- failed = true;
181
- row.reason += `; ${m("branch deletion failed", "falha ao excluir branch")}`;
182
- }
183
- git(join(ws, row.repo), "worktree", "prune");
172
+ for (const row of rows.filter((r) => r.verdict === "REMOVABLE")) {
173
+ const pids = yes ? stopWorktreeProcesses(row.path) : worktreeProcesses(row.path).map(describe);
174
+ if (pids.length)
175
+ row.reason += `; ${yes ? m("stopped processes", "processos encerrados") : m("processes to stop", "processos a encerrar")}: ${pids.join(", ")}`;
176
+ if (!yes)
177
+ continue;
178
+ const removed = git(join(ws, row.repo), "worktree", "remove", row.path);
179
+ if (removed.status !== 0) {
180
+ failed = true;
181
+ row.reason += `; ${m("worktree removal failed", "falha ao remover worktree")}`;
182
+ continue;
184
183
  }
184
+ if (git(join(ws, row.repo), "branch", "-D", row.branch).status !== 0) {
185
+ failed = true;
186
+ row.reason += `; ${m("branch deletion failed", "falha ao excluir branch")}`;
187
+ }
188
+ git(join(ws, row.repo), "worktree", "prune");
189
+ }
185
190
  const stale = staleAgentStates(ws, days, clock());
186
191
  if (yes)
187
192
  for (const f of stale)
@@ -2,23 +2,28 @@
2
2
  // claude <workspace>/.claude/skills/issue/SKILL.md (Claude Code loads it as the /issue skill)
3
3
  // agents <workspace>/AGENTS.md (Codex, OpenCode and others that read AGENTS.md)
4
4
  // AGENTS.md may hold the user's own text: orq only replaces its block between the orq:issue markers.
5
- // orq instructions claude|agents [-w <workspace>] [--print]
5
+ // orq instructions claude|agents [-w <workspace>] [--conductor <agent>] [--print]
6
+ // orq instructions strategy [-w <workspace>] [--conductor <agent>] [--json] which wait/resume strategy applies (#119)
6
7
  import { existsSync, readFileSync } from "node:fs";
7
8
  import { join, resolve } from "node:path";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { writeAtomic } from "../fsutil.js";
11
+ import { getPath, loadConfig } from "../config.js";
12
+ import { capabilities, loopTimeout, strategy, strategyText } from "../harness.js";
10
13
  import { m } from "../i18n.js";
11
- export const USAGE = "orq instructions claude|agents [-w <workspace>] [--print] write the /issue playbook for the conductor";
14
+ export const USAGE = `orq instructions claude|agents [-w <workspace>] [--conductor <agent>] [--print] write the /issue playbook for the conductor
15
+ orq instructions strategy [-w <workspace>] [--conductor <agent>] [--json] show the conductor's wait/resume strategy`;
12
16
  const SRC = fileURLToPath(new URL("../../skills/issue/SKILL.md", import.meta.url));
13
17
  const BEGIN = "<!-- orq:issue begin (generated by `orq instructions agents`; edits inside are overwritten) -->";
14
18
  const END = "<!-- orq:issue end -->";
15
- /** The playbook for harnesses without skills: no frontmatter, a trigger line, and how to wait without background tasks. */
16
- export function agentsBlock(skill) {
19
+ /** The playbook for harnesses without skills: no frontmatter, a trigger line, and the conductor's waiting strategy. */
20
+ export function agentsBlock(skill, waiting = strategyText("unknown", capabilities("unknown"))) {
17
21
  const body = skill.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim();
18
22
  return `${BEGIN}
19
23
  When the user says "/issue <ID>", "take issue X" or "run ABC-123", follow this playbook.
20
- If your harness cannot run a command in the background, wait for children with \`orq wait\` in the foreground
21
- (\`orq wait --any <prefix>...\`): it is the one allowed blocking wait.
24
+ Read \`ORQ.md\` for the workspace map and orchestration settings (legacy workspaces: \`CLAUDE.md\`).
25
+
26
+ ${waiting}
22
27
 
23
28
  ${body}
24
29
  ${END}
@@ -31,15 +36,30 @@ export function mergeBlock(cur, block) {
31
36
  return cur.slice(0, a) + block.trimEnd() + cur.slice(b + END.length);
32
37
  return cur ? `${cur.trimEnd()}\n\n${block}` : block;
33
38
  }
39
+ /** Conductor agent: --conductor, else roles.conductor.agent, else claude. */
40
+ function conductorOf(cfg, flag) {
41
+ return flag || String(getPath(cfg, "roles.conductor.agent") ?? "claude");
42
+ }
43
+ /** AGENTS.md is read by non-Claude harnesses: a Claude conductor's wake-up must not leak into it unless asked for. */
44
+ function agentsWaiting(agent, caps, cfg, flag) {
45
+ if (agent === "claude" && !flag)
46
+ return strategyText("AGENTS.md harness", capabilities("unknown", cfg, process.env));
47
+ return strategyText(agent, caps);
48
+ }
34
49
  export async function instructions(argv) {
35
- let ws = process.cwd(), print = false, fmt;
50
+ let ws = process.cwd(), print = false, json = false, fmt, who;
36
51
  for (let i = 0; i < argv.length; i++) {
37
- if (argv[i] === "-w")
52
+ const a = argv[i];
53
+ if (a === "-w")
38
54
  ws = resolve(argv[++i]);
39
- else if (argv[i] === "--print")
55
+ else if (a === "--print")
40
56
  print = true;
41
- else if (argv[i] === "claude" || argv[i] === "agents")
42
- fmt = argv[i];
57
+ else if (a === "--json")
58
+ json = true;
59
+ else if (a === "--conductor" && argv[i + 1])
60
+ who = argv[++i];
61
+ else if (a === "claude" || a === "agents" || a === "strategy")
62
+ fmt = a;
43
63
  else {
44
64
  console.error(USAGE);
45
65
  return 2;
@@ -49,13 +69,35 @@ export async function instructions(argv) {
49
69
  console.error(USAGE);
50
70
  return 2;
51
71
  }
72
+ let cfg = {};
73
+ try {
74
+ cfg = loadConfig(ws).cfg;
75
+ }
76
+ catch { /* no config: built-in defaults */ }
77
+ const agent = conductorOf(cfg, who), caps = capabilities(agent, cfg, process.env), s = strategy(caps);
78
+ if (fmt === "strategy") {
79
+ const fallback = s === "background-task" ? "foreground-loop" : "none";
80
+ if (json) {
81
+ console.log(JSON.stringify({ conductor: agent, strategy: s, fallback, waitTimeout: loopTimeout(caps), capabilities: caps }));
82
+ return 0;
83
+ }
84
+ console.log(`conductor ${agent}: ${s}`);
85
+ console.log(` background=${caps.background} resume_on_completion=${caps.resumeOnCompletion} foreground_wait=${caps.foregroundWait} prompt_delivery=${caps.promptDelivery} foreground_timeout=${caps.foregroundTimeout}s`);
86
+ console.log(s === "background-task"
87
+ ? m(" ends its turn after dispatch; orq wait in the background wakes it (fallback: foreground loop)", " encerra o turno após o dispatch; o orq wait em background o acorda (fallback: loop em foreground)")
88
+ : s === "manual"
89
+ ? m(" cannot continue by itself: reports pending prefixes and waits for the user's message", " não continua sozinho: informa os prefixos pendentes e espera a mensagem do usuário")
90
+ : m(` stays active: orq wait --any --timeout ${loopTimeout(caps)} in a loop; it does NOT resume by itself after ending a turn`, ` fica ativo: orq wait --any --timeout ${loopTimeout(caps)} em loop; NÃO retoma sozinho depois de encerrar o turno`));
91
+ console.log(m(` override: agents.${agent}.conductor.capabilities`, ` ajuste: agents.${agent}.conductor.capabilities`));
92
+ return 0;
93
+ }
52
94
  if (!existsSync(SRC)) {
53
95
  console.error(m(`playbook not found: ${SRC}`, `playbook não encontrado: ${SRC}`));
54
96
  return 2;
55
97
  }
56
98
  const skill = readFileSync(SRC, "utf8");
57
99
  const file = fmt === "claude" ? join(ws, ".claude", "skills", "issue", "SKILL.md") : join(ws, "AGENTS.md");
58
- const text = fmt === "claude" ? skill : mergeBlock(existsSync(file) ? readFileSync(file, "utf8") : "", agentsBlock(skill));
100
+ const text = fmt === "claude" ? skill : mergeBlock(existsSync(file) ? readFileSync(file, "utf8") : "", agentsBlock(skill, agentsWaiting(agent, caps, cfg, who)));
59
101
  if (print) {
60
102
  process.stdout.write(text);
61
103
  return 0;
@@ -75,6 +75,8 @@ export async function pick(argv) {
75
75
  }
76
76
  }
77
77
  const { cfg } = loadConfig(ws);
78
+ if (process.env.ORQ_CONDUCTOR_AGENT)
79
+ cfg._conductorAgent = process.env.ORQ_CONDUCTOR_AGENT;
78
80
  const agents = (cfg.agents ?? {});
79
81
  if (implAgent && !implVendor)
80
82
  implVendor = vendorOf(implAgent, agents[implAgent]);
@@ -4,7 +4,7 @@
4
4
  // orq setup --detect installed agent CLIs, as JSON (for a conductor driving the setup)
5
5
  // orq setup --answers <file|-> [--write] proposal from answers JSON (see Answers in src/suggest.ts); --write saves it
6
6
  // orq setup --install no questions: what orq found, and the skills linked for Claude Code
7
- // orq setup --workspace <dir> [--conductor <agent>] .orquestra, CLAUDE.md map, /issue playbook in its format
7
+ // orq setup --workspace <dir> [--conductor <agent>] .orquestra, ORQ.md map, /issue playbook in its format
8
8
  // --preset economy|quality|single-agent
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { copyFileSync, existsSync, readFileSync } from "node:fs";
@@ -18,6 +18,7 @@ import { getPath, loadConfig } from "../config.js";
18
18
  import { m } from "../i18n.js";
19
19
  import { environment, installSkills, prepareWorkspace } from "../install.js";
20
20
  import { expand, userConfigFile } from "../paths.js";
21
+ import { setTomlString } from "../toml-set.js";
21
22
  import { suggestRoles, toToml } from "../suggest.js";
22
23
  export const USAGE = `orq setup [--preset economy|quality|single-agent] guided first configuration (roles, weights, memory)
23
24
  orq setup --detect | --answers <file|-> [--write] | --install | --workspace <dir> [--conductor <agent>]`;
@@ -125,18 +126,30 @@ function workspace(dir, conductor) {
125
126
  let c = conductor;
126
127
  if (!c) {
127
128
  try {
128
- c = getPath(loadConfig().cfg, "roles.conductor.agent");
129
+ c = getPath(loadConfig(expand(dir)).cfg, "roles.conductor.agent");
129
130
  }
130
131
  catch { /* none */ }
131
132
  }
132
- c ??= onPath("claude") ? "claude" : "agents";
133
+ c ??= detect()[0] ?? "claude";
133
134
  const d = expand(dir); // typed at the prompt: ~ is not expanded by any shell
134
135
  console.log(`Workspace ${d}`);
135
- for (const l of prepareWorkspace(d, c === "claude" ? "claude" : "agents"))
136
+ for (const l of prepareWorkspace(d, c === "claude" ? "claude" : "agents", undefined, c))
136
137
  console.log(l);
138
+ if (c !== "agents") {
139
+ const toml = join(d, ".orq", "workspace.toml"), json = join(d, ".orq", "workspace.json");
140
+ const file = existsSync(toml) || !existsSync(json) ? toml : json;
141
+ const text = existsSync(file) ? readFileSync(file, "utf8") : "";
142
+ if (file.endsWith(".json")) {
143
+ const cfg = text ? JSON.parse(text) : {};
144
+ cfg.roles = { ...cfg.roles, conductor: { ...cfg.roles?.conductor, agent: c } };
145
+ writeAtomic(file, JSON.stringify(cfg, null, 2) + "\n");
146
+ }
147
+ else
148
+ writeAtomic(file, setTomlString(text, "roles.conductor", "agent", c));
149
+ }
137
150
  }
138
- const next = () => console.log(m(`Next: fill in the workspace CLAUDE.md, open your conductor in that folder and say "/issue <ID>".
139
- Board: orq board · quota: orq quota · completion: orq completion <shell>`, `Próximo: preencha o CLAUDE.md do workspace, abra o seu conductor nessa pasta e diga "/issue <ID>".
151
+ const next = () => console.log(m(`Next: fill in the workspace ORQ.md, open your conductor in that folder and say "/issue <ID>".
152
+ Board: orq board · quota: orq quota · completion: orq completion <shell>`, `Próximo: preencha o ORQ.md do workspace, abra o seu conductor nessa pasta e diga "/issue <ID>".
140
153
  Painel: orq board · cota: orq quota · autocompletar: orq completion <shell>`));
141
154
  export async function setup(argv) {
142
155
  let preset, answers, doWrite = false, ws, conductor;
@@ -0,0 +1,69 @@
1
+ // Conductor harness capabilities and the wait/resume strategy they allow (#119).
2
+ // A harness is where the conductor talks to the user (Claude Code, Codex, OpenCode...). Being able to start a process in
3
+ // the background is not the same as being woken up when it ends: only `resume_on_completion` lets the conductor end its
4
+ // turn after dispatching. Everything else keeps an active, bounded `orq wait --timeout` loop.
5
+ // Config (any field optional, overrides the built-in): agents.<agent>.conductor.capabilities =
6
+ // { background, resume_on_completion, foreground_wait, foreground_timeout, prompt_delivery }
7
+ import { getPath } from "./config.js";
8
+ const LOOP = { background: false, resumeOnCompletion: false, foregroundWait: true, foregroundTimeout: 240, promptDelivery: false };
9
+ /** Built-in knowledge. Unknown harnesses get the conservative loop: never promise a wake-up nobody verified. */
10
+ export const BUILTIN_CAPABILITIES = {
11
+ claude: { background: true, resumeOnCompletion: true, foregroundWait: true, foregroundTimeout: 540, promptDelivery: false },
12
+ codex: { ...LOOP },
13
+ opencode: { ...LOOP },
14
+ copilot: { ...LOOP },
15
+ antigravity: { ...LOOP },
16
+ };
17
+ const bool = (v, d) => typeof v === "boolean" ? v : d;
18
+ /** Capabilities of `agent` as conductor: built-in, then agents.<agent>.conductor.capabilities, then the environment. */
19
+ export function capabilities(agent, cfg = {}, env = {}) {
20
+ const base = BUILTIN_CAPABILITIES[agent] ?? LOOP;
21
+ const u = (getPath(cfg, `agents.${agent}.conductor.capabilities`) ?? {});
22
+ const t = Number(u.foreground_timeout);
23
+ return {
24
+ background: bool(u.background, base.background),
25
+ resumeOnCompletion: bool(u.resume_on_completion, base.resumeOnCompletion),
26
+ foregroundWait: bool(u.foreground_wait, base.foregroundWait),
27
+ foregroundTimeout: Number.isFinite(t) && t > 0 ? Math.floor(t) : base.foregroundTimeout,
28
+ promptDelivery: bool(u.prompt_delivery, base.promptDelivery || env.HERDR_ENV === "1"),
29
+ };
30
+ }
31
+ /** Ending the turn after dispatch is only allowed with a channel that wakes the conductor. */
32
+ export function strategy(c) {
33
+ if (c.background && c.resumeOnCompletion)
34
+ return "background-task";
35
+ return c.foregroundWait ? "foreground-loop" : "manual";
36
+ }
37
+ /** Timeout for `orq wait` inside one foreground call: leave a margin under the harness limit. */
38
+ export const loopTimeout = (c) => Math.max(30, c.foregroundTimeout - 30);
39
+ /** The "Waiting strategy" section embedded in the generated conductor instructions. */
40
+ export function strategyText(agent, c) {
41
+ const herdr = "In herdr (`HERDR_ENV=1`) the done line is typed into your pane by herdr: that is your wake-up; dispatch, report in one line and end your turn.";
42
+ if (strategy(c) === "background-task")
43
+ return `## Waiting strategy: background task (${agent})
44
+ ${herdr}
45
+ Elsewhere: after dispatching, run \`orq wait --any <prefix>...\` as a **background task** of your harness and end your
46
+ turn. Its exit starts your next turn; its output is the done line. Wait again for the prefixes still pending.`;
47
+ if (strategy(c) === "manual")
48
+ return `## Waiting strategy: manual (${agent})
49
+ ${herdr}
50
+ Elsewhere your harness can neither wake you when a child ends nor keep a foreground wait open, so you cannot continue
51
+ by yourself. After dispatching, tell the user plainly: which prefixes are pending, that you will **not** continue on
52
+ your own, and that they resume you by messaging you (e.g. "status") once \`orq wait --any <pending prefixes>\` has
53
+ returned. On that message, check \`<prefix>.done\` and act; never dispatch a child again just because it is still running.`;
54
+ const t = loopTimeout(c);
55
+ return `## Waiting strategy: foreground loop (${agent})
56
+ ${herdr}
57
+ Elsewhere your harness does **not** resume you when a background process ends, so never end your turn expecting a
58
+ wake-up. After dispatching, report in one line, then loop:
59
+ 1. Run \`orq wait --any --timeout ${t} <every pending prefix>\` in the **foreground** (if your shell tool has a timeout
60
+ setting, give it more than ${t} s; if the tool still kills the command, use a shorter \`--timeout\`). If the tool
61
+ returns a running session/process id instead of an exit code, keep polling **that same** session until it exits;
62
+ don't start another \`orq wait\` while it runs.
63
+ 2. Exit 0/1 = a done line: act on it (verify, review, fix) and keep looping over what is still pending.
64
+ 3. Exit 124 = nothing finished yet: give the user a one-line status and go back to 1. A timeout is not a failure:
65
+ never dispatch the same child again because of it (\`<prefix>.done\` missing = still running).
66
+ The loop is the one allowed blocking wait. If the user writes in between, answer, then resume the loop. If you must end
67
+ your turn with children still pending, say plainly that you will **not** continue by yourself and that the user has to
68
+ message you (e.g. "status") to resume.`;
69
+ }
package/dist/install.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // skills orq's skills (skills/*/SKILL.md, shipped in the package) linked into ~/.claude/skills and each Claude
3
3
  // account's skills folder — a junction on Windows (no admin rights), a copy if linking fails. A real
4
4
  // folder, or a link to another live copy (a clone of the repo), is never touched.
5
- // workspace <folder>/.orquestra, .worktrees, CLAUDE.md from the template (only if missing) and the /issue playbook
5
+ // workspace <folder>/.orquestra, .worktrees, ORQ.md map, pointers and the /issue playbook
6
6
  // in the conductor's format (orq instructions)
7
7
  // environment what orq found: git, terminal host, memory cap, keyring, the shell's completion line
8
8
  import { spawnSync } from "node:child_process";
@@ -11,7 +11,9 @@ import { homedir } from "node:os";
11
11
  import { basename, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { claudeAccounts } from "./accounts.js";
14
+ import { loadConfig } from "./config.js";
14
15
  import { writeAtomic } from "./fsutil.js";
16
+ import { capabilities, strategyText } from "./harness.js";
15
17
  import { m } from "./i18n.js";
16
18
  import { backend } from "./keyring.js";
17
19
  import { hasKernelCap } from "./process.js";
@@ -77,25 +79,39 @@ export function installSkills(roots = skillRoots()) {
77
79
  return out;
78
80
  }
79
81
  /** Prepare a conductor's workspace folder; `fmt` = claude (SKILL.md) | agents (AGENTS.md). */
80
- export function prepareWorkspace(dir, fmt, name = basename(resolve(dir))) {
82
+ export function prepareWorkspace(dir, fmt, name = basename(resolve(dir)), conductor = fmt === "claude" ? "claude" : "unknown") {
81
83
  const ws = resolve(dir), out = [];
82
84
  for (const d of [".orquestra", ".worktrees"])
83
85
  mkdirSync(join(ws, d), { recursive: true });
84
- const map = join(ws, "CLAUDE.md");
86
+ const map = join(ws, "ORQ.md"), legacy = join(ws, "CLAUDE.md");
85
87
  if (!existsSync(map)) {
86
- const t = readFileSync(join(PKG, "templates", "workspace.CLAUDE.md"), "utf8");
87
- writeAtomic(map, t.split("{{WORKSPACE}}").join(ws).split("{{NAME}}").join(name));
88
+ const t = existsSync(legacy) ? readFileSync(legacy, "utf8") :
89
+ readFileSync(join(PKG, "templates", "workspace.CLAUDE.md"), "utf8").split("{{WORKSPACE}}").join(ws).split("{{NAME}}").join(name);
90
+ writeAtomic(map, t);
88
91
  out.push(m(` created ${map} — fill in repos, group, base branch, Plane and issue standard`, ` criado ${map} — preencha repos, grupo, branch base, Plane e padrão de issue`));
89
92
  }
90
93
  else
91
94
  out.push(m(` ok ${map} (kept)`, ` ok ${map} (mantido)`));
95
+ if (!existsSync(legacy))
96
+ writeAtomic(legacy, "# orq workspace\n\nRead `ORQ.md` for the workspace map and orchestration settings.\n");
92
97
  // The conductor's instructions (what `orq conductor` looks for): regenerated on every run.
93
98
  const ci = join(ws, ".orquestra-conductor.md");
94
99
  const t = readFileSync(join(PKG, "templates", "conductor.instructions.md"), "utf8");
95
- writeAtomic(ci, t.split("{{WORKSPACE}}").join(ws).split("{{NAME}}").join(name));
100
+ let cfg = {};
101
+ try {
102
+ cfg = loadConfig(ws).cfg;
103
+ }
104
+ catch { /* no config yet */ }
105
+ const waiting = strategyText(conductor, capabilities(conductor, cfg, process.env));
106
+ writeAtomic(ci, t.split("{{WORKSPACE}}").join(ws).split("{{NAME}}").join(name).split("{{WAIT_STRATEGY}}").join(waiting));
96
107
  out.push(` ${m("wrote", "escrevi")} ${ci}`);
97
- const r = spawnSync(process.execPath, [join(PKG, "dist", "cli.js"), "instructions", fmt, "-w", ws], { encoding: "utf8" });
98
- out.push(` ${(r.stdout || r.stderr).trim()}`);
108
+ for (const format of fmt === "claude" ? ["agents", "claude"] : ["agents"]) {
109
+ const r = spawnSync(process.execPath, [join(PKG, "dist", "cli.js"), "instructions", format, "-w", ws,
110
+ ...(format === "agents" && conductor !== "claude" && conductor !== "unknown" ? ["--conductor", conductor] : [])], { encoding: "utf8" });
111
+ if (r.status !== 0)
112
+ throw new Error(r.stderr || `orq instructions ${format} failed`);
113
+ out.push(` ${r.stdout.trim()}`);
114
+ }
99
115
  return out;
100
116
  }
101
117
  const SHELLS = ["bash", "zsh", "fish", "powershell"];
package/dist/pick.js CHANGED
@@ -145,6 +145,8 @@ function takesPrompt(cfg, name) {
145
145
  export function pick(inp) {
146
146
  const { cfg, role, level } = inp;
147
147
  const agents = (cfg.agents ?? {});
148
+ const conductor = String(cfg._conductorAgent ?? cfg.roles?.conductor?.agent ?? "");
149
+ const conductorHeadroom = Number(cfg.conductor?.headroom ?? 0);
148
150
  const pol = (cfg.policy ?? {});
149
151
  const [base5, baseWeek] = limitsFor(pol, level);
150
152
  const w = weights(cfg);
@@ -201,7 +203,7 @@ export function pick(inp) {
201
203
  }
202
204
  // Quota: an agent with several accounts (claude) prefers one not projecting past the limit, then the least used.
203
205
  const qn = a.quota ?? [];
204
- const headroom = Math.max(0, Number(a.headroom ?? 0));
206
+ const headroom = Math.max(0, Number(a.headroom ?? (name === conductor ? conductorHeadroom : 0)));
205
207
  const limit5 = base5 - headroom, limitWeek = baseWeek - headroom;
206
208
  const accs = qn.filter((n) => !inp.blocks.has(n)).map((n) => [n, n in inp.quota ? inp.quota[n] : -1]);
207
209
  if (qn.length && !accs.length) {
@@ -329,7 +331,7 @@ export function pick(inp) {
329
331
  return { v, txt: `${label}=${v.toFixed(2)}` + (obs === undefined ? "" : `(${prior.toFixed(2)}→${obs.toFixed(2)})`) };
330
332
  };
331
333
  const cands = pass.map((c) => {
332
- const prior = Number(c.a.prior ?? 0.6), n = c.st?.n ?? 0, reserve = Boolean(c.a.reserve);
334
+ const prior = Number(c.a.prior ?? 0.6), n = c.st?.n ?? 0, reserve = Boolean(c.a.reserve ?? c.agent === conductor);
333
335
  const appr = role === "impl" && c.st?.approved != null && c.st.n_appr >= 3;
334
336
  const deliv = appr ? c.st.approved / 100 : c.st ? c.st.ok / 100 : undefined;
335
337
  const d = axis(m("delivery", "entrega"), prior, deliv, appr ? c.st.n_appr : n);
package/dist/suggest.js CHANGED
@@ -98,7 +98,7 @@ export function toToml(p) {
98
98
  if (a.quota)
99
99
  out.push(`quota = ${list(a.quota)}`);
100
100
  if (a.reserve)
101
- out.push("reserve = true");
101
+ out.push("# reserve follows roles.conductor.agent (or the running conductor)");
102
102
  out.push("# models.<role>.<level> = \"model[@effort]\" — without models, the CLI's default model is used", "");
103
103
  }
104
104
  for (const [r, v] of Object.entries(p.roles)) {
package/dist/testrun.js CHANGED
@@ -12,6 +12,7 @@ import { acquire, release } from "./lock.js";
12
12
  import { handoffFile, notify } from "./notice.js";
13
13
  import { killTree, parseSize, runCapped } from "./process.js";
14
14
  import { closeOwnTab, detectHost, launch } from "./terminal.js";
15
+ import { stopWorktreeProcesses } from "./worktree-processes.js";
15
16
  export const JS_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|mts|cts)$/;
16
17
  export const git = (dir, ...a) => {
17
18
  const r = spawnSync("git", ["-C", dir, ...a], { encoding: "utf8", maxBuffer: 1 << 28 });
@@ -109,6 +110,7 @@ export function linkModules(from, to) {
109
110
  * (and must never walk into the main clone's node_modules through it).
110
111
  */
111
112
  export function dropWorktree(repo, dir) {
113
+ stopWorktreeProcesses(dir);
112
114
  const nm = join(dir, "node_modules");
113
115
  try {
114
116
  if (lstatSync(nm).isSymbolicLink() || process.platform === "win32")
@@ -0,0 +1,102 @@
1
+ // Stop processes whose current working directory is inside a worktree before git removes its files.
2
+ import { readFileSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ function inside(dir, path) {
5
+ const rel = relative(dir, path);
6
+ return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
7
+ }
8
+ function stat(pid) {
9
+ try {
10
+ return readFileSync(`/proc/${pid}/stat`, "utf8");
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ /** orq itself and every ancestor (the shell or tmux pane that launched it), which must never be stopped. */
17
+ function ownChain() {
18
+ const chain = new Set();
19
+ for (let pid = process.pid; pid > 1 && !chain.has(pid);) {
20
+ chain.add(pid);
21
+ const ppid = Number(/\) \S+ (\d+)/.exec(stat(pid) ?? "")?.[1]);
22
+ if (!ppid)
23
+ break;
24
+ pid = ppid;
25
+ }
26
+ return chain;
27
+ }
28
+ /** Processes whose cwd is under `dir`, excluding orq and its ancestors; other platforms skip this check. */
29
+ export function worktreeProcesses(dir) {
30
+ if (process.platform !== "linux")
31
+ return [];
32
+ let root;
33
+ try {
34
+ root = realpathSync(dir);
35
+ }
36
+ catch {
37
+ root = resolve(dir);
38
+ }
39
+ let entries;
40
+ try {
41
+ entries = readdirSync("/proc");
42
+ }
43
+ catch {
44
+ return [];
45
+ }
46
+ const skip = ownChain(), pids = [];
47
+ for (const entry of entries) {
48
+ if (!/^\d+$/.test(entry))
49
+ continue;
50
+ const pid = Number(entry);
51
+ if (skip.has(pid))
52
+ continue;
53
+ try {
54
+ if (inside(root, resolve(readlinkSync(`/proc/${entry}/cwd`))))
55
+ pids.push(pid);
56
+ }
57
+ catch { /* process exited or inaccessible */ }
58
+ }
59
+ return pids;
60
+ }
61
+ /** `pid (name)` for output, so the user sees what is (or would be) stopped. */
62
+ export function describe(pid) {
63
+ try {
64
+ return `${pid} (${readFileSync(`/proc/${pid}/comm`, "utf8").trim()})`;
65
+ }
66
+ catch {
67
+ return String(pid);
68
+ }
69
+ }
70
+ /** SIGTERM matching processes, wait up to five seconds, then SIGKILL survivors. Returns `describe`d entries. */
71
+ export function stopWorktreeProcesses(dir) {
72
+ const pids = worktreeProcesses(dir), names = pids.map(describe);
73
+ for (const pid of pids)
74
+ try {
75
+ process.kill(pid, "SIGTERM");
76
+ }
77
+ catch { /* already exited */ }
78
+ const deadline = Date.now() + 5_000;
79
+ while (Date.now() < deadline && pids.some(alive))
80
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
81
+ for (const pid of pids)
82
+ if (alive(pid))
83
+ try {
84
+ process.kill(pid, "SIGKILL");
85
+ }
86
+ catch { /* already exited */ }
87
+ return names;
88
+ }
89
+ function alive(pid) {
90
+ if (process.platform === "linux") {
91
+ const s = stat(pid);
92
+ if (s === undefined || /^\d+ \(.*\) Z /.test(s))
93
+ return false;
94
+ }
95
+ try {
96
+ process.kill(pid, 0);
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irythill/orq",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Orchestrate coding agents (Claude Code, Codex, OpenCode, Antigravity, Copilot) from one conductor.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,7 @@ description: Runs a Plane issue end to end by orchestrating coding agents in her
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
- **Ecosystem configuration:** the **workspace** `CLAUDE.md` (the folder named in your conductor instructions) defines
11
+ **Ecosystem configuration:** in the folder named in your conductor instructions, read `ORQ.md` (legacy: `CLAUDE.md`). It defines
12
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.
@@ -17,11 +17,17 @@ herdr (`HERDR_ENV=1`), in tmux windows inside tmux, else in the background (thei
17
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
18
  conductor outside herdr.
19
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
- task** (Claude Code: Bash with `run_in_background`); its exit wakes you and its output is the line
20
+ your pane after each child. Anywhere else no line arrives: `orq wait <prefix>` prints
22
21
  `[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.
22
+ `orq wait --any <prefix1> <prefix2>` (one finish per call; wait again for the rest).
23
+ - **Waiting strategy** (your conductor instructions name it; `orq instructions strategy` shows it). It depends on what
24
+ your harness can do, not on whether it can start a background process:
25
+ - *background task* — only when a finished background task starts a new turn by itself (Claude Code: Bash with
26
+ `run_in_background`): run `orq wait --any ...` that way and end your turn.
27
+ - *foreground loop* — every other harness (Codex, OpenCode, ...): stay active. Run
28
+ `orq wait --any --timeout <s> <pending prefixes>` in the foreground; exit 124 = nothing finished yet → one-line
29
+ status to the user and run it again (never re-dispatch on a timeout). Don't end your turn expecting a wake-up; if
30
+ you must, tell the user you won't continue until they message you.
25
31
  - `orq verify` and `orq baseline` run like children (herdr tab, tmux window or background) and you wait for them the same
26
32
  way, e.g. `orq wait $O/verify-<repo>`.
27
33
  - Peeking at a child: `<prefix>.log` (in herdr also `herdr pane read $(cat <prefix>.pane) --source recent`).
@@ -42,7 +48,7 @@ Issue artifacts (prompts, notes) live in `$WS/.orquestra/<ID>/`.
42
48
  - `mcp__plane__workitem` `retrieve_by_identifier` with `workitem_identifier: "<ID>"`; then `workitem_comment list`.
43
49
  - Extract: goal, **acceptance criteria**, affected repos, links. No acceptance criteria → write a short proposal and
44
50
  **ask the user** before dispatching.
45
- - Issue missing assignee, priority, labels or module → **fill them in** following the workspace `CLAUDE.md` "Issue
51
+ - Issue missing assignee, priority, labels or module → **fill them in** following the workspace `ORQ.md` "Issue
46
52
  standard". The same standard applies to any new issue you create.
47
53
  - Move it to **In Progress** (`workitem update` with the state_id from `state list`).
48
54
 
@@ -93,7 +99,7 @@ Write the prompt to `$WS/.orquestra/<ID>/impl-<repo>.md` (short: goal, acceptanc
93
99
  verification commands, "base: origin/<BASE>; don't rebase/merge other branches; local commits, no push; commit
94
100
  messages following <path of the repo's commit conventions, see step 7>").
95
101
  Always include:
96
- - **Repo checklist:** copy the repo's "Checklist" column from the workspace `CLAUDE.md`.
102
+ - **Repo checklist:** copy the repo's "Checklist" column from the workspace `ORQ.md`.
97
103
  - **Decisions:** "end your final answer with a `## Decisions` section, at most 10 lines: what you chose and what you
98
104
  ruled out, trade-offs, and what you left out on purpose and why". The reviewer reads it (step 5), so a deliberate
99
105
  choice doesn't come back as a blocker.
@@ -102,7 +108,7 @@ Always include:
102
108
  and report; after the rebase repeat lint/tsc". An agent without network (Codex) can't fetch: `orq agent` fetches before
103
109
  the run and adds a note to its prompt, so it only rebases locally.
104
110
 
105
- **Base = the repo's base branch from the workspace `CLAUDE.md`, always from the updated remote (`origin/<BASE>`).**
111
+ **Base = the repo's base branch from the workspace `ORQ.md`, always from the updated remote (`origin/<BASE>`).**
106
112
  Worktree with `git worktree add` (not `herdr worktree create`: that opens a separate herdr workspace per worktree, and
107
113
  children are grouped by the repo's `-g`).
108
114
  ```bash
@@ -139,7 +145,7 @@ orq agent $(cat $O/impl-<repo>.pick) -g <group> -t "<ID> impl <repo>" -d $WT -o
139
145
  - Every child prompt ends with: "Don't try to talk to the conductor or use herdr; finish with your complete final
140
146
  answer (that is what I read)."
141
147
  - **Verification in the child: lint and tsc only, one command at a time** (the cap is 4 GB per agent and jest/vitest
142
- blow it; tests are yours, step 4b): use the Verification column of the workspace `CLAUDE.md` and put in the prompt
148
+ blow it; tests are yours, step 4b): use the Verification column of the workspace `ORQ.md` and put in the prompt
143
149
  "verify sequentially, never in parallel or watch mode: (1) lint only the changed files (`git diff --name-only
144
150
  origin/<BASE>...HEAD`); (2) `NODE_OPTIONS=--max-old-space-size=2048 tsc --noEmit` for the project. Don't run tests".
145
151
  Full suite only in CI (or a manual `orq baseline` if the user asks).
@@ -148,7 +154,7 @@ orq agent $(cat $O/impl-<repo>.pick) -g <group> -t "<ID> impl <repo>" -d $WT -o
148
154
  arriving), run `orq notice` (delivery log) and `<prefix>.done` for that issue, then handle the failure; running
149
155
  `orq wait`, `orq verify` or `orq agent` for the issue acknowledges it.
150
156
  - **Never block waiting** (`herdr agent wait`, `pane wait-output`, `sleep`, `timeout`): completion arrives as a
151
- `[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`
157
+ `[HERDR-AGENT DONE]` line in your pane (herdr) or through `orq wait` per your waiting strategy. Dispatch, report in one line, then end your turn (herdr / background task) or keep looping (foreground loop). "Done yet?" = `<prefix>.done`
152
158
  exists (never `pgrep`).
153
159
  - Investigation only (no code): "READ ONLY" prompt, in the repo itself, no worktree. `orq agent -r` (Claude:
154
160
  `--permission-mode plan`; the others: throwaway worktree, no editing).
@@ -169,7 +175,7 @@ something is red, new or old, and never widen a baseline or allowlist to silence
169
175
  outside the issue's domain: ask the user first (then it ships as its own PR, not as a note).
170
176
  **Mandatory order every round (impl and each fix): child `ok` → verify → review.** Never dispatch the review (or an
171
177
  "early" fix) while this round's verify is pending, even if it is queued behind another verify: wait for
172
- the `... verify ...` done line and end your turn until then.
178
+ the `... verify ...` done line (end your turn or keep looping, per your waiting strategy).
173
179
 
174
180
  ## 5. Cross review (always another vendor)
175
181
  ```bash
@@ -265,7 +271,7 @@ rules live) commits and writes the PR. Find and cite in the prompt, by path, wha
265
271
  high severity, `file:line` and a concrete failure, not already decided in the rounds, goes into a fix round (it
266
272
  counts towards `policy.max_rounds`); the rest goes into the Plane comment or a follow-up issue. Don't re-request the
267
273
  bot's review to "get it clean".
268
- - **CI after every push:** `gh pr checks <n>` (no `--watch`; if pending, end your turn and check on the next notice).
274
+ - **CI after every push:** `gh pr checks <n>` (no `--watch`; if pending, check again on the next notice or loop iteration).
269
275
  Red → `gh run view <id> --log-failed | tail -40`: error from the diff → fix round; error already on the base → create
270
276
  a Plane issue (workspace standard) and tell the user. "Ready to merge" only with green checks or an explained failure
271
277
  + issue.
@@ -279,4 +285,4 @@ rules live) commits and writes the PR. Find and cite in the prompt, by path, wha
279
285
  ## Saving tokens
280
286
  Short prompts (never paste the whole `CLAUDE.md` — the agent reads the repo's own). Reviews judge only the diff, but the prompt must say the reviewer **may open files for context** (cat, rg, git
281
287
  show — read only): Codex read "review only the diff" as "use only the attached diff" and returned an empty review. Don't re-read
282
- the whole issue at every step. Respect the MCP limitations listed in the workspace `CLAUDE.md`.
288
+ the whole issue at every step. Respect the MCP limitations listed in the workspace `ORQ.md`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orq-setup
3
- description: Guided first-time setup of orq for a new developer — detects and smoke-tests the installed agent CLIs, asks which agents take which roles, models, Claude accounts and pick weights, writes ~/.config/orq/config.toml, then prepares a conductor workspace (orq setup --workspace + workspace CLAUDE.md) and checks Plane. Use when the user says "/orq-setup", "set up orq", "configure orq" or just installed orq (npm i -g @irythill/orq, or install.sh).
3
+ description: Guided first-time setup of orq for a new developer — detects and smoke-tests the installed agent CLIs, asks which agents take which roles, models, Claude accounts and pick weights, writes ~/.config/orq/config.toml, then prepares a conductor workspace (orq setup --workspace + workspace ORQ.md) and checks Plane. Use when the user says "/orq-setup", "set up orq", "configure orq" or just installed orq (npm i -g @irythill/orq, or install.sh).
4
4
  ---
5
5
 
6
6
  # /orq-setup — configure orq for this developer
@@ -85,10 +85,10 @@ Explain simply, then suggest the example's values and ask if they want to change
85
85
  ## 6. Workspace
86
86
  - Ask for the folder that groups their repos (e.g. `~/Code/acme-platform`). After the OK:
87
87
  `orq setup --workspace <folder> --conductor <the conductor agent>` — `.orquestra/`, `.worktrees/`, the map
88
- `<folder>/CLAUDE.md` from the template (never overwrites one) and the `/issue` playbook in the conductor's format
88
+ `<folder>/ORQ.md` from the template (never overwrites one) and the `/issue` playbook in the conductor's format
89
89
  (`.claude/skills/issue/SKILL.md` for Claude Code, `AGENTS.md` for the others). Conductor in herdr (clone only):
90
90
  `$ORQ/setup/new-conductor.sh <name> <folder>` also writes its instructions and permissions.
91
- - Fill the repo table in `<folder>/CLAUDE.md` by inspecting each git repo inside it:
91
+ - Fill the repo table in `<folder>/ORQ.md` by inspecting each git repo inside it:
92
92
  - base branch: `git -C <repo> symbolic-ref --short refs/remotes/origin/HEAD` (strip `origin/`); ask if the team
93
93
  branches from another one (e.g. `dev`).
94
94
  - group: the repo folder name in lower case (the herdr workspace or tmux group for its children).
@@ -2,23 +2,26 @@
2
2
 
3
3
  You orchestrate agents in the `{{WORKSPACE}}` ecosystem, in herdr (`HERDR_ENV=1`) or in any other terminal.
4
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.
5
+ 1. Read `{{WORKSPACE}}/ORQ.md` (repos, groups, base branch, Plane, issue standard, MCP limitations).
6
+ In an older workspace without `ORQ.md`, read `{{WORKSPACE}}/CLAUDE.md`.
7
+ 2. For every Plane issue, follow the `/issue <ID>` playbook (a skill or the generated block in `AGENTS.md`).
7
8
  3. Never push, open a PR, merge or deploy unless the user asks. When unsure about scope or cost, ask.
8
9
  4. **Children only through `orq agent`** (implementer, fix, review), with the agent chosen by `orq pick`; tests through
9
10
  `orq verify` (it checks the base by itself; no `orq baseline` in the flow), as the skill says. NEVER
10
11
  `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
12
  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.
13
+ arriving in your pane; elsewhere, `orq wait` printing `[ORQ AGENT DONE] <title> status=ok|fail ... answer: <file>`
14
+ (plus the `<prefix>.done` file either way), run as the **Waiting strategy** below says. When it arrives: read the
15
+ answer file and act. Nothing else means done.
15
16
  6. **"Is it still running?"** is answered by the file only: `<prefix>.done` exists → finished; it doesn't → running;
16
17
  check `<prefix>.log` (last write time) or, in herdr, peek at the pane: `herdr pane read $(cat <prefix>.pane) --source recent`.
17
18
  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).
19
+ 7. **Never block waiting** (`herdr agent wait`, `pane wait-output`, `sleep`) except through `orq wait` as the Waiting
20
+ strategy below says. End your turn after dispatching only when that strategy gives you a wake-up.
20
21
  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
+ 9. Between batches of unrelated issues, suggest a fresh conversation: state lives in Plane and in `.orquestra/`.
23
+
24
+ {{WAIT_STRATEGY}}
22
25
 
23
26
  ## How you are used
24
27
  - **Dispatcher, not consultant.** Your job: issue → dispatch → report → PR/cleanup. Long questions about architecture,
@@ -26,6 +29,6 @@ Don't write code yourself. Opened by `orq conductor`. Talk to the user in the us
26
29
  to a design session; come back with the decision recorded in the issue".
27
30
  - **The issue is the source.** A decision made in chat → record it in Plane (description or comment) before
28
31
  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
32
+ - **Context:** past ~150k tokens or after closing a batch of related issues → suggest a fresh conversation (state in Plane and in
30
33
  `.orquestra/`).
31
34
  - 1–3 line answers. Final issue report: 3–5 lines + the PR link.
@@ -1,11 +1,11 @@
1
- # CLAUDE.md — {{NAME}} ecosystem ({{WORKSPACE}})
1
+ # ORQ.md — {{NAME}} ecosystem ({{WORKSPACE}})
2
2
 
3
3
  This folder groups the ecosystem's repositories. Each repo has its own `CLAUDE.md`/`AGENTS.md` with commands and
4
4
  rules — read the repo's own before touching it. This file only holds the map and the orchestration settings.
5
5
 
6
6
  ## Orchestration (read by the `/issue` skill)
7
7
  - **Workspace:** `{{WORKSPACE}}`
8
- - **Conductor:** opened with `orq conductor [account] [model] [-r <session-id>]` in this folder, in any terminal (inside a
8
+ - **Conductor:** opened with `orq conductor [agent] [account|-] [model] [-r <session-id>]` in this folder, in any terminal (inside a
9
9
  herdr pane too; `herdr-conductor` still works there). Instructions in `.orquestra-conductor.md`; the **Group** in the
10
10
  table below is the herdr workspace (`-g`) or just a label outside herdr.
11
11
  Tests from the Verification column (jest/vitest) **don't run in the child**: the conductor runs `orq verify`