@agent-relay/factory 0.1.1 → 0.1.3

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 (132) hide show
  1. package/README.md +145 -205
  2. package/dist/cli/fleet.d.ts +5 -1
  3. package/dist/cli/fleet.d.ts.map +1 -1
  4. package/dist/cli/fleet.js +152 -27
  5. package/dist/cli/fleet.js.map +1 -1
  6. package/dist/config/schema.d.ts +4 -4
  7. package/dist/config/schema.d.ts.map +1 -1
  8. package/dist/config/schema.js +13 -2
  9. package/dist/config/schema.js.map +1 -1
  10. package/dist/constants/linear.d.ts +2 -0
  11. package/dist/constants/linear.d.ts.map +1 -1
  12. package/dist/constants/linear.js +6 -0
  13. package/dist/constants/linear.js.map +1 -1
  14. package/dist/dispatch/templates.d.ts +18 -2
  15. package/dist/dispatch/templates.d.ts.map +1 -1
  16. package/dist/dispatch/templates.js +18 -10
  17. package/dist/dispatch/templates.js.map +1 -1
  18. package/dist/fleet/create-fleet.d.ts +3 -2
  19. package/dist/fleet/create-fleet.d.ts.map +1 -1
  20. package/dist/fleet/create-fleet.js +3 -2
  21. package/dist/fleet/create-fleet.js.map +1 -1
  22. package/dist/fleet/ensure-relay-broker.d.ts +24 -0
  23. package/dist/fleet/ensure-relay-broker.d.ts.map +1 -0
  24. package/dist/fleet/ensure-relay-broker.js +56 -0
  25. package/dist/fleet/ensure-relay-broker.js.map +1 -0
  26. package/dist/fleet/internal-fleet-client.d.ts +8 -3
  27. package/dist/fleet/internal-fleet-client.d.ts.map +1 -1
  28. package/dist/fleet/internal-fleet-client.js +32 -5
  29. package/dist/fleet/internal-fleet-client.js.map +1 -1
  30. package/dist/fleet/relay-fleet-client.d.ts +90 -13
  31. package/dist/fleet/relay-fleet-client.d.ts.map +1 -1
  32. package/dist/fleet/relay-fleet-client.js +496 -20
  33. package/dist/fleet/relay-fleet-client.js.map +1 -1
  34. package/dist/github/index.d.ts +4 -4
  35. package/dist/github/index.js +2 -2
  36. package/dist/github/probe-closer.d.ts +1 -1
  37. package/dist/github/probe-closer.js +1 -1
  38. package/dist/index.d.ts +33 -29
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +17 -15
  41. package/dist/index.js.map +1 -1
  42. package/dist/linear/state-resolver.d.ts +3 -2
  43. package/dist/linear/state-resolver.d.ts.map +1 -1
  44. package/dist/linear/state-resolver.js +74 -10
  45. package/dist/linear/state-resolver.js.map +1 -1
  46. package/dist/mount/local-mount-preflight.d.ts +1 -0
  47. package/dist/mount/local-mount-preflight.d.ts.map +1 -1
  48. package/dist/mount/local-mount-preflight.js +64 -12
  49. package/dist/mount/local-mount-preflight.js.map +1 -1
  50. package/dist/mount/relayfile-binary.d.ts +2 -0
  51. package/dist/mount/relayfile-binary.d.ts.map +1 -1
  52. package/dist/mount/relayfile-binary.js +39 -5
  53. package/dist/mount/relayfile-binary.js.map +1 -1
  54. package/dist/mount/relayfile-cloud-mount-client.d.ts +2 -2
  55. package/dist/mount/relayfile-cloud-mount-client.js +1 -1
  56. package/dist/node/factory-node.d.ts +59 -0
  57. package/dist/node/factory-node.d.ts.map +1 -0
  58. package/dist/node/factory-node.js +351 -0
  59. package/dist/node/factory-node.js.map +1 -0
  60. package/dist/node/factory.node.d.ts +3 -0
  61. package/dist/node/factory.node.d.ts.map +1 -0
  62. package/dist/node/factory.node.js +5 -0
  63. package/dist/node/factory.node.js.map +1 -0
  64. package/dist/orchestrator/batch-tracker.d.ts +2 -2
  65. package/dist/orchestrator/factory.d.ts +5 -2
  66. package/dist/orchestrator/factory.d.ts.map +1 -1
  67. package/dist/orchestrator/factory.js +413 -59
  68. package/dist/orchestrator/factory.js.map +1 -1
  69. package/dist/orchestrator/index.d.ts +4 -4
  70. package/dist/orchestrator/index.d.ts.map +1 -1
  71. package/dist/orchestrator/index.js +3 -3
  72. package/dist/orchestrator/index.js.map +1 -1
  73. package/dist/orchestrator/process-identity.d.ts +1 -1
  74. package/dist/orchestrator/reaper.d.ts +3 -3
  75. package/dist/orchestrator/reaper.js +3 -3
  76. package/dist/ports/fleet.d.ts +8 -2
  77. package/dist/ports/fleet.d.ts.map +1 -1
  78. package/dist/ports/index.d.ts +4 -4
  79. package/dist/ports/state.d.ts +3 -3
  80. package/dist/ports/writeback.d.ts +1 -1
  81. package/dist/safety/factory-scope.d.ts +2 -2
  82. package/dist/state/in-memory-state-store.d.ts +1 -1
  83. package/dist/state/in-memory-state-store.js +1 -1
  84. package/dist/subscriptions/event-client.d.ts +1 -1
  85. package/dist/subscriptions/event-client.js +2 -2
  86. package/dist/subscriptions/globs.js +1 -1
  87. package/dist/subscriptions/index.d.ts +7 -7
  88. package/dist/subscriptions/index.js +4 -4
  89. package/dist/subscriptions/linear-filter.d.ts +1 -1
  90. package/dist/subscriptions/linear-filter.js +1 -1
  91. package/dist/subscriptions/specs.d.ts +1 -1
  92. package/dist/subscriptions/specs.js +2 -2
  93. package/dist/testing/fakes.d.ts +1 -1
  94. package/dist/testing/fakes.js +1 -1
  95. package/dist/testing/fakes.js.map +1 -1
  96. package/dist/testing/index.d.ts +1 -1
  97. package/dist/testing/index.js +1 -1
  98. package/dist/triage/heuristic.d.ts +8 -4
  99. package/dist/triage/heuristic.d.ts.map +1 -1
  100. package/dist/triage/heuristic.js +70 -6
  101. package/dist/triage/heuristic.js.map +1 -1
  102. package/dist/triage/index.d.ts +6 -6
  103. package/dist/triage/index.d.ts.map +1 -1
  104. package/dist/triage/index.js +4 -4
  105. package/dist/triage/index.js.map +1 -1
  106. package/dist/triage/llm.d.ts +1 -1
  107. package/dist/triage/llm.d.ts.map +1 -1
  108. package/dist/triage/llm.js +5 -3
  109. package/dist/triage/llm.js.map +1 -1
  110. package/dist/triage/schema.d.ts +133 -29
  111. package/dist/triage/schema.d.ts.map +1 -1
  112. package/dist/triage/schema.js +6 -3
  113. package/dist/triage/schema.js.map +1 -1
  114. package/dist/triage/tiered.d.ts +1 -1
  115. package/dist/triage/tiered.d.ts.map +1 -1
  116. package/dist/triage/tiered.js +9 -2
  117. package/dist/triage/tiered.js.map +1 -1
  118. package/dist/types.d.ts +10 -9
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/writeback/github.d.ts +2 -2
  121. package/dist/writeback/github.js +1 -1
  122. package/dist/writeback/index.d.ts +5 -5
  123. package/dist/writeback/index.js +3 -3
  124. package/dist/writeback/linear.d.ts +3 -3
  125. package/dist/writeback/linear.d.ts.map +1 -1
  126. package/dist/writeback/linear.js +57 -14
  127. package/dist/writeback/linear.js.map +1 -1
  128. package/dist/writeback/slack.d.ts +1 -1
  129. package/dist/writeback/slack.js +2 -2
  130. package/package.json +10 -2
  131. package/scripts/com.agentrelay.factory-canary.plist.example +53 -0
  132. package/scripts/factory-canary.sh +89 -0
