@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.
- package/README.md +145 -205
- package/dist/cli/fleet.d.ts +5 -1
- package/dist/cli/fleet.d.ts.map +1 -1
- package/dist/cli/fleet.js +152 -27
- package/dist/cli/fleet.js.map +1 -1
- package/dist/config/schema.d.ts +4 -4
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +13 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/constants/linear.d.ts +2 -0
- package/dist/constants/linear.d.ts.map +1 -1
- package/dist/constants/linear.js +6 -0
- package/dist/constants/linear.js.map +1 -1
- package/dist/dispatch/templates.d.ts +18 -2
- package/dist/dispatch/templates.d.ts.map +1 -1
- package/dist/dispatch/templates.js +18 -10
- package/dist/dispatch/templates.js.map +1 -1
- package/dist/fleet/create-fleet.d.ts +3 -2
- package/dist/fleet/create-fleet.d.ts.map +1 -1
- package/dist/fleet/create-fleet.js +3 -2
- package/dist/fleet/create-fleet.js.map +1 -1
- package/dist/fleet/ensure-relay-broker.d.ts +24 -0
- package/dist/fleet/ensure-relay-broker.d.ts.map +1 -0
- package/dist/fleet/ensure-relay-broker.js +56 -0
- package/dist/fleet/ensure-relay-broker.js.map +1 -0
- package/dist/fleet/internal-fleet-client.d.ts +8 -3
- package/dist/fleet/internal-fleet-client.d.ts.map +1 -1
- package/dist/fleet/internal-fleet-client.js +32 -5
- package/dist/fleet/internal-fleet-client.js.map +1 -1
- package/dist/fleet/relay-fleet-client.d.ts +90 -13
- package/dist/fleet/relay-fleet-client.d.ts.map +1 -1
- package/dist/fleet/relay-fleet-client.js +496 -20
- package/dist/fleet/relay-fleet-client.js.map +1 -1
- package/dist/github/index.d.ts +4 -4
- package/dist/github/index.js +2 -2
- package/dist/github/probe-closer.d.ts +1 -1
- package/dist/github/probe-closer.js +1 -1
- package/dist/index.d.ts +33 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -15
- package/dist/index.js.map +1 -1
- package/dist/linear/state-resolver.d.ts +3 -2
- package/dist/linear/state-resolver.d.ts.map +1 -1
- package/dist/linear/state-resolver.js +74 -10
- package/dist/linear/state-resolver.js.map +1 -1
- package/dist/mount/local-mount-preflight.d.ts +1 -0
- package/dist/mount/local-mount-preflight.d.ts.map +1 -1
- package/dist/mount/local-mount-preflight.js +64 -12
- package/dist/mount/local-mount-preflight.js.map +1 -1
- package/dist/mount/relayfile-binary.d.ts +2 -0
- package/dist/mount/relayfile-binary.d.ts.map +1 -1
- package/dist/mount/relayfile-binary.js +39 -5
- package/dist/mount/relayfile-binary.js.map +1 -1
- package/dist/mount/relayfile-cloud-mount-client.d.ts +2 -2
- package/dist/mount/relayfile-cloud-mount-client.js +1 -1
- package/dist/node/factory-node.d.ts +59 -0
- package/dist/node/factory-node.d.ts.map +1 -0
- package/dist/node/factory-node.js +351 -0
- package/dist/node/factory-node.js.map +1 -0
- package/dist/node/factory.node.d.ts +3 -0
- package/dist/node/factory.node.d.ts.map +1 -0
- package/dist/node/factory.node.js +5 -0
- package/dist/node/factory.node.js.map +1 -0
- package/dist/orchestrator/batch-tracker.d.ts +2 -2
- package/dist/orchestrator/factory.d.ts +5 -2
- package/dist/orchestrator/factory.d.ts.map +1 -1
- package/dist/orchestrator/factory.js +413 -59
- package/dist/orchestrator/factory.js.map +1 -1
- package/dist/orchestrator/index.d.ts +4 -4
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/index.js +3 -3
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/process-identity.d.ts +1 -1
- package/dist/orchestrator/reaper.d.ts +3 -3
- package/dist/orchestrator/reaper.js +3 -3
- package/dist/ports/fleet.d.ts +8 -2
- package/dist/ports/fleet.d.ts.map +1 -1
- package/dist/ports/index.d.ts +4 -4
- package/dist/ports/state.d.ts +3 -3
- package/dist/ports/writeback.d.ts +1 -1
- package/dist/safety/factory-scope.d.ts +2 -2
- package/dist/state/in-memory-state-store.d.ts +1 -1
- package/dist/state/in-memory-state-store.js +1 -1
- package/dist/subscriptions/event-client.d.ts +1 -1
- package/dist/subscriptions/event-client.js +2 -2
- package/dist/subscriptions/globs.js +1 -1
- package/dist/subscriptions/index.d.ts +7 -7
- package/dist/subscriptions/index.js +4 -4
- package/dist/subscriptions/linear-filter.d.ts +1 -1
- package/dist/subscriptions/linear-filter.js +1 -1
- package/dist/subscriptions/specs.d.ts +1 -1
- package/dist/subscriptions/specs.js +2 -2
- package/dist/testing/fakes.d.ts +1 -1
- package/dist/testing/fakes.js +1 -1
- package/dist/testing/fakes.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/triage/heuristic.d.ts +8 -4
- package/dist/triage/heuristic.d.ts.map +1 -1
- package/dist/triage/heuristic.js +70 -6
- package/dist/triage/heuristic.js.map +1 -1
- package/dist/triage/index.d.ts +6 -6
- package/dist/triage/index.d.ts.map +1 -1
- package/dist/triage/index.js +4 -4
- package/dist/triage/index.js.map +1 -1
- package/dist/triage/llm.d.ts +1 -1
- package/dist/triage/llm.d.ts.map +1 -1
- package/dist/triage/llm.js +5 -3
- package/dist/triage/llm.js.map +1 -1
- package/dist/triage/schema.d.ts +133 -29
- package/dist/triage/schema.d.ts.map +1 -1
- package/dist/triage/schema.js +6 -3
- package/dist/triage/schema.js.map +1 -1
- package/dist/triage/tiered.d.ts +1 -1
- package/dist/triage/tiered.d.ts.map +1 -1
- package/dist/triage/tiered.js +9 -2
- package/dist/triage/tiered.js.map +1 -1
- package/dist/types.d.ts +10 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/writeback/github.d.ts +2 -2
- package/dist/writeback/github.js +1 -1
- package/dist/writeback/index.d.ts +5 -5
- package/dist/writeback/index.js +3 -3
- package/dist/writeback/linear.d.ts +3 -3
- package/dist/writeback/linear.d.ts.map +1 -1
- package/dist/writeback/linear.js +57 -14
- package/dist/writeback/linear.js.map +1 -1
- package/dist/writeback/slack.d.ts +1 -1
- package/dist/writeback/slack.js +2 -2
- package/package.json +10 -2
- package/scripts/com.agentrelay.factory-canary.plist.example +53 -0
- package/scripts/factory-canary.sh +89 -0
package/README.md
CHANGED
|
@@ -1,248 +1,188 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agent Workforce Factory
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
39
|
+
## Install
|
|
18
40
|
|
|
19
41
|
```bash
|
|
20
|
-
|
|
42
|
+
npm install @agent-relay/factory
|
|
21
43
|
```
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
45
|
-
| `factory loop` |
|
|
46
|
-
| `factory start --mode live` | Long-lived daemon
|
|
47
|
-
| `factory status` | Print
|
|
48
|
-
| `factory
|
|
49
|
-
| `factory
|
|
50
|
-
| `factory
|
|
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
|
|
56
|
-
`--
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
### Scheduled sync-fidelity canary
|
|
61
117
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
130
|
+
## Tell it what to work on
|
|
72
131
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[`
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
has a default:
|
|
148
|
+
## Run it as a fleet node (optional)
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
+
export default createFactoryNodeDefinition({ config: readFactoryNodeConfigSync() })
|
|
165
|
+
```
|
|
225
166
|
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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.
|
package/dist/cli/fleet.d.ts
CHANGED
|
@@ -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;
|
package/dist/cli/fleet.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"AAKA,OAAO,EAKL,aAAa,EACb,WAAW,
|
|
1
|
+
{"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../src/cli/fleet.ts"],"names":[],"mappings":"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"}
|