loreli 1.0.0 → 2.0.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.
Files changed (63) hide show
  1. package/README.md +66 -26
  2. package/package.json +17 -14
  3. package/packages/action/prompts/action.md +172 -0
  4. package/packages/action/src/index.js +33 -5
  5. package/packages/agent/README.md +107 -18
  6. package/packages/agent/src/backends/claude.js +111 -11
  7. package/packages/agent/src/backends/codex.js +78 -5
  8. package/packages/agent/src/backends/cursor.js +104 -27
  9. package/packages/agent/src/backends/index.js +162 -5
  10. package/packages/agent/src/cli.js +80 -3
  11. package/packages/agent/src/discover.js +396 -0
  12. package/packages/agent/src/factory.js +39 -34
  13. package/packages/agent/src/models.js +24 -6
  14. package/packages/classify/README.md +136 -0
  15. package/packages/classify/prompts/blocker.md +12 -0
  16. package/packages/classify/prompts/feedback.md +14 -0
  17. package/packages/classify/prompts/pane-state.md +20 -0
  18. package/packages/classify/src/index.js +81 -0
  19. package/packages/config/README.md +156 -91
  20. package/packages/config/src/defaults.js +32 -21
  21. package/packages/config/src/index.js +33 -2
  22. package/packages/config/src/schema.js +57 -39
  23. package/packages/hub/src/github.js +59 -20
  24. package/packages/identity/README.md +1 -1
  25. package/packages/identity/src/index.js +2 -2
  26. package/packages/knowledge/README.md +86 -106
  27. package/packages/knowledge/src/index.js +56 -225
  28. package/packages/mcp/README.md +51 -7
  29. package/packages/mcp/instructions.md +6 -1
  30. package/packages/mcp/scaffolding/loreli.yml +115 -77
  31. package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +1 -0
  32. package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +4 -1
  33. package/packages/mcp/scaffolding/mcp-configs/.mcp.json +4 -1
  34. package/packages/mcp/src/index.js +45 -16
  35. package/packages/mcp/src/tools/agent-context.js +44 -0
  36. package/packages/mcp/src/tools/agents.js +34 -13
  37. package/packages/mcp/src/tools/context.js +3 -2
  38. package/packages/mcp/src/tools/github.js +11 -47
  39. package/packages/mcp/src/tools/hitl.js +19 -6
  40. package/packages/mcp/src/tools/index.js +2 -1
  41. package/packages/mcp/src/tools/refactor.js +227 -0
  42. package/packages/mcp/src/tools/repo.js +44 -0
  43. package/packages/mcp/src/tools/start.js +159 -90
  44. package/packages/mcp/src/tools/status.js +5 -2
  45. package/packages/mcp/src/tools/work.js +18 -8
  46. package/packages/orchestrator/src/index.js +345 -79
  47. package/packages/planner/README.md +84 -1
  48. package/packages/planner/prompts/plan-reviewer.md +109 -0
  49. package/packages/planner/prompts/planner.md +191 -0
  50. package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
  51. package/packages/planner/src/index.js +326 -111
  52. package/packages/review/README.md +2 -2
  53. package/packages/review/prompts/reviewer.md +158 -0
  54. package/packages/review/src/index.js +196 -76
  55. package/packages/risk/README.md +81 -22
  56. package/packages/risk/prompts/risk.md +272 -0
  57. package/packages/risk/src/index.js +44 -33
  58. package/packages/tmux/src/index.js +61 -12
  59. package/packages/workflow/README.md +18 -14
  60. package/packages/workflow/prompts/preamble.md +14 -0
  61. package/packages/workflow/src/index.js +191 -12
  62. package/packages/workspace/README.md +2 -2
  63. package/packages/workspace/src/index.js +69 -18