package/README.md CHANGED
@@ -1,248 +1,188 @@
1
- # @agent-relay/factory
1
+ # Agent Workforce Factory
2
2
 
3
- The autonomous issue factory: a loop that discovers Linear issues, triages them,
4
- dispatches agents to implement fixes, opens PRs, drives them to completion through
5
- a merge gate, and closes the issues — all under a hard safety scope.
3
+ **Turn issues into reviewed pull requests, automatically.**
6
4
 
7
- The package publishes the **`factory`** CLI (`bin/factory.mjs`). From a repo
8
- checkout, build first and invoke the CLI through the package bin:
5
+ Point the factory at your ticketing system (Linear, GitHub, Shortcut, Asana etc) and it does the loop a human
6
+ otherwise babysits: it discovers the issues that are ready, decides how to tackle
7
+ each one, spawns coding agents to implement and review the change, opens a PR,
8
+ drives it through a merge gate, and closes the issue — all inside a safety scope
9
+ you define, so it only ever acts on work you've explicitly opted in.
9
10
 
10
- ```bash
11
- npm ci
12
- npm run build
11
+ ## Why use it
12
+
13
+ - **Clear the small-but-real backlog.** The well-scoped fixes and chores that pile
14
+ up get done without a person shepherding each one.
15
+ - **It only touches what you allow.** A safety gate (title prefix + team) means
16
+ the factory dispatches *exactly* the issues you mark for it and ignores
17
+ everything else — opt-in by construction.
18
+ - **Real PRs, not blind merges.** Every change goes through an implement → review
19
+ → merge-gate flow and lands as a normal PR. It defaults to *never* auto-merging
20
+ until you turn that on.
21
+ - **You stay in the loop.** It posts threaded status to Slack and can ask a human
22
+ for clarification mid-task when an issue is ambiguous.
23
+ - **Drop in by label.** Hand it new work just by labeling a Linear or GitHub
24
+ issue — no new tooling in your day-to-day.
25
+
26
+ A good fit when you have a steady stream of scoped issues and want them turned
27
+ into PRs without standing up your own agent orchestration.
28
+
29
+ ## How it works
13
30
 
14
- node bin/factory.mjs factory <action> --config <cfg>
15
31
  ```
32
+ discover ready issues → triage (how to do it) → dispatch agents (implement + review)
33
+ → open PR → merge gate → close issue
34
+ ```
35
+
36
+ Each step is gated by your config and the safety scope. Issues outside the scope
37
+ are pulled but never dispatched.
16
38
 
17
- Once installed from npm, the ergonomic form is:
39
+ ## Install
18
40
 
19
41
  ```bash
20
- factory factory <action> [options]
42
+ npm install @agent-relay/factory
21
43
  ```
22
44
 
23
- > **Heads-up:** every `factory` action requires `--config <path>` (see
24
- > [Configuration](#configuration)). Commands fail fast with
25
- > `factory commands require --config <path>` if it's missing.
45
+ The factory talks to a relay broker through the **`agent-relay`** sidecar; install
46
+ and sign in to that separately (it's a peer of this package). Once installed, the
47
+ CLI is available as `factory`:
48
+
49
+ ```bash
50
+ factory run-once --config ./factory.config.json --dry-run
51
+ ```
26
52
 
27
- ---
53
+ From a source checkout instead of an npm install, run
54
+ `npm ci && npm run build` first, then `node bin/factory.mjs <action> …`.
28
55
 
29
56
  ## Quick start
30
57
 
31
- ```bash
32
- # One-shot batch — discover + triage + dispatch one cycle, then exit.
33
- # Add --dry-run to plan without writing or spawning anything.
34
- factory factory run-once --config ./factory.config.json --dry-run
58
+ 1. **Write a minimal config** (`factory.config.json`). Only `workspaceId` and a
59
+ repo route are required:
35
60
 