package/README.md CHANGED
@@ -52,6 +52,7 @@ export GITHUB_TOKEN=ghp_your_token_here
52
52
  ```
53
53
 
54
54
  To persist it, add the export to your shell profile (`~/.zshrc`, `~/.bashrc`) or create a `.env` file in the project root. Never commit tokens to version control.
55
+ > 🔐 **Secret handling** — Personal access tokens carry the same blast radius as your GitHub account. Keep them in environment variables or a secret manager, scope them to the minimum permissions, and rotate them regularly. See [GitHub’s “Keeping your API credentials secure” guidance](https://docs.github.com/en/rest/authentication/keeping-your-api-credentials-secure) for the official checklist.
55
56
 
56
57
  <details>
57
58
  <summary>Using a fine-grained token instead</summary>
@@ -81,6 +82,12 @@ Hold off on running `loreli` commands until you've completed Step 4, because the
81
82
 
82
83
  Loreli runs as an MCP server over stdio. Your IDE or MCP client needs a config entry that tells it how to start Loreli. Add this to your **global** (user-level) MCP settings so the token stays out of project files.
83
84
 
85
+ Before editing any config file:
86
+
87
+ 1. Export `GITHUB_TOKEN` in the shell or source it from a local `.env` so the value never lands in Git history.
88
+ 2. Use your client’s environment forwarding rather than pasting literals. Cursor and VS Code expand `${env:NAME}` placeholders inside `mcpServers` blocks, while Claude’s `.mcp.json` honors `${NAME}` tokens per the [Cursor MCP docs](https://docs.cursor.com/ko/context/mcp), [VS Code variable reference](https://code.visualstudio.com/docs/editor/variables-reference), and [Claude Code MCP guide](https://docs.claude.ai/claude-code/mcp).
89
+ 3. Keep repo-level `loreli.yml` and workspace configs secret-free; only user-level config or wrapper scripts should reference tokens.
90
+
84
91
  **Cursor / VS Code** — open **Settings** > search **MCP** > **Edit in settings.json**, or add directly to your user-level `~/.cursor/mcp.json`:
85
92
 
86
93
  ```json
@@ -90,13 +97,15 @@ Loreli runs as an MCP server over stdio. Your IDE or MCP client needs a config e
90
97
  "command": "npx",
91
98
  "args": ["loreli", "mcp"],
92
99
  "env": {
93
- "GITHUB_TOKEN": "ghp_your_token_here"
100
+ "GITHUB_TOKEN": "${env:GITHUB_TOKEN}"
94
101
  }
95
102
  }
96
103
  }
97
104
  }
98
105
  ```
99
106
 
107
+ Cursor and VS Code both expand `${env:NAME}` placeholders, so this entry reads the token you already exported (`export GITHUB_TOKEN=...`) without storing it in the config. Cursor additionally supports an `envFile` attribute if you prefer pointing at a dedicated `.env` file that lives outside your repos (see the [Cursor MCP docs](https://docs.cursor.com/ko/context/mcp) for the full schema).
108
+
100
109
  **Claude Code (CLI)** — add to your user-level config at `~/.claude/.mcp.json`:
101
110
 
102
111
  ```json
@@ -106,14 +115,14 @@ Loreli runs as an MCP server over stdio. Your IDE or MCP client needs a config e
106
115
  "command": "npx",
107
116
  "args": ["loreli", "mcp"],
108
117
  "env": {
109
- "GITHUB_TOKEN": "ghp_your_token_here"
118
+ "GITHUB_TOKEN": "${GITHUB_TOKEN}"
110
119
  }
111
120
  }
112
121
  }
113
122
  }
114
123
  ```
115
124
 
116
- The `env` block keeps the token scoped to Loreli's process. Alternatively, if `GITHUB_TOKEN` is already exported in your shell profile, you can omit the `env` block entirely — Loreli reads `process.env.GITHUB_TOKEN` at startup.
125
+ The `env` block keeps the token scoped to Loreli's process, and Claude expands `${VAR}` placeholders at load time per the [Claude Code MCP guide](https://docs.claude.ai/claude-code/mcp#environment-variable-expansion), so you only need to manage the token in your shell. Alternatively, if `GITHUB_TOKEN` is already exported in your shell profile, you can omit the `env` block entirely — Loreli reads `process.env.GITHUB_TOKEN` at startup.
117
126
 
118
127
  <details>
119
128
  <summary>Claude Desktop</summary>
@@ -129,12 +138,14 @@ Add to your Claude Desktop config file:
129
138
  "command": "npx",
130
139
  "args": ["loreli", "mcp"],
131
140
  "env": {
132
- "GITHUB_TOKEN": "ghp_your_token_here"
141
+ "GITHUB_TOKEN": "${GITHUB_TOKEN}"
133
142
  }
134
143
  }
135
144
  }
136
145
  }
137
146
  ```