36
- # Live daemon — subscription-driven; runs until SIGINT/SIGTERM.
37
- factory factory start --mode live --config ./factory.config.json
38
- ```
61
+ ```json
62
+ {
63
+ "workspaceId": "your-workspace-id",
64
+ "repos": {
65
+ "byLabel": { "pear": "AgentWorkforce/pear" },
66
+ "clonePaths": { "AgentWorkforce/pear": "/path/to/your/pear/checkout" },
67
+ "default": "AgentWorkforce/pear"
68
+ }
69
+ }
70
+ ```
71
+
72
+ `workspaceId` is your relay workspace; `repos.byLabel` maps a Linear label to a
73
+ repo; `clonePaths` tells the agent where that repo lives locally so it has
74
+ somewhere to make changes.
75
+
76
+ 2. **Plan a cycle without touching anything** — `--dry-run` discovers and triages
77
+ but writes nothing and spawns no agents:
78
+
79
+ ```bash
80
+ factory run-once --config ./factory.config.json --dry-run
81
+ ```
82
+
83
+ 3. **Let it work for real:**
84
+
85
+ ```bash
86
+ # One discovery→dispatch cycle, then exit.
87
+ factory run-once --config ./factory.config.json
88
+
89
+ # Or run continuously as a daemon (the production form).
90
+ factory start --mode live --config ./factory.config.json
91
+ ```
92
+
93
+ > **Pulled some issues but dispatched none?** That's the safety gate doing its
94
+ > job — the issues are real but outside your scope. See
95
+ > [Tell it what to work on](#tell-it-what-to-work-on).
39
96
 
40
97
  ## Commands
41
98
 
42
99
  | Command | What it does |
43
100
  |---|---|
44
- | `factory run-once` | One discovery→triage→dispatch cycle, then exit. Honors `--dry-run`. |
45
- | `factory loop` | Bounded multi-iteration loop (`loop.maxIterations`), then exit. |
46
- | `factory start --mode live` | Long-lived daemon. Subscription-driven; writes + refreshes a loop heartbeat. The production entrypoint. |
47
- | `factory status` | Print the in-memory factory status as JSON. |
48
- | `factory loop-status` | Read the heartbeat file and report liveness (stale vs. alive). |
49
- | `factory kill-loop` | Send SIGTERM to the daemon PID recorded in the heartbeat. |
50
- | `factory reap-orphans` | Crash backstop: reap orphaned agent pairs whose heartbeat is stale. Run as a scheduled job. |
51
- | `factory triage <KEY\|path>` | Triage a single issue and print the decision. |
52
- | `factory dispatch <KEY\|path>` | Triage + dispatch a single issue. Honors `--dry-run`. |
53
- | `factory close-probe <PR#> --repo <owner/repo> --issue <KEY>` | Manually close a synthetic E2E probe PR. |
101
+ | `factory run-once` | One discover→triage→dispatch cycle, then exit. Honors `--dry-run`. |
102
+ | `factory loop` | A bounded multi-iteration loop, then exit. |
103
+ | `factory start --mode live` | Long-lived daemon the production entrypoint. Runs until you stop it. |
104
+ | `factory status` | Print current factory status as JSON. |
105
+ | `factory triage <KEY\|path>` | Triage one issue and print the decision. |
106
+ | `factory dispatch <KEY\|path>` | Triage + dispatch one issue. Honors `--dry-run`. |
107
+ | `factory canary <KEY\|path>` | Assert a known "Ready for Agent" issue is dispatch-ready by the real dry-run triage path. Prints `{ok,issue,status,reason}`; exits non-zero (with the skip reason) if it isn't. |
54
108
 
55
- Global options (accepted anywhere in the args): `--config <path>`,
56
- `--dry-run`, `--backend <internal\|relay>`.
109
+ Global options work anywhere in the args: `--config <path>`, `--dry-run`,
110
+ `--backend <internal|relay>`. The internal backend reuses a relay broker that's
111
+ already running for your workspace, and starts one if none is.
57
112
 