147
+
148
+ Claude Desktop shares the same `.mcp.json` schema as the CLI, so `${GITHUB_TOKEN}` resolves against the environment variable available when the desktop app launches. Keep the variable in your login shell or macOS Keychain-backed launch script instead of copying the literal token into this file.
138
149
  </details>
139
150
 
140
151
  <details>
@@ -146,9 +157,10 @@ Add to `~/.codex/config.toml` (user-level):
146
157
  [mcp_servers.loreli]
147
158
  command = "npx"
148
159
  args = ["loreli", "mcp"]
160
+ env_vars = ["GITHUB_TOKEN"]
149
161
  ```
150
162
 
151
- Codex reads `GITHUB_TOKEN` from the shell environment. Set it in your shell profile before running `codex`.
163
+ Codex only forwards a short built-in whitelist of environment variables to STDIO MCP servers; anything sensitive like `GITHUB_TOKEN` must be explicitly listed in `env_vars` so the CLI copies it from your shell into the Loreli process without writing the value to disk. Once you add the line above and export `GITHUB_TOKEN` in your shell, Codex will pass it through on launch per the [Codex config reference](https://developers.openai.com/codex/config-reference) and [docs/example-config](https://fossies.org/linux/codex-rust/docs/example-config.md).
152
164
  </details>
153
165
 
154
166
  To confirm your MCP configuration is discoverable by the CLI, run the commands below in a shell where `GITHUB_TOKEN` is set. This matters because the CLI resolves tool calls through the MCP server entry, and without it even `loreli --version` and `loreli tools list` will fail. You should see the version string and a list of available tools.
@@ -299,13 +311,15 @@ Death snapshots are cleaned up by `Storage.prune()` alongside other session data
299
311
 
300
312
  ## Agent Backends
301
313
 
302
- Loreli supports multiple agent backends. The `BackendRegistry` auto-discovers which are available at startup.
314
+ Loreli supports multiple agent backends. The `BackendRegistry` auto-discovers which are available at startup, including available models for backends that support runtime discovery.
315
+
316
+ | Backend | CLI Binary | Provider | Model Discovery |
317
+ |---------|-----------|----------|----------------|
318
+ | Claude | `claude` | Anthropic | Proxy listing (`/v1/models` or `/models`) when `ANTHROPIC_BASE_URL` is configured; otherwise static defaults |
319
+ | Cursor | `cursor-agent` | Multi-provider | `--list-models` with auto tier classification |
320
+ | Codex | `codex` | OpenAI | Proxy listing (`/v1/models` or `/models`) when `OPENAI_BASE_URL` is configured; otherwise static defaults |
303
321
 
304
- | Backend | CLI Binary | Provider | Description |
305
- |---------|-----------|----------|-------------|
306
- | Claude | `claude` | Anthropic | Stays running in tmux, receives messages |
307
- | Cursor | `cursor-agent` | Multi-provider | Multi-provider via Cursor's model routing |
308
- | Codex | `codex` | OpenAI | Interactive CLI agent (--full-auto, --no-alt-screen, file-based prompts) |
322
+ Model aliases (`fast`, `balanced`, `powerful`) resolve through: config override > runtime discovery > static defaults > pass-through. See [packages/agent/README.md](packages/agent/README.md) for the full resolution chain and LiteLLM/proxy override docs.
309
323
 
310
324
  Loreli derives review strategy from detected side capability at runtime:
311
325
 
@@ -333,7 +347,7 @@ pr:
333
347
  command: npm test # default pre-PR command; blocks pr/create on failure
334
348
  selfReview:
335
349
  enabled: true # default: pr/create requires preview + confirm=true
336
- model: balanced # fast | balanced | powerful
350
+ model: balanced # fast | balanced | powerful (global fallback)
337
351
  labels:
338
352
  track: true # enable provider/model label tracking
339
353
  extra: [] # additional labels applied to all loreli items
@@ -341,6 +355,9 @@ timeouts:
341
355
  stall: "10m" # human-readable before agent is considered stalled
342
356
  shutdown: "1m" # graceful shutdown before kill
343
357
  poll: "2s" # orchestrator poll interval
358
+ rapidDeath: "15s" # startup crash detection window
359
+ proxyDiscovery: "5s" # HTTP timeout for proxy model discovery
360
+ nudge: true # enable tier-1 stall nudge messages
344
361
  watch:
345
362
  interval: "60s" # reactor tick interval
346
363
  maxRounds: 7 # max review rounds before escalation
@@ -352,11 +369,30 @@ agents:
352
369
  disallowedTools: # CLI tools denied in agent workspaces
353
370
  - gh
354
371
  - curl
355
- prompts:
356
- action: .loreli/action.md # custom prompt for action agents (relative to repo root)
357
- reviewer: .loreli/review.md # custom prompt for reviewer agents
358
- planner: .loreli/planner.md # custom prompt for planner agents
359
- risk: .loreli/risk.md # custom prompt for risk agents
372
+ workflows: # per-role model, scaling, trace, prompt
373
+ action:
374
+ model: balanced
375
+ maxAgents: 3
376
+ # prompt: .loreli/action.md # optional custom prompt per role
377
+ reviewer:
378
+ model: balanced
379
+ maxAgents: 2
380
+ trace:
381
+ enabled: true
382
+ maxOutputChars: 4000
383
+ risk:
384
+ model: fast
385
+ maxAgents: 3
386
+ skip: false
387
+ trace:
388
+ enabled: true
389
+ maxOutputChars: 2000
390
+ planner:
391
+ model: powerful
392
+ maxAgents: 1
393
+ trace:
394
+ enabled: true
395
+ maxOutputChars: 4000
360
396
  ```
361
397
 
362
398
  `pr.validation.command` and `pr.selfReview.enabled` harden PR creation quality gates and are enabled by default:
@@ -403,7 +439,7 @@ See [packages/config/README.md](packages/config/README.md#configurable-base-bran
403
439
 
404
440
  ## Custom Prompt Extensions
405
441
 
406
- Projects can inject custom instructions into agent prompts by setting `prompts.{role}` in `loreli.yml`. Each key maps to a role (`action`, `reviewer`, `planner`, `risk`) and the value is a file path relative to the repository root. Loreli reads the file from the target repo (via the GitHub API) once per session and prepends its content to the rendered prompt — after the built-in autonomous preamble, before the role-specific template.
442
+ Projects can inject custom instructions into agent prompts by setting `workflows.{role}.prompt` in `loreli.yml`. Each role (`action`, `reviewer`, `planner`, `risk`) can have a `prompt` key whose value is a file path relative to the repository root. Loreli reads the file from the target repo (via the GitHub API) once per session and prepends its content to the rendered prompt — after the built-in autonomous preamble, before the role-specific template.
407
443
 
408
444
  This mechanism addresses a common need: different repositories have different coding standards, architectural constraints, or domain knowledge that agents should follow. Per-role prompts let you tailor instructions to each role — action agents might need coding standards and API constraints, while reviewers need quality gates and review checklists.
409
445
 
@@ -434,24 +470,28 @@ Create prompt files in your repository — for example under `.loreli/`:
434
470
  </review-standards>
435
471
  ```
436
472
 
437
- Then reference them in `loreli.yml`:
473
+ Then reference them in `loreli.yml` under `workflows`:
438
474
 
439
475
  ```yaml
440
- prompts:
441
- action: .loreli/action.md
442
- reviewer: .loreli/review.md
443
- planner: .loreli/planner.md
444
- risk: .loreli/risk.md # optional — omit roles that don't need custom prompts
476
+ workflows:
477
+ action:
478
+ prompt: .loreli/action.md
479
+ reviewer:
480
+ prompt: .loreli/review.md
481
+ planner:
482
+ prompt: .loreli/planner.md
483
+ risk:
484
+ prompt: .loreli/risk.md # optional — omit roles that don't need custom prompts
445
485
  ```
446
486
 
447
487
  After the next `start()`, each agent role receives only its own custom instructions at the top of its prompt, before the role-specific template content. Roles without a configured prompt file are unaffected.
448
488
 
449
489
  ### How It Works
450
490
 
451
- The custom prompt flows through the same rendering pipeline as the autonomous preamble — `Workflow.render()` and `Workflow.renderFrom()` resolve `prompts.{role}` from config and prepend the content automatically. The injection order is:
491
+ The custom prompt flows through the same rendering pipeline as the autonomous preamble — `Workflow.render()` and `Workflow.renderFrom()` resolve `workflows.{role}.prompt` from config and prepend the content automatically. The injection order is:
452
492
 
453
493
  1. **Autonomous preamble** — built-in headless operation directives (always present)
454
- 2. **Custom prompt** — role-specific project instructions from `prompts.{role}` (when configured)
494
+ 2. **Custom prompt** — role-specific project instructions from `workflows.{role}.prompt` (when configured)
455
495
  3. **Role template** — the action/planner/reviewer prompt
456
496
 
457
497
  The file is read once from GitHub when the first prompt for that role is rendered and cached for the remainder of the session. If the file is missing or unreadable, rendering continues normally without it — agents are never blocked by a missing custom prompt.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loreli",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "Agentic team orchestration via GitHub — an MCP server that coordinates AI agents using issues, PRs, and reviews as the communication layer.",
6
6
  "exports": {
@@ -21,23 +21,13 @@
21
21
  "./marker": "./packages/marker/src/index.js",
22
22
  "./knowledge": "./packages/knowledge/src/index.js",
23
23
  "./context": "./packages/context/src/index.js",
24
+ "./classify": "./packages/classify/src/index.js",
24
25
  "./test-utils": "./packages/test-utils/src/index.js",
25
26
  "./mcp": "./packages/mcp/src/index.js"
26
27
  },
27
28
  "bin": {
28
29
  "loreli": "./bin/loreli.js"
29
30
  },
30
- "scripts": {
31
- "pretest": "node --env-file-if-exists=.env scripts/clean-test-repo.js",
32
- "test": "node --env-file-if-exists=.env --test --test-concurrency=1 --experimental-test-coverage --test-coverage-branches=90 --test-coverage-functions=90 --test-coverage-lines=90 packages/*/test/*.test.js",
33
- "test:ci": "LORELI_TEST_MODE=unit node --env-file-if-exists=.env --test --test-concurrency=1 packages/*/test/*.test.js",
34
- "e2e": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/single/*.test.js e2e/multi/*.test.js",
35
- "e2e:single": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/single/*.test.js",
36
- "e2e:multi": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/multi/*.test.js",
37
- "lint:secrets": "npx secretlint \"**/*\"",
38
- "prepublishOnly": "pnpm lint:secrets",
39
- "prepare": "node node_modules/pre-commit/install.js && node node_modules/pre-push/install.js"
40
- },
41
31
  "pre-commit": [
42
32
  "lint:secrets"
43
33
  ],
@@ -45,7 +35,7 @@
45
35
  "lint:secrets"
46
36
  ],
47
37
  "dependencies": {
48
- "@mcp-layer/cli": "^1.0.0",
38
+ "@mcp-layer/cli": "^1.3.0",
49
39
  "@modelcontextprotocol/sdk": "^1.26.0",
50
40
  "@octokit/rest": "^21.1.1",
51
41
  "@secretlint/secretlint-rule-preset-recommend": "^11.3.1",
@@ -77,7 +67,20 @@
77
67
  ],
78
68
  "license": "MIT",
79
69
  "devDependencies": {
70
+ "@changesets/cli": "^2.29.2",
80
71
  "pre-commit": "^1.2.2",
81
72
  "pre-push": "^0.1.4"
73
+ },
74
+ "scripts": {
75
+ "pretest": "node --env-file-if-exists=.env scripts/clean-test-repo.js",
76
+ "test": "node --env-file-if-exists=.env --test --test-concurrency=1 --experimental-test-coverage --test-coverage-branches=90 --test-coverage-functions=90 --test-coverage-lines=90 packages/*/test/*.test.js",
77
+ "test:ci": "LORELI_TEST_MODE=unit node --env-file-if-exists=.env --test --test-concurrency=1 packages/*/test/*.test.js",
78
+ "e2e": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/single/*.test.js e2e/multi/*.test.js",
79
+ "e2e:single": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/single/*.test.js",
80
+ "e2e:multi": "node --env-file-if-exists=.env scripts/clean-test-repo.js && node --env-file-if-exists=.env --test --test-concurrency=1 e2e/multi/*.test.js",
81
+ "lint:secrets": "npx secretlint \"**/*\"",
82
+ "changeset": "changeset",
83
+ "changeset:version": "changeset version",
84
+ "changeset:publish": "changeset publish"
82
85
  }
83
- }
86
+ }
@@ -0,0 +1,172 @@
1
+ You are **{{name}}**, an action agent for the repository **{{{repo}}}**.
2
+
3
+ <instructions>
4
+
5
+ ## Objective
6
+
7
+ Claim open issues, implement the work, and create pull requests for review.
8
+
9
+ ### Claiming
10
+
11
+ 1. Find an unclaimed issue (no claim comment yet).
12
+ 2. Use the **comment** tool with `claim: true` to claim it.
13
+ 3. First-comment-wins — if someone claimed before you, move to the next issue.
14
+
15
+ ### Tools
16
+
17
+ Use these Loreli MCP tools for all GitHub operations:
18
+
19
+ - **pr** (action: `create`) — Open a pull request. Provide `title`, `body`, `head` (branch name), and `reasoning`. Your claimed issue is auto-referenced with "Closes #N". Agent stamp and labels are applied automatically.
20
+ - **read** — Read any issue, PR, or discussion by number. Use this to look up acceptance criteria, understand linked issues, or review discussion context.
21
+ - **comment** — Post a comment on your current work item. Use for progress updates or responding to reviewer feedback. Set `claim: true` to claim an issue.
22
+ - **comment** (abandon: `true`) — Abandon the current issue as fundamentally impossible. Requires `reason`. Posts an abandon comment, labels the issue, closes it, and notifies human reviewers. Use ONLY when the issue describes work that cannot be done (e.g., "remove a directory that doesn't exist", "implement an API that conflicts with existing architecture"). Do NOT use for "this is hard" or "I need more time."
23
+ - **refactor** — Flag a bug, code smell, or tech debt you encounter during implementation. Call **immediately on discovery** — do not wait until your PR is complete. Provide `kind`, `title`, `description` with stable identifiers, and optionally `files` (with `#R<start>-R<end>` range hints) and `impact`. The tool deduplicates and creates a planning discussion automatically.
24
+ - **plan** (action: `escalate`) — Flag a process, architecture, or scope coordination concern as a new discussion. Provide `title` and `body`. Use this for concerns about the overall approach, not for code-level issues (use **refactor** for those).
25
+
26
+ ### Working
27
+
28
+ Your workspace is already a checkout of **{{{repo}}}** with git fully configured.
29
+
30
+ **Trust the workspace git setup:**
31
+ - Do NOT clone the repository again, run `git init`, create alternate `.git` directories, or modify git internals.
32
+ - If `ls -la` shows `@` flags on files, that is a macOS extended attribute (harmless). Verify with `git status`, not `ls`.
33
+ - Git credentials and push URLs are pre-configured.
34
+
35
+ You are on branch `{{{branch}}}`. Use this branch for all your work — do not create other branches.
36
+
37
+ ### Policy Preflight
38
+
39
+ Before implementation, check whether `AGENTS.md` exists at the repository root. If present, read it and treat it as the authoritative repository policy for coding style, testing, docs, and validation expectations.
40
+
41
+ ### Implementation
42
+
43
+ 1. Read the issue's acceptance criteria and testing strategy.
44
+ 2. Write tests first, following the TDD approach described in the issue.
45
+ 3. Implement the code to make the tests pass.
46
+ 4. Verify your work (see Verification below).
47
+ 5. Use the **pr** tool (action: `create`) with `head` as `{{{branch}}}` only after verification is complete. The tool automatically stages, commits, and pushes your changes before creating the PR.
48
+
49
+ ### Verification
50
+
51
+ Before creating a PR, verify your work passes quality checks. Unverified PRs waste the reviewer's session and delay the entire pipeline:
52
+
53
+ 1. Run the test suite and confirm all tests pass.
54
+ 2. If the repo has a linter or type checker, run it and fix any errors you introduced.
55
+ 3. Review your own diff — check for debug statements, commented-out code, or unintended changes.
56
+
57
+ Only create the PR after verification succeeds.
58
+ Your PR `reasoning` must explicitly list the verification commands you ran and confirm they passed.
59
+
60
+ ### Completion Gate
61
+
62
+ You are **not done until** exactly one terminal outcome is reached:
63
+
64
+ 1. **PR path**: the **pr** tool with `action: create` succeeds for `head: {{{branch}}}` and returns a PR number.
65
+ 2. **Abandon path**: the **comment** tool with `abandon: true` is posted with a concrete reason.
66
+
67
+ Do **not** end your turn with only a local summary, passing tests, or `git status`.
68
+ Do **not stop** at the Codex idle prompt before one terminal outcome above is complete.
69
+ If PR creation fails, diagnose and retry with corrected arguments instead of stopping.
70
+
71
+ ### Documentation
72
+
73
+ When your changes touch these areas, documentation is mandatory:
74
+
75
+ - **Architectural changes** (new packages, new modules, changed data flow): create or update architecture diagrams in `docs/` and explain the decision rationale.
76
+ - **Error handling** (new error classes, try/catch blocks, error responses): document each error with resolution steps that anyone can follow regardless of skill level.
77
+ - **New APIs** (exported functions, REST endpoints, tool definitions): document parameters, return shapes, and error behavior in JSDoc and the package README.
78
+
79
+ ### PR Reasoning
80
+
81
+ When creating a PR, include a `reasoning` parameter summarizing your approach. This is included in the PR body as a collapsible trace for human review.
82
+
83
+ ### Rules
84
+
85
+ - Stay within the scope defined in the issue's acceptance criteria. Scope creep in a multi-agent system cascades — your unplanned changes can conflict with parallel agents working on adjacent issues.
86
+ - Do not add features or fixes beyond the issue scope. When you encounter bugs, code smells, or tech debt during your work, use **refactor** immediately to flag them — then continue your assigned task. For process, architecture, or scope coordination concerns, use **plan** (action: `escalate`) instead.
87
+ - Commit with descriptive messages following the repo's conventions.
88
+ - When review comments arrive, address them promptly and use **comment** to request re-review.
89
+ - If approaching context limits, commit and push your progress, then post a comment documenting remaining work before stopping.
90
+
91
+ </instructions>
92
+
93
+ <output_format>
94
+
95
+ Structure your PR `reasoning` parameter with:
96
+
97
+ - **Approach**: What strategy you chose and why.
98
+ - **Key decisions**: Any trade-offs or alternatives you considered.
99
+ - **Verification**: What you tested and how you confirmed correctness.
100
+
101
+ </output_format>
102
+
103
+ <examples>
104
+
105
+ <example title="Well-formed PR reasoning">
106
+ **Approach**: Implemented exponential backoff using a recursive retry wrapper around the existing `fetch` call. Chose composition over modifying `fetch` directly to keep the retry logic testable in isolation.
107
+
108
+ **Key decisions**: Used jitter via `Math.random() * baseDelay * 0.5` to avoid thundering herd. Capped at 3 retries based on the acceptance criteria. Classified 429 and 5xx as retryable; all other 4xx propagate immediately.
109
+
110
+ **Verification**: All 5 test scenarios from the issue pass. Ran `pnpm test` — 47/47 passing. Ran `pnpm lint` — no new warnings. Reviewed diff for debug artifacts — clean.
111
+ </example>
112
+
113
+ </examples>
114
+
115
+ {{#issue}}
116
+ <context>
117
+
118
+ ## Assigned Issue
119
+
120
+ You have been assigned the following issue. It is already claimed on your behalf — proceed directly to implementation.
121
+
122
+ {{{issue}}}
123
+
124
+ </context>
125
+ {{/issue}}
126
+
127
+ {{#reviewFeedback}}
128
+ <context>
129
+
130
+ ## Review Feedback
131
+
132
+ The reviewer **{{reviewer}}** ({{reviewerProvider}}) has requested changes on **PR #{{number}}**:
133
+
134
+ {{#comments}}
135
+ - {{body}}
136
+ {{/comments}}
137
+
138
+ Address each item:
139
+
140
+ 1. Make the requested changes.
141
+ 2. Run verification again to confirm nothing is broken.
142
+ 3. Push your commits.
143
+ 4. Use the **comment** tool to post: "@{{reviewer}} Changes addressed, ready for re-review."
144
+
145
+ </context>
146
+ {{/reviewFeedback}}
147
+
148
+ {{#hitl}}
149
+ <context>
150
+
151
+ ## Human In The Loop Feedback
152
+
153
+ A human reviewer has provided feedback on your PR. Their comments are below:
154
+
155
+ {{#feedback}}
156
+ - **@{{author}}**: {{body}}
157
+ {{/feedback}}
158
+
159
+ Address each comment carefully:
160
+
161
+ 1. Make the requested changes.
162
+ 2. Run verification again to confirm nothing is broken.
163
+ 3. Push your commits.
164
+ 4. Use the **comment** tool to post: "@{{reviewer}} Changes addressed, ready for re-review."
165
+ 5. Do **not** merge — the human reviewer will merge when satisfied.
166
+
167
+ </context>
168
+ {{/hitl}}
169
+
170
+ <agent_metadata>
171
+ Faction: {{faction}} | Provider: {{provider}}
172
+ </agent_metadata>
@@ -313,7 +313,8 @@ export class ActionWorkflow extends Workflow {
313
313
  try {
314
314
  await reset(cwd, agent.identity.name, issue.number, base, options);
315
315
  } catch (err) {
316
- log.warn(`dispatch: workspace reset failed for ${agent.identity.name}: ${err.message}`);
316
+ log.warn(`dispatch: workspace reset failed for ${agent.identity.name}: ${err.message} — skipping issue`);
317
+ continue;
317
318
  }
318
319
 
319
320
  // Dormant agents need respawning; spawned agents are already alive.
@@ -391,7 +392,8 @@ export class ActionWorkflow extends Workflow {
391
392
  let agent = null;
392
393
  for (const c of allComments) {
393
394
  if (has(c.body, 'claim')) {
394
- agent = parse(c.body, 'claim')?.agent ?? c.author;
395
+ const parsed = parse(c.body, 'claim')?.agent;
396
+ if (parsed) agent = parsed;
395
397
  }
396
398
  if (has(c.body, 'release') || has(c.body, 'restart')) {
397
399
  agent = null;
@@ -476,6 +478,23 @@ export class ActionWorkflow extends Workflow {
476
478
  * @throws {Error} When no action agents are available.
477
479
  */
478
480
  async work(repo) {
481
+ if (this._dispatching) return [];
482
+ this._dispatching = true;
483
+
484
+ try {
485
+ return await this._work(repo);
486
+ } finally {
487
+ this._dispatching = false;
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Internal work implementation, guarded by _dispatching in work().
493
+ *
494
+ * @param {string} repo - Repository in "owner/name" format.
495
+ * @returns {Promise<Array<{issue: number, agent: string, reviewer: string|null}>>}
496
+ */
497
+ async _work(repo) {
479
498
  log.info(`work cycle: ${repo}`);
480
499
  const actions = this.agents().filter(function active(a) {
481
500
  return a.state !== 'dormant' && this._stale(a.identity?.name) !== true;
@@ -544,7 +563,8 @@ export class ActionWorkflow extends Workflow {
544
563
  try {
545
564
  await reset(cwd, agent.identity.name, issue.number, base, options);
546
565
  } catch (err) {
547
- log.warn(`work: branch reset failed for ${agent.identity.name}: ${err.message}`);
566
+ log.warn(`work: branch reset failed for ${agent.identity.name}: ${err.message} — skipping issue`);
567
+ continue;
548
568
  }
549
569
 
550
570
  const issueBranch = `${agent.identity.name}/issue-${issue.number}`;
@@ -621,15 +641,23 @@ export class ActionWorkflow extends Workflow {
621
641
 
622
642
  agent = await this.enlist(provider, 'action', {
623
643
  theme: pick(cfg?.get?.('theme')),
624
- model: cfg?.get?.('model'),
625
644
  config: cfg
626
645
  });
627
646
  }
628
647
 
629
- // Build a prompt with the human feedback and dispatch
648
+ // Fetch PR details so the rework prompt includes branch context.
649
+ // Without this, the agent cannot identify which branch to work on.
650
+ let prDetails;
651
+ try {
652
+ prDetails = await this.hub.pull(repo, pr);
653
+ } catch (err) {
654
+ log.warn(`rework: failed to fetch PR #${pr}: ${err.message}`);
655
+ }
656
+
630
657
  await this.dispatch(agent, {
631
658
  name: agent.identity.name,
632
659
  repo,
660
+ branch: prDetails?.head ?? `${agent.identity.name}/work`,
633
661
  faction: agent.identity.faction,
634
662
  provider: agent.identity.provider,
635
663
  model: agent.identity.model,