58
- ## The 2-process production model
113
+ (There are a few more operational commands — `loop-status`, `kill-loop`,
114
+ `reap-orphans`, `close-probe` — for running the daemon in production.)
59
115
 
60
- The factory runs as **two** coordinated processes (see issue #321 §4):
116
+ ### Scheduled sync-fidelity canary
61
117
 
62
- 1. **Live daemon** `factory factory start --mode live --config <live>`. Drives the
63
- loop and refreshes a heartbeat file.
64
- 2. **External reaper** a *scheduled* `factory factory reap-orphans --config <live>`
65
- that acts as a crash backstop, cleaning up orphaned agent pairs if the daemon dies.
118
+ `factory canary` is the regression detector for upstream sync drift: if a synced
119
+ issue stops carrying enough state to be dispatchable (e.g. the Linear sync
120
+ regresses to records without `state.id`), a known-good issue flips from
121
+ dispatch-ready to skipped. Run it on a schedule against a standing "Ready for
122
+ Agent" canary issue and alert on failure.
66
123
 
67
- > **HARD precondition:** the reaper **must** use the **same `--config`** as the
68
- > live daemon. A mismatched config reaps nothing *and* leaves the backstop broken
69
- > (the coupling is load-bearing see §7 of issue #321).
124
+ `scripts/factory-canary.sh` wraps the command for cron/launchd: it runs from your
125
+ deployment dir (reusing the running relay broker), bounds a hung run, and posts a
126
+ Slack alert via `FACTORY_CANARY_SLACK_WEBHOOK` on failure. See
127
+ `scripts/com.agentrelay.factory-canary.plist.example` for an every-6h launchd
128
+ template.
70
129
 
71
- ### Other operating preconditions (issue #321 §7)
130
+ ## Tell it what to work on
72
131
 
73
- - **`gh`-authenticated environment.** The gh-resolver is completion-load-bearing
74
- while the cloud GitHub→mount PR-sync is degraded; a `gh` auth drop halts completion.
75
- - Run the real binary path (`factory` / `node bin/factory.mjs`), not a shim.
132
+ Two ways to hand the factory an issue — both are just labeling/titling, nothing
133
+ to install:
76
134
 
77
- ## Configuration
78
-
79
- Pass a JSON file via `--config`. The full schema (with every default) lives in
80
- [`src/config/schema.ts`](src/config/schema.ts), validated by Zod at load time —
81
- an invalid config fails fast with a field-level error.
135
+ | Source | What you do | Result |
136
+ |---|---|---|
137
+ | **Linear** | Title it `[factory] <task>`, set the team + a repo label, move it to **Ready for Agent** | dispatched directly |
138
+ | **GitHub** | Add the **`factory`** label to the issue | mirrored into a `[factory]` Linear issue, then dispatched |
82
139
 
83
- The file is either a bare config object, or a `{ "factoryConfig": { … } }`
84
- wrapper (the wrapper form also allows a sibling `fixtureFiles` map see
85
- [Fixture mode](#fixture-mode-offline-testing)).
140
+ The **safety gate** is what keeps this opt-in: by default the factory only
141
+ dispatches an issue whose **title starts with your configured prefix** *and* whose
142
+ **team matches** your configured team. Everything else is ignored. Loosen it
143
+ deliberately — it's the main guardrail.
86
144
 
87
- ### Minimal config
145
+ > Tip: `[factory-e2e]` is reserved for the factory's own self-test soak (its PRs
146
+ > auto-close). For real work you want to keep, use the `[factory]` prefix.
88
147
 
89
- Only two fields are required `workspaceId` and `repos.byLabel`. Everything else
90
- has a default:
148
+ ## Run it as a fleet node (optional)
91
149
 
92
- ```json
93
- {
94
- "workspaceId": "your-workspace-id",
95
- "repos": {
96
- "byLabel": { "pear": "AgentWorkforce/pear" },
97
- "default": "AgentWorkforce/pear"
98
- }
99
- }
100
- ```
150
+ The package also ships a fleet **node definition** so a machine can advertise
151
+ `spawn:claude` / `spawn:codex` / `workflow:run` to the cloud and run agents in the
152
+ checkouts it owns:
101
153
 
102
- ### Complete annotated config
103
-
104
- A realistic live config, showing the fields you'll actually want to set (comments
105
- are illustrative — strip them; JSON has no comments):
106
-
107
- ```jsonc
108
- {
109
- // relayfile cloud mount workspace id — the workspace whose /linear and /slack
110
- // trees the factory reads. Same id the Pear app uses for this workspace.
111
- "workspaceId": "ws_abc123",
112
-
113
- // Which Linear issues to pull. Empty arrays = no filter on that dimension.
114
- // Values are Linear team keys / project names / label names / assignee ids.
115
- "subscription": {
116
- "teams": ["AR"],
117
- "labels": ["pear"],
118
- "projects": [],
119
- "assignees": []
120
- },
121
-
122
- // Issue → repo routing. Precedence (first match wins):
123
- // byLabel → byProject → keywordRules (regex on title/desc) → default
124
- // No match and no default → the issue is escalated, never dispatched.
125
- "repos": {
126
- "byLabel": { "pear": "AgentWorkforce/pear", "cloud": "AgentWorkforce/cloud" },
127
- "byProject": { "Pear": "AgentWorkforce/pear" },
128
- "keywordRules": [{ "pattern": "relayfile|mount", "repo": "AgentWorkforce/relayfile" }],
129
- // Where each repo is checked out locally for the agent to work in.
130
- "clonePaths": { "AgentWorkforce/pear": "/Users/you/Projects/pear" },
131
- "default": "AgentWorkforce/pear"
132
- },
133
-
134
- // SAFETY GATE — an issue is only dispatched if BOTH hold (see below):
135
- "safety": {
136
- "requireTitlePrefix": "[factory-e2e]",
137
- "requireTeamKey": "AR"
138
- },
139
-
140
- "batchSize": 5, // max issues dispatched per cycle
141
- "dispatch": { "maxAttempts": 2, "errorCooldownMs": 60000 },
142
- "mergePolicy": "never", // see mergePolicy note below
143
- "models": { // optional per-role model overrides
144
- "implementer": "claude-opus-4-8",
145
- "reviewer": "claude-opus-4-8",
146
- "triage": "claude-haiku-4-5-20251001"
147
- },
148
- "slack": { "channel": "C0B902XR6PN" }, // optional threaded status updates
149
- "loop": {
150
- "heartbeatPath": "/tmp/factory-run/factory-loop-heartbeat.json",
151
- "registryPath": "/tmp/factory-run/factory-loop-registry.json",
152
- "heartbeatStaleMs": 60000
153
- }
154
- }
154
+ ```bash
155
+ agent-relay fleet serve @agent-relay/factory/node
155
156
  ```
156
157
 
157
- ### Where the values come from
158
-
159
- - **`workspaceId`** *(required)* — the relayfile cloud mount workspace id for this
160
- workspace (the same one the Pear app mounts `/linear` and `/slack` under).
161
- - **`repos.byLabel`** *(required)* — map a Linear label → `owner/repo`. The other
162
- routing maps are optional; **precedence is `byLabel` → `byProject` →
163
- `keywordRules` → `default`**, else the issue is escalated (never dispatched).
164
- - **`repos.clonePaths`** — `owner/repo` → local working-tree path. Without an entry
165
- the agent has nowhere to apply changes for that repo, so set it for every repo
166
- you actually dispatch to.
167
- - **`stateIds`** — the Linear workflow-state UUIDs for `readyForAgent`,
168
- `agentImplementing`, `done`, `inPlanning`, and optionally `humanReview`.
169
- Defaults cover the **AR team's** core states (see
170
- [`src/constants/linear.ts`](src/constants/linear.ts)). `humanReview` is
171
- intentionally omitted from the schema default so legacy configs fall back to
172
- direct-to-Done behavior unless they opt into a review state. If you run against
173
- a different Linear team, override the state UUIDs with that team's values (read
174
- them from the Linear API / the issue JSON's `state.id`).
175
- - **`safety`** — the scope gate (below). Defaults `[factory-e2e]` + team `AR`;
176
- GitHub issue mirrors created by the factory use `[factory]` and are accepted
177
- by the same gate.
178
-
179
- ### The safety gate (what actually gets dispatched)
180
-
181
- `isInFactoryScope` ([`src/safety/factory-scope.ts`](src/safety/factory-scope.ts))
182
- dispatches an issue only when **both** are true:
183
-
184
- 1. The issue **title starts with `safety.requireTitlePrefix`** — exactly
185
- `[factory-e2e]`, or `[factory-e2e] <rest>` by default. Factory-created
186
- GitHub mirrors titled `[factory] <GitHub title>` are also accepted. Anything
187
- else is out of scope.
188
- 2. The issue's **team key equals `safety.requireTeamKey`** (`AR`).
189
-
190
- This is why `factory run-once` may pull issues but dispatch none — they're real
191
- issues that fall outside the gate. Loosen the gate deliberately; it's the primary
192
- guardrail against the factory acting on issues it shouldn't.
193
-
194
- > **⚠️ `[factory-e2e]` is the synthetic self-test prefix — its PRs auto-close.**
195
- > The default `requireTitlePrefix` is `[factory-e2e]`, which is *also* the marker
196
- > the factory uses to identify synthetic E2E soak issues. A `[factory-e2e]`-titled
197
- > issue's PR is **auto-closed, never merged** (`#isSyntheticProbeIssue`) — great
198
- > for self-test soaks, wrong for real work. **For real issues you want to keep,
199
- > set `requireTitlePrefix` to `[factory]`** and title issues `[factory] <task>`;
200
- > reserve `[factory-e2e]` for the soak. The synthetic check matches `[factory-e2e]`
201
- > exactly, so `[factory]` issues are not auto-closed. (Note: `[factory-e2e]` does
202
- > **not** satisfy a `[factory]` gate — the prefixes differ at the `]`/`-`, so don't
203
- > mix them.)
204
-
205
- ### Ingesting GitHub issues (label `factory`)
206
-
207
- The factory also picks up **GitHub issues labeled `factory`** (case-insensitive),
208
- read from the mounted `/github/repos/**` tree (pushed in by the GitHub→relayfile
209
- sync — no API polling). For each, it creates one Linear mirror titled
210
- `[factory] <GitHub title>` in **Ready for Agent**, routes it via `repos.byLabel`,
211
- then dispatches it like any other Linear issue. Mirrors are deduped (one per
212
- GitHub issue) and set to `done` when the GitHub issue closes.
213
-
214
- So to hand the factory a GitHub issue you **just add the `factory` label** — you
215
- do **not** put `[factory]` in the GitHub title (the factory adds that to the
216
- Linear mirror). This is the cross-repo path: label, say, a `relay` repo issue
217
- `factory` and it flows in, routed to `AgentWorkforce/relay`.
158
+ It reads its node config from `./factory.node.json` (or `$FACTORY_NODE_CONFIG`).
159
+ Prefer to build the definition yourself?
218
160
 
219
- | Source | What you do | Result |
220
- |---|---|---|
221
- | Linear (direct) | title `[factory] <task>`, team AR, label = repo, Ready for Agent | dispatched directly |
222
- | GitHub (any configured repo) | add the **`factory` label** | mirrored to a `[factory]` Linear issue, then dispatched |
161
+ ```ts
162
+ import { createFactoryNodeDefinition, readFactoryNodeConfigSync } from '@agent-relay/factory'
223
163
 
224
- ### Other notable fields (defaults in parentheses)
164
+ export default createFactoryNodeDefinition({ config: readFactoryNodeConfigSync() })
165
+ ```
225
166
 
226
- - `mergePolicy` (`never`) — `never` keeps PRs open; `on-green-with-review` enables
227
- autonomous merge on green + approved review. **Stays `never` until the flip is
228
- thrown** (issue #321 §6).
229
- - `loop.heartbeatPath` / `loop.registryPath` / `loop.heartbeatStaleMs` (`60000`) —
230
- daemon/reaper coupling (the reaper must point at the same paths via the same
231
- `--config`).
232
- - `batchSize` (`5`), `dispatch.maxAttempts` (`2`), `dispatch.errorCooldownMs`
233
- (`60000`), `models.{implementer,reviewer,triage}`, `slack.channel`,
234
- `subscription.*`, `liveSubscription.*`, `stateIds.*`.
167
+ ## Configuration
235
168
 
236
- ### Fixture mode (offline testing)
169
+ Pass a JSON file via `--config`. Beyond the two required fields above, useful
170
+ knobs include issue **routing** (`repos.byLabel` / `byProject` / `keywordRules` /
171
+ `default`), the **safety gate** (`safety.requireTitlePrefix`, `safety.requireTeamKey`),
172
+ `mergePolicy` (defaults to `never`), per-role **model** overrides, and an optional
173
+ **Slack** channel for status threads.
237
174
 
238
- If the config (or a `{ "factoryConfig": …, "fixtureFiles": }` wrapper) includes a
239
- `fixtureFiles` map, the CLI swaps in fake fleet + mount clients backed by those files —
240
- no cloud, no real agents. See [`test/fixtures/factory.config.json`](test/fixtures/factory.config.json).
175
+ The full schema every field and default is validated by Zod at load time, so
176
+ an invalid config fails fast with a field-level error. See
177
+ [`src/config/schema.ts`](src/config/schema.ts) for the authoritative reference,
178
+ and [`test/fixtures/factory.config.json`](test/fixtures/factory.config.json) for a
179
+ worked example (including offline fixture mode).
241
180
 
242
181
  ## Notes
243
182
 
244
- - The daemon is headless by design. Pear consumes this package and may expose its
245
- own `pear factory ...` passthrough, but this repository's published CLI is
246
- `factory`.
247
- - Build output lives in `dist/`; `npm pack --dry-run` should include only
248
- `dist/`, `bin/factory.mjs`, `package.json`, and `README.md`.
183
+ - The daemon is headless by design; tools like Pear can consume this package and
184
+ wrap it, but the published CLI is `factory`.
185
+ - The published `dist/` is plain ESM, runnable directly by Node (`node bin/factory.mjs`)
186
+ and importable by ESM consumers.
187
+ - For production operation (the live-daemon + reaper backstop model, heartbeats,
188
+ and `gh`-auth preconditions), see the operations notes alongside the config schema.
@@ -1,9 +1,10 @@
1
- import { createFactory, createFleet, type Capability, type Factory, type FactoryConfig, type FleetBackend, type FleetClient, type GhRunner, type FactoryStateResolution, type MountClient, type ProbeCloser, type RelayfileCloudMountClientConfig, type ResolvedFactoryWorkspace } from '../index';
1
+ import { createFactory, createFleet, ensureRelayBroker, type Capability, type Factory, type FactoryConfig, type FleetBackend, type FleetClient, type GhRunner, type FactoryStateResolution, type MountClient, type ProbeCloser, type RelayfileCloudMountClientConfig, type ResolvedFactoryWorkspace } from '../index.js';
2
2
  interface FleetCliDeps {
3
3
  fleet?: FleetClient;
4
4
  mount?: MountClient;
5
5
  createFactory?: typeof createFactory;
6
6
  createFleet?: typeof createFleet;
7
+ ensureRelayBroker?: typeof ensureRelayBroker;
7
8
  cloudMountFromConfig?: (config?: RelayfileCloudMountClientConfig) => Promise<MountClient>;
8
9
  resolveWorkspace?: () => Promise<ResolvedFactoryWorkspace>;
9
10
  resolveStates?: (mount: MountClient, config: FactoryConfig) => Promise<FactoryStateResolution>;
@@ -49,6 +50,9 @@ type ParsedCommand = {
49
50
  kind: 'factory';
50
51
  action: 'start';
51
52
  mode?: 'live';
53
+ } | {
54
+ kind: 'factory-canary';
55
+ issue: string;
52
56
  } | {
53
57
  kind: 'factory-triage';
54
58
  issue: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}