@bluewombat/runtime 0.8.0 → 0.10.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 +236 -142
- package/SPECS.md +138 -97
- package/dist/cli.js +35 -37
- package/dist/cli.js.map +1 -1
- package/dist/config/agent-shorthand.d.ts +24 -4
- package/dist/config/agent-shorthand.d.ts.map +1 -1
- package/dist/config/agent-shorthand.js +65 -28
- package/dist/config/agent-shorthand.js.map +1 -1
- package/dist/config/defaults.d.ts +0 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +0 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/find-config.d.ts +3 -2
- package/dist/config/find-config.d.ts.map +1 -1
- package/dist/config/find-config.js +4 -4
- package/dist/config/find-config.js.map +1 -1
- package/dist/config/home.d.ts +2 -0
- package/dist/config/home.d.ts.map +1 -1
- package/dist/config/home.js +4 -2
- package/dist/config/home.js.map +1 -1
- package/dist/config/load-config.d.ts +2 -2
- package/dist/config/load-config.d.ts.map +1 -1
- package/dist/config/load-config.js +342 -73
- package/dist/config/load-config.js.map +1 -1
- package/dist/config/parse-args.d.ts +1 -1
- package/dist/config/parse-args.d.ts.map +1 -1
- package/dist/config/parse-args.js +132 -103
- package/dist/config/parse-args.js.map +1 -1
- package/dist/config/types.d.ts +142 -38
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +13 -1
- package/dist/config/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loop/authority.d.ts +41 -5
- package/dist/loop/authority.d.ts.map +1 -1
- package/dist/loop/authority.js +68 -24
- package/dist/loop/authority.js.map +1 -1
- package/dist/loop/context.d.ts +23 -3
- package/dist/loop/context.d.ts.map +1 -1
- package/dist/loop/deliveries.d.ts +16 -16
- package/dist/loop/deliveries.d.ts.map +1 -1
- package/dist/loop/deliveries.js +35 -187
- package/dist/loop/deliveries.js.map +1 -1
- package/dist/loop/describe.d.ts +4 -6
- package/dist/loop/describe.d.ts.map +1 -1
- package/dist/loop/describe.js +2 -2
- package/dist/loop/describe.js.map +1 -1
- package/dist/loop/drive.d.ts +11 -7
- package/dist/loop/drive.d.ts.map +1 -1
- package/dist/loop/drive.js +24 -38
- package/dist/loop/drive.js.map +1 -1
- package/dist/loop/features.d.ts +23 -0
- package/dist/loop/features.d.ts.map +1 -0
- package/dist/loop/features.js +834 -0
- package/dist/loop/features.js.map +1 -0
- package/dist/loop/journal.d.ts +1 -1
- package/dist/loop/journal.js +2 -2
- package/dist/loop/journal.js.map +1 -1
- package/dist/loop/lock.d.ts +1 -1
- package/dist/loop/lock.js +1 -1
- package/dist/loop/open-host.d.ts.map +1 -1
- package/dist/loop/open-host.js +132 -31
- package/dist/loop/open-host.js.map +1 -1
- package/dist/loop/probe.d.ts +1 -1
- package/dist/loop/probe.d.ts.map +1 -1
- package/dist/loop/probe.js +51 -22
- package/dist/loop/probe.js.map +1 -1
- package/dist/loop/process-tree.d.ts +1 -1
- package/dist/loop/process-tree.js +1 -1
- package/dist/loop/report.d.ts +9 -7
- package/dist/loop/report.d.ts.map +1 -1
- package/dist/loop/report.js +49 -106
- package/dist/loop/report.js.map +1 -1
- package/dist/loop/streams.d.ts +5 -5
- package/dist/loop/streams.d.ts.map +1 -1
- package/dist/loop/streams.js +3 -3
- package/dist/loop/streams.js.map +1 -1
- package/dist/loop/tick.d.ts +6 -4
- package/dist/loop/tick.d.ts.map +1 -1
- package/dist/loop/tick.js +50 -78
- package/dist/loop/tick.js.map +1 -1
- package/dist/loop/trace.d.ts +5 -5
- package/dist/loop/trace.d.ts.map +1 -1
- package/dist/loop/trace.js +5 -14
- package/dist/loop/trace.js.map +1 -1
- package/dist/loop/transformers.d.ts +18 -14
- package/dist/loop/transformers.d.ts.map +1 -1
- package/dist/loop/transformers.js +139 -74
- package/dist/loop/transformers.js.map +1 -1
- package/dist/loop/warm.d.ts +3 -3
- package/dist/loop/warm.js +4 -4
- package/dist/loop/warm.js.map +1 -1
- package/dist/loop/work-line.d.ts +1 -1
- package/dist/operator/agent-plugins.d.ts +11 -0
- package/dist/operator/agent-plugins.d.ts.map +1 -1
- package/dist/operator/agent-plugins.js +17 -11
- package/dist/operator/agent-plugins.js.map +1 -1
- package/dist/operator/board.d.ts +3 -8
- package/dist/operator/board.d.ts.map +1 -1
- package/dist/operator/board.js +2 -11
- package/dist/operator/board.js.map +1 -1
- package/dist/operator/cancel.d.ts +2 -2
- package/dist/operator/cancel.js +6 -6
- package/dist/operator/cancel.js.map +1 -1
- package/dist/operator/doctor.d.ts +9 -0
- package/dist/operator/doctor.d.ts.map +1 -1
- package/dist/operator/doctor.js +110 -53
- package/dist/operator/doctor.js.map +1 -1
- package/dist/operator/explain.d.ts +76 -0
- package/dist/operator/explain.d.ts.map +1 -0
- package/dist/operator/explain.js +585 -0
- package/dist/operator/explain.js.map +1 -0
- package/dist/operator/film.d.ts +3 -11
- package/dist/operator/film.d.ts.map +1 -1
- package/dist/operator/film.js +8 -93
- package/dist/operator/film.js.map +1 -1
- package/dist/operator/init.d.ts.map +1 -1
- package/dist/operator/init.js +52 -55
- package/dist/operator/init.js.map +1 -1
- package/dist/operator/live.js +1 -1
- package/dist/operator/live.js.map +1 -1
- package/dist/operator/log.js +12 -6
- package/dist/operator/log.js.map +1 -1
- package/dist/operator/release.d.ts +2 -2
- package/dist/operator/release.js +7 -7
- package/dist/operator/release.js.map +1 -1
- package/dist/operator/run.d.ts +25 -0
- package/dist/operator/run.d.ts.map +1 -0
- package/dist/operator/run.js +70 -0
- package/dist/operator/run.js.map +1 -0
- package/dist/operator/setup.d.ts.map +1 -1
- package/dist/operator/setup.js +22 -16
- package/dist/operator/setup.js.map +1 -1
- package/dist/plugins/manager.d.ts +33 -2
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/plugins/manager.js +67 -0
- package/dist/plugins/manager.js.map +1 -1
- package/package.json +15 -16
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Host: the process you run, `@bluewombat/runtime`, binary `mason`. It listens,
|
|
4
4
|
admits, calls Conductor, and reports. It does not know a tracker: it loads one
|
|
5
|
-
|
|
5
|
+
TicketTracker package and hands it the options it never reads. Contract:
|
|
6
6
|
[SPECS.md](./SPECS.md).
|
|
7
7
|
|
|
8
8
|
## Install
|
|
@@ -24,12 +24,11 @@ npx mason run
|
|
|
24
24
|
npx mason watch
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
A script gets the same result with no questions — `--manager` turns them off:
|
|
27
|
+
A script gets the same result with no questions — `--manager` turns them off (it names the one package that answers both sides):
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
mason init --manager @bluewombat/manager-github \
|
|
31
31
|
--manager-option repo=owner/name \
|
|
32
|
-
--manager-option labels=mason \
|
|
33
32
|
--work-line-stable ./work-line-stable \
|
|
34
33
|
--clone git@github.com:owner/name.git --branch dev
|
|
35
34
|
mason setup --apply
|
|
@@ -52,15 +51,43 @@ package's README — Host does not grow a `--repo` flag for it.
|
|
|
52
51
|
|
|
53
52
|
| Command | Job |
|
|
54
53
|
| --------------- | -------------------------------------------------------------------------------------- |
|
|
55
|
-
| `mason run` | Drain the
|
|
54
|
+
| `mason run` | Drain the TicketTracker, run Conductor, report |
|
|
56
55
|
| `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
|
|
57
56
|
| `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
|
|
58
57
|
| `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
|
|
59
58
|
| `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
|
|
60
59
|
| `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
|
|
61
60
|
| `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
|
|
62
|
-
| `mason
|
|
63
|
-
| `mason
|
|
61
|
+
| `mason explain` | Say what an agent role starts with: prompt, plugins, skills, the project's profile. Read-only, offline. Exit 1 on a setup every attempt would fail on |
|
|
62
|
+
| `mason cancel` | Abandon a Ticket by key, when the tracker cannot say it is gone |
|
|
63
|
+
| `mason release` | Drop a held unit without waiting for the bail clock |
|
|
64
|
+
|
|
65
|
+
### What an agent role starts with
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mason explain builder.producer # one role; no role names all of them
|
|
69
|
+
mason explain --json # the same facts for a script
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It reads the config, the role's prompt file, the plugin folders of the home, and — for the
|
|
73
|
+
project's side — the deck requests and locks of the work line as it was last refreshed. It runs
|
|
74
|
+
no agent and fetches nothing. Each line says where it comes from: `[home]` for the role you wrote
|
|
75
|
+
in the Mason home, `[base]` and `[profile]` for the project's deck setup, read from the
|
|
76
|
+
`ai-plugin.lock.json` the project committed (which lists the skills, agents, hooks and MCP servers
|
|
77
|
+
of each plugin, so the plugin does not have to be fetched to be described).
|
|
78
|
+
|
|
79
|
+
What cannot be known is said, not guessed:
|
|
80
|
+
|
|
81
|
+
- before the work line exists, the project's side is "not known yet";
|
|
82
|
+
- a request no committed lock pins, a prompt that does not place a required `{{placeholder}}` or
|
|
83
|
+
uses one the role does not fill, an unreadable prompt file, a missing plugin folder: `FAIL`
|
|
84
|
+
(exit 1), because every attempt of that role would fail the same way;
|
|
85
|
+
- a lock of a version it does not read, a missing profile, two plugins bringing a skill of the
|
|
86
|
+
same name: `warn`;
|
|
87
|
+
- the key of the agent is never printed, only whether its variable is set;
|
|
88
|
+
- a role written as a plain command is said to be one;
|
|
89
|
+
- the final prompt of a ticket, and what the agent's own tool reads from the checkout, are listed
|
|
90
|
+
as not covered.
|
|
64
91
|
|
|
65
92
|
One `run` per ledger: `run` writes `<ledger>/lock` with its pid and a second
|
|
66
93
|
`run` on the same home refuses to start while that process lives (two runs
|
|
@@ -74,15 +101,33 @@ steps it is handed. A manager without `setupManager` says it has nothing to do.
|
|
|
74
101
|
`init` takes `--manager NAME` (which turns the questions off), `--manager-option KEY=VALUE`,
|
|
75
102
|
`--work-line-stable DIR`, `--clone REMOTE`, `--branch NAME`, `--force` and `--interactive`.
|
|
76
103
|
|
|
104
|
+
`mason run` speaks before it does anything, so a terminal is never blank: it says that it
|
|
105
|
+
is starting, then the config, the trackers, the ledger and the poll interval, then that it is
|
|
106
|
+
opening the host and that it has started. A tick with work prints one line per step; a loop
|
|
107
|
+
with nothing to do says once a minute that it is polling; a tracker that cannot be read is
|
|
108
|
+
named on every poll. The last line says that it stopped and with which exit code, after the
|
|
109
|
+
error message on stderr when there was one.
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
10:28:50 run starting
|
|
113
|
+
10:28:50 config /path/to/home
|
|
114
|
+
10:28:50 trackers features @bluewombat/manager-github, tickets @bluewombat/manager-github
|
|
115
|
+
10:28:50 ledger /path/to/home/.mason/ledger
|
|
116
|
+
10:28:50 poll every 10s
|
|
117
|
+
10:28:50 opening ledger, lock, work line and trackers
|
|
118
|
+
10:28:51 started pid 4242, Ctrl-C to stop
|
|
119
|
+
10:29:51 waiting nothing to do: 6 poll(s) in 60s
|
|
120
|
+
```
|
|
121
|
+
|
|
77
122
|
Leave `mason run` going in one terminal. In another:
|
|
78
123
|
|
|
79
124
|
```bash
|
|
80
125
|
mason watch
|
|
81
126
|
```
|
|
82
127
|
|
|
83
|
-
That is the operator surface: the durable state of every
|
|
128
|
+
That is the operator surface: the durable state of every Ticket, then the film
|
|
84
129
|
of the current step (`submitted` included). It does not talk to the tracker, and
|
|
85
|
-
it cannot `
|
|
130
|
+
it cannot `resume` or claim. `status` is the same snapshot without the
|
|
86
131
|
follow. `--json` prints that snapshot as JSON.
|
|
87
132
|
|
|
88
133
|
When a run only printed `unavailable:<key>`, the reason is already in the
|
|
@@ -95,25 +140,28 @@ mason log --problems
|
|
|
95
140
|
`--all` keeps the idle polls. `--tail N` limits the text film (`0` shows every
|
|
96
141
|
kept line). `--json` prints every kept line; with `--tail`, a truncated reply
|
|
97
142
|
names how many were kept and how many are shown. The closing tally counts each
|
|
98
|
-
distinct failure.
|
|
99
|
-
non-zero exit. If a slot was started with `--transcript-dir`, the newest
|
|
143
|
+
distinct failure. If a slot was started with `--transcript-dir`, the newest
|
|
100
144
|
transcript files are named underneath, with the agent exit and duration read
|
|
101
145
|
from the start of each file.
|
|
102
146
|
|
|
103
|
-
To abandon a
|
|
147
|
+
To abandon a Ticket the tracker can no longer mention (a deleted issue, a
|
|
104
148
|
manager with no `probe`), stop `run` if it holds the ledger lock, then:
|
|
105
149
|
|
|
106
150
|
```bash
|
|
107
151
|
mason cancel github:owner/name#19
|
|
108
152
|
```
|
|
109
153
|
|
|
110
|
-
|
|
111
|
-
|
|
154
|
+
A unit that was stopped (Ctrl-C, a crash) is picked up where it was by the next
|
|
155
|
+
`mason run`: its workspace is kept, the Attempts it finished still count and the
|
|
156
|
+
numbering goes on. To start a unit over instead (the line moved on, the work took
|
|
157
|
+
a wrong turn), stop `run` if it holds the lock, then:
|
|
112
158
|
|
|
113
159
|
```bash
|
|
114
160
|
mason release github:owner/name#19
|
|
115
161
|
```
|
|
116
162
|
|
|
163
|
+
`release` destroys the workspace and forgets it; the next `run` isolates a fresh one.
|
|
164
|
+
|
|
117
165
|
The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
|
|
118
166
|
crash — the ledger is. The file can grow; there is no rotation yet.
|
|
119
167
|
|
|
@@ -122,7 +170,7 @@ crash — the ledger is. The file can grow; there is no rotation yet.
|
|
|
122
170
|
`mason.config.yaml`, found by walking up from the working directory. `--config
|
|
123
171
|
FILE` names another one. Relative paths resolve against the file; relative
|
|
124
172
|
flag paths resolve against the working directory. Flags override the file, and
|
|
125
|
-
`--
|
|
173
|
+
`--feature-tracker-option` and `--ticket-tracker-option` override one option of that side without dropping the others.
|
|
126
174
|
Parsed with the `yaml` package (YAML 1.2 core schema), so a repeated key is
|
|
127
175
|
refused rather than silently keeping the last one, and `no` / `on` / `off`
|
|
128
176
|
stay plain strings — never coerced to a boolean the way YAML 1.1 would. A
|
|
@@ -130,33 +178,32 @@ value that starts with `@`, like every `@bluewombat/...` package name, needs
|
|
|
130
178
|
quotes: `@` is a reserved YAML indicator at the start of a plain scalar.
|
|
131
179
|
|
|
132
180
|
```yaml
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
181
|
+
featureTracker:
|
|
182
|
+
package: "@bluewombat/manager-fake"
|
|
183
|
+
options:
|
|
184
|
+
source: ./.mason/source
|
|
185
|
+
target: ./.mason/threads
|
|
186
|
+
ticketTracker:
|
|
187
|
+
package: "@bluewombat/manager-fake"
|
|
188
|
+
options:
|
|
189
|
+
source: ./.mason/source
|
|
190
|
+
target: ./.mason/threads
|
|
137
191
|
workLine:
|
|
138
192
|
stable: ./work-line-stable
|
|
139
193
|
isolation: "@bluewombat/isolation-copy"
|
|
140
194
|
warm:
|
|
141
195
|
cmd: [pnpm, install, --frozen-lockfile]
|
|
142
196
|
timeoutMs: 600000
|
|
143
|
-
planner:
|
|
144
|
-
cmd:
|
|
145
|
-
- node
|
|
146
|
-
- ./node_modules/@bluewombat/slots/planners/one-subtask.mjs
|
|
147
|
-
timeoutMs: 600000
|
|
148
|
-
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
149
197
|
builder:
|
|
150
198
|
producer:
|
|
151
199
|
cmd: [node, ./mason-builder.mjs]
|
|
152
200
|
timeoutMs: 600000
|
|
153
|
-
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
154
201
|
repair:
|
|
155
202
|
cmd: [node, ./mason-repair.mjs]
|
|
156
203
|
timeoutMs: 600000
|
|
157
|
-
|
|
204
|
+
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
158
205
|
maxAttempts: 3
|
|
159
|
-
|
|
206
|
+
ticket:
|
|
160
207
|
gates: { defaultTimeoutMs: 900000, gates: [] }
|
|
161
208
|
authority:
|
|
162
209
|
enabled: false
|
|
@@ -166,40 +213,45 @@ pollIntervalMs: 10000
|
|
|
166
213
|
|
|
167
214
|
| Field | Meaning |
|
|
168
215
|
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
216
|
+
| `featureTracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
|
|
217
|
+
| `ticketTracker` | Same shape: where the Tickets of a Feature are, and the Authority that holds the lines. May name the same package as `featureTracker`, or another |
|
|
171
218
|
| `workLine.stable` | Existing directory the work folds into. Optional when the manager names a reference work line: Host then keeps its own copy at `<home>/work-line` |
|
|
219
|
+
| `workLine.targetStable` | A second copy of the work line, on the branch a Feature is folded into: what is merged into the Feature when it is brought up to date. Host keeps its own at `<home>/target-line` when the manager names a reference; required with an Authority otherwise |
|
|
172
220
|
| `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
|
|
173
|
-
| `workLine.isolationOptions` | What that strategy is told, unread by Host — like `
|
|
174
|
-
| `workLine.warm` | Optional `cmd` / `timeoutMs`. Runs
|
|
221
|
+
| `workLine.isolationOptions` | What that strategy is told, unread by Host — like a manager's `options`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any |
|
|
222
|
+
| `workLine.warm` | Optional `cmd` / `timeoutMs`. Runs in the workspace after Isolation, before the Builder — typically `pnpm install --frozen-lockfile`. Absent: the Builder gets a cold tree |
|
|
175
223
|
| `workLine.branch` | The work line a fold lands on. Without it, what the reference names, or else whatever is checked out |
|
|
176
224
|
| `authority` | `enabled`, the `publish` / `refresh` slots, and the optional `describe` slot (§ Describer). Absent: the fold is local |
|
|
177
225
|
| `home` | Where Host keeps what is its own — default `./.mason` |
|
|
178
|
-
| `workspaceRoot` | Isolated
|
|
226
|
+
| `workspaceRoot` | Isolated directories (created). Default `<home>/workspaces` |
|
|
179
227
|
| `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
|
|
180
228
|
| `persist` | Package name or path exporting `openPersist({ ledgerRoot })`: how the ledger is stored under that root. Default `@bluewombat/persist-fs`; `@bluewombat/persist-sqlite` ships too. `watch` reads through the same backend |
|
|
181
229
|
| `agents` | Agents a pass names instead of writing `cmd` (§ Agent passes written short) |
|
|
182
|
-
| `
|
|
183
|
-
| `builder.
|
|
184
|
-
| `builder.
|
|
230
|
+
| `builder.tests` | Required. The tests of the task, written before its code by an agent that is not the producer: `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }`, or `{ agent: false }` for none. Its `gates` check them (`tests-red`: they must fail, for the right reason). What it wrote is committed as a checkpoint titled `Tests: …`, which `builder.gates` can keep intact (`tests-frozen`). A ticket the tracker labels as having no tests first (`no-tests` with the GitHub manager) runs no agent and no `tests-red` |
|
|
231
|
+
| `builder.e2e` | Optional. Declares the e2e acceptance check (see § The e2e acceptance check): the pass that makes the Ticket holding the suite, `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }` or `{ cmd, timeoutMs, gates }`. One command for the first pass and for a pass a Gate refused; its own `gates` (they check the form: compiles, one test per manifest id, no application file touched). Nothing is borrowed from `builder.producer`, `builder.repair` or `builder.gates`. No tests are written first for that Ticket. Needs a Feature Gate with `skipWhen: skip-test-e2e`, and the other way round |
|
|
232
|
+
| `builder.producer` | `cmd` / `timeoutMs`. Spawned in the workspace. Exit 0 runs `builder.gates` |
|
|
233
|
+
| `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited: it names its own command. It is judged by the same `builder.gates` as the producer |
|
|
234
|
+
| `builder.gates` | `defaultTimeoutMs` and `gates`: the one Gate list for `builder.producer` and `builder.repair`. `gates` written on either pass is refused, naming this key. Empty: the unit runs ungated |
|
|
185
235
|
| `builder.maxAttempts` | Attempts one Task may start. Default 3 |
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
236
|
+
| `ticket.fix` | `cmd` / `timeoutMs` / `gates`. Spawned when a judgement of the whole refused it — by an Authority, by `ticket.validate`, or by both. Judged by its own `gates` |
|
|
237
|
+
| `ticket.validate` | `cmd` / `timeoutMs` / `gates`. A local, read-only judge of the assembled ticket, run after align, with or without an Authority. A refusal is repaired by `ticket.fix` the same as an Authority's. Judged by its own `gates`, never `builder`'s or `ticket.fix`'s. `validate` is required when a `ticket` block is written, and so is `feature.validate`: `{ agent: false, gates }` turns the agent off and keeps the gates, and `{ agent: false }` with no gates turns the review off — a bare `gates:` counts as none. |
|
|
238
|
+
| `ticket.maxAttempts` | Attempts that one fix may start. Default 3 |
|
|
239
|
+
| `ticket.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled ticket. Empty: the Authority judges alone. Independent of `ticket.fix.gates` / `ticket.validate.gates` — never shared with either |
|
|
190
240
|
| `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
|
|
191
|
-
| `maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `
|
|
241
|
+
| `ticket.maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `ticket.validate`, or by both — one shared budget. Default 3 |
|
|
242
|
+
| `ticket.maxParallel` | How many Tickets of a Project are worked on at once. Default 1: one after the other. Above 1 needs `authority.enabled`; a Ticket waiting on a check or a person holds a place, the others go on. Tickets that wait on another (*Blocked by*) still start only once it is `done` |
|
|
243
|
+
| `feature` | Required with `authority.enabled`. How a Feature's line is delivered to the line it is folded into, by the same steps as a Ticket: the final line is merged into the Feature's work (`workLine.targetStable`, or Host's own copy of the reference), `validate` (same shape as `ticket.validate`) reviews the whole against the Feature's request and its `gates` (at least one) are where the project's checks on the whole line run, a refusal goes to `fix`, the line is published, and `gates` (at least one, the CI) judge it. `maxAttempts` and `maxRefusals` (default 3, same rule as a Ticket's) are its own. Nothing falls back to `ticket`. The merge itself is automatic only with `merge-when-ready`; the delivery to the Authority is for every Feature |
|
|
192
244
|
| `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
|
|
193
|
-
| `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<
|
|
245
|
+
| `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<ticket>/`, named in the journal by a `stream-opened` line. `dir` resolves against the config file; default `<home>/streams`. `keep` is `stdout` / `stderr`, default both. Off unless `enabled` is true: a stream is the Project's own code and prompts in the clear |
|
|
194
246
|
|
|
195
247
|
Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
|
|
196
248
|
`repair` with no command of its own is refused rather than quietly running the
|
|
197
|
-
first-pass agent again. `builder` makes
|
|
198
|
-
and, when that judgement refuses it, runs `
|
|
199
|
-
`
|
|
249
|
+
first-pass agent again. `builder` makes the unit of work. `ticket` judges the whole
|
|
250
|
+
and, when that judgement refuses it, runs `ticket.fix` — not a second first-pass.
|
|
251
|
+
`ticket.validate`, when declared, is a second, local judge of the whole: it
|
|
200
252
|
runs after align, before the Authority sees the work (or before the final
|
|
201
|
-
fold, with none). Its refusal is repaired by `
|
|
202
|
-
Authority's is, and shares the same `maxRefusals` budget.
|
|
253
|
+
fold, with none). Its refusal is repaired by `ticket.fix` exactly as an
|
|
254
|
+
Authority's is, and shares the same `ticket.maxRefusals` budget.
|
|
203
255
|
|
|
204
256
|
Paths a slot **writes** — `--transcript-dir`, `--agent-home` and
|
|
205
257
|
`--plugins-out` — always resolve against the config file's directory, even
|
|
@@ -223,52 +275,47 @@ agents:
|
|
|
223
275
|
denyShell: [] # commands refused to the agent, e.g. [gh, vercel]; ssh and scp always are
|
|
224
276
|
transcripts: ./.mason/transcripts
|
|
225
277
|
pluginsOut: ./.mason/plugins # where deck profiles are built
|
|
226
|
-
planner:
|
|
227
|
-
agent: cursor
|
|
228
|
-
prompt: ./prompts/planner.md
|
|
229
|
-
read: ./.mason/work-line # planner only
|
|
230
|
-
plugins: [./ai-plugins/core/planner]
|
|
231
|
-
profile: planner
|
|
232
|
-
args: [--plan] # the vendor CLI's own flags
|
|
233
|
-
timeoutMs: 600000
|
|
234
278
|
builder:
|
|
235
279
|
producer:
|
|
236
280
|
agent: cursor
|
|
237
281
|
prompt: ./prompts/builder.md
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
homePlugins: [./ai-plugins/core/builder]
|
|
283
|
+
appDeckProfile: .ai/profiles/builder
|
|
284
|
+
args: [--mode, agent] # the vendor CLI's own flags
|
|
240
285
|
timeoutMs: 1200000
|
|
241
286
|
gates: { defaultTimeoutMs: 120000, gates: [] }
|
|
242
287
|
```
|
|
243
288
|
|
|
244
289
|
Host writes the `cmd` it stands for — `node <slots>/<role>.mjs --prompt-file …
|
|
245
|
-
-- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --profile
|
|
290
|
+
-- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --app-deck-profile
|
|
246
291
|
… --plugins-out … --agent-arg …` — with `<slots>` the
|
|
247
292
|
`node_modules/@bluewombat/slots` next to the config. Nothing past the config
|
|
248
|
-
knows a pass was written short. The roles: `
|
|
249
|
-
`builder.repair`, `
|
|
250
|
-
(an argv
|
|
293
|
+
knows a pass was written short. The roles: `builder.producer`,
|
|
294
|
+
`builder.repair`, `ticket.fix`, `ticket.validate`, and `authority.describe`
|
|
295
|
+
(an argv, or `{ agent, prompt?, homePlugins?, appDeckProfile?, args? }`).
|
|
251
296
|
|
|
252
297
|
| Key | Meaning |
|
|
253
298
|
| --------- | --------------------------------------------------------------------------------------------------------- |
|
|
254
299
|
| `agent` | A name `agents` declares. With `cmd` beside it, the pass is refused |
|
|
255
300
|
| `prompt` | `--prompt-file` of the role slot. Absent: the role's built-in template |
|
|
256
301
|
| `rules` | `--rules-file` of the role slot |
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `profile` | The project's deck profile for this role. Needs the agent's `pluginsOut`. See the slots' § Agents/Plugins |
|
|
302
|
+
| `homePlugins` | Plugin folders of the Mason home, each a `--plugin-dir` |
|
|
303
|
+
| `appDeckProfile` | The app's deck profile for this role, as a path: `[<app dir>/].ai/profiles/<name>`. Needs the agent's `pluginsOut`. See the slots' § Agents/Plugins |
|
|
260
304
|
| `args` | Appended to the agent's own `args`, each an `--agent-arg` |
|
|
261
305
|
|
|
262
|
-
|
|
263
|
-
base plugins
|
|
306
|
+
An `appDeckProfile` is read in the app, not in the Mason home: the role gets the
|
|
307
|
+
app's base plugins, that profile's plugins and its `prompt.md`, as the app's
|
|
308
|
+
committed deck locks pin them. deck keeps profiles in `<app>/.ai/profiles/<name>`,
|
|
309
|
+
so the path has that shape; `<app dir>` is where the app's `.ai/` is in the
|
|
310
|
+
checkout (`.` by default, `apps/web` in a monorepo). It stays inside the checkout.
|
|
264
311
|
`mason doctor` checks those locks in the work line without the network.
|
|
265
312
|
|
|
266
|
-
Every tracker field lives in `
|
|
313
|
+
Every tracker field lives in the `options` of its side. Host does not read them.
|
|
267
314
|
|
|
268
315
|
### Choosing `pollIntervalMs`
|
|
269
316
|
|
|
270
317
|
The sleep between two ticks, and nothing else. While a tick drives work — a
|
|
271
|
-
|
|
318
|
+
Builder, a validate — it runs to the end; the interval does not
|
|
272
319
|
touch it. It only sets how long `mason run` takes to notice:
|
|
273
320
|
|
|
274
321
|
- a new issue, or an edit to one;
|
|
@@ -276,7 +323,7 @@ touch it. It only sets how long `mason run` takes to notice:
|
|
|
276
323
|
that submitted (`docs/DECISIONS.md`, the Authority re-drive row), so on a
|
|
277
324
|
work line without CI the whole interval is waited for a Gate that answers in
|
|
278
325
|
two seconds;
|
|
279
|
-
- a `
|
|
326
|
+
- a `resume`, or an issue closed to cancel.
|
|
280
327
|
|
|
281
328
|
**What one tick costs**, with `@bluewombat/manager-github` (read in
|
|
282
329
|
`src/loop/tick.ts` `runOnce`, `src/loop/probe.ts` `abandonGone`, the manager's
|
|
@@ -287,8 +334,8 @@ against the token's budget.
|
|
|
287
334
|
| The tick | GitHub REST calls | Counted when nothing moved | Besides |
|
|
288
335
|
| --------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ---------------------------------------------- |
|
|
289
336
|
| Nothing to do | 1: the issues updated since the Cursor, 100 per page | 0 (304) | none: no fetch |
|
|
290
|
-
| A
|
|
291
|
-
| A
|
|
337
|
+
| A Ticket in flight | + 1 per Ticket that can still be abandoned: is its issue still there | 0 (304) | a `git fetch` of the work line copy (not REST) |
|
|
338
|
+
| A Ticket `submitted` | + the judgement: `ci-green` asks for the pull request, its check runs and its statuses (about 3) | about 3: `ci-green` is its own process and keeps no `etag` | |
|
|
292
339
|
|
|
293
340
|
Reports to the tracker (a comment, a label move: 2 to 4 calls each) happen
|
|
294
341
|
when the work moves, not per tick, and each changes the listing: the read
|
|
@@ -298,7 +345,7 @@ after a report is a full one.
|
|
|
298
345
|
(GitHub's primary rate limit). It is shared by every caller using that token:
|
|
299
346
|
the reports, `ci-green`, `gh`, and every other Mason home pointed at it.
|
|
300
347
|
|
|
301
|
-
| `pollIntervalMs` | Idle, nothing moved | A
|
|
348
|
+
| `pollIntervalMs` | Idle, nothing moved | A Ticket `submitted` (about 3 counted a tick) | Worst case, every read changed (about 5) |
|
|
302
349
|
| ---------------- | ------------------- | ---------------------------------------------- | ---------------------------------------- |
|
|
303
350
|
| 30000 | ~0 /h | 360 /h (7 %) | 600 /h (12 %) |
|
|
304
351
|
| 15000 | ~0 /h | 720 /h (14 %) | 1,200 /h (24 %) |
|
|
@@ -312,14 +359,14 @@ listing. Without conditional reads an idle tick counted one call (360 /h at
|
|
|
312
359
|
**The floor.** An idle tick takes 1 to 1.5 s, almost all of it the `listen`
|
|
313
360
|
call (measured on `tilap/orangemonkey-site`, October 2026). Below 2 to 3 s
|
|
314
361
|
the loop never sleeps. Below 10 s nothing a person notices is gained: a
|
|
315
|
-
|
|
362
|
+
Ticket takes minutes, and 10 s against 30 s saves about 20 s per Ticket,
|
|
316
363
|
mostly the post-Submission wait (30 s of a 5 min 40 s run, measured). Going
|
|
317
|
-
lower spends the token while a
|
|
364
|
+
lower spends the token while a Ticket is submitted, for seconds nobody
|
|
318
365
|
notices.
|
|
319
366
|
|
|
320
|
-
**So:** 10000 for one Mason home on a token. Idle
|
|
367
|
+
**So:** 10000 for one Mason home on a token. Idle does not spend the
|
|
321
368
|
budget, so the quota is not what holds the floor: usefulness is, and a
|
|
322
|
-
submitted
|
|
369
|
+
submitted Ticket still is (its judgement counts). Several homes on the same
|
|
323
370
|
token add up: multiply the `submitted` and worst-case columns by their count
|
|
324
371
|
and stay under half the budget. The journal is not a reason either way: quiet
|
|
325
372
|
ticks are folded into one `idle` line a minute (`src/loop/journal.ts`).
|
|
@@ -330,7 +377,7 @@ settle or its timeout ends the attempt.
|
|
|
330
377
|
|
|
331
378
|
## Slots
|
|
332
379
|
|
|
333
|
-
Commands mason spawns and never imports: the
|
|
380
|
+
Commands mason spawns and never imports: the Builder, each Gate,
|
|
334
381
|
and — when an Authority is enabled — the Publisher and the Refresher. Every one
|
|
335
382
|
reads argv and answers with **one JSON object on stdout**; an exit code alone is
|
|
336
383
|
not the contract. mason does not pick a producer, and a slot may be written in
|
|
@@ -341,10 +388,6 @@ the shipped roles and agents (Cursor CLI, Claude Code), the shipped Gates, and
|
|
|
341
388
|
their options. What follows is what Host guarantees them.
|
|
342
389
|
|
|
343
390
|
```json
|
|
344
|
-
"planner": {
|
|
345
|
-
"cmd": ["node", "./node_modules/@bluewombat/slots/planners/one-subtask.mjs"],
|
|
346
|
-
"timeoutMs": 600000
|
|
347
|
-
},
|
|
348
391
|
"builder": {
|
|
349
392
|
"producer": {
|
|
350
393
|
"cmd": [
|
|
@@ -362,8 +405,8 @@ composition with `--` is the same.
|
|
|
362
405
|
|
|
363
406
|
### Builder
|
|
364
407
|
|
|
365
|
-
Spawned in the
|
|
366
|
-
`--
|
|
408
|
+
Spawned in the workspace with `--intention`, `--id`, `--attempt`, and
|
|
409
|
+
`--context`. On a retry it also gets `--report` and
|
|
367
410
|
`--report-from`: what refused the last pass, and which Gate said it. Exit 0
|
|
368
411
|
means the Gates may run.
|
|
369
412
|
|
|
@@ -373,19 +416,19 @@ hides the change from them.
|
|
|
373
416
|
|
|
374
417
|
The config always names a producer (and a repair). Implementer can still run a
|
|
375
418
|
Task as its Gate sequence alone — that is the first judgement of the assembled
|
|
376
|
-
|
|
419
|
+
ticket, which makes nothing.
|
|
377
420
|
|
|
378
|
-
###
|
|
421
|
+
### Ticket fix
|
|
379
422
|
|
|
380
|
-
Spawned in the
|
|
423
|
+
Spawned in the ticket workspace when a judgement of the whole refused it
|
|
381
424
|
(`--report`, `--report-from`). Same argv as a Builder otherwise. Host hands
|
|
382
|
-
Implementer `
|
|
425
|
+
Implementer `ticket.fix` as that pass's only agent: there is no first-pass
|
|
383
426
|
producer at this moment. The refusal it repairs may have come from an
|
|
384
|
-
Authority or from `
|
|
427
|
+
Authority or from `ticket.validate`.
|
|
385
428
|
|
|
386
|
-
###
|
|
429
|
+
### Ticket validate
|
|
387
430
|
|
|
388
|
-
Spawned in the
|
|
431
|
+
Spawned in the ticket workspace after align, with or without an Authority —
|
|
389
432
|
`--intention`, `--id`, `--attempt`, `--context`. No `--report`: it is a
|
|
390
433
|
read-only review, not a repair, and takes nothing a producer would. Optional.
|
|
391
434
|
Its verdict is not the exit code alone: the slot reads what the agent itself
|
|
@@ -394,24 +437,22 @@ that completes cleanly but finds a problem is still a refusal.
|
|
|
394
437
|
|
|
395
438
|
### Gates
|
|
396
439
|
|
|
397
|
-
|
|
398
|
-
`builder.repair
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
440
|
+
Four sequences: `builder.gates`, shared by `builder.producer` and
|
|
441
|
+
`builder.repair` (a repair is judged by exactly what judged the first try),
|
|
442
|
+
then one per assembly role — `ticket.fix`, `ticket.validate` — plus
|
|
443
|
+
`ticket.gates` for the one judgement-only pass with no producer of its own
|
|
444
|
+
(re-checking the assembled ticket; `ci-green` reads the checks the work line
|
|
445
|
+
ran on what the Publisher placed, so it belongs there). The assembly lists are
|
|
446
|
+
not shared with each other or with the builder, and no dispatch chooses between
|
|
447
|
+
two buckets: a check right for one pass is not necessarily right for another,
|
|
448
|
+
even within the same stage. `gates` written on `builder.producer` or
|
|
449
|
+
`builder.repair` is refused, and the message names `builder.gates`.
|
|
450
|
+
|
|
451
|
+
Implementer's Gates are told `--stage unit` or `--stage ticket` alongside
|
|
452
|
+
`--id`, `--attempt`, `--gate-id` and `--intention`.
|
|
409
453
|
Most have no use for it. `workspace-changed` does: "nothing changed" means
|
|
410
454
|
nothing was done in a unit, and is the correct outcome for an assembly that
|
|
411
|
-
needed nothing.
|
|
412
|
-
`--key` and `--gate-id` — no Attempt counter, no `--stage`, no
|
|
413
|
-
`--definition-of-done` — so a Gate written for Implementer's shape is not a
|
|
414
|
-
drop-in for `planner.gates`.
|
|
455
|
+
needed nothing.
|
|
415
456
|
|
|
416
457
|
`init` writes an empty list on `builder.producer` and `builder.repair`; add a
|
|
417
458
|
Gate when the Project has a check.
|
|
@@ -419,15 +460,15 @@ Gate when the Project has a check.
|
|
|
419
460
|
Every child carries its own ceiling and no other. A Gate entry may name a
|
|
420
461
|
`timeoutMs`; without one it takes its sequence's `defaultTimeoutMs`, and a Gate
|
|
421
462
|
that ends up with neither is refused when the config loads. Nothing is derived
|
|
422
|
-
from what another child left behind, so a slow producer
|
|
463
|
+
from what another child left behind, so a slow producer cannot leave its
|
|
423
464
|
Gates a millisecond to answer in.
|
|
424
465
|
|
|
425
|
-
What bounds
|
|
426
|
-
`builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.
|
|
466
|
+
What bounds the unit is therefore what you wrote:
|
|
467
|
+
`builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.gates timeouts)`
|
|
427
468
|
for a first pass, and the same shape with `repair` in place of `producer` once
|
|
428
469
|
a Gate has refused one.
|
|
429
470
|
A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
|
|
430
|
-
is bounded the same way from `
|
|
471
|
+
is bounded the same way from `ticket.maxAttempts` and `ticket.fix`.
|
|
431
472
|
|
|
432
473
|
```json
|
|
433
474
|
{
|
|
@@ -443,16 +484,63 @@ is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
|
|
|
443
484
|
}
|
|
444
485
|
```
|
|
445
486
|
|
|
446
|
-
###
|
|
487
|
+
### The e2e acceptance check
|
|
488
|
+
|
|
489
|
+
A Project proves each Feature by an acceptance suite, run once on the assembled Feature
|
|
490
|
+
line, when its config says so, by a pair:
|
|
491
|
+
|
|
492
|
+
```yaml
|
|
493
|
+
builder:
|
|
494
|
+
e2e: # makes the Ticket that holds the suite (label `e2e` on the tracker)
|
|
495
|
+
agent: claude
|
|
496
|
+
prompt: ./prompts/e2e.md # {{task}} is the scenarios; {{report}}, {{refused_by}}, {{refusal}} on a refused pass
|
|
497
|
+
timeoutMs: 1800000
|
|
498
|
+
gates: # form only: it compiles, one test per manifest id, no application file touched
|
|
499
|
+
gates:
|
|
500
|
+
- { id: e2e-form, argv: [...], timeoutMs: 120000 }
|
|
501
|
+
feature:
|
|
502
|
+
validate:
|
|
503
|
+
agent: false
|
|
504
|
+
gates:
|
|
505
|
+
defaultTimeoutMs: 120000
|
|
506
|
+
gates:
|
|
507
|
+
- id: e2e
|
|
508
|
+
skipWhen: skip-test-e2e # Host does not run this Gate for a Feature that carries the label
|
|
509
|
+
timeoutMs: 720000
|
|
510
|
+
argv:
|
|
511
|
+
- node
|
|
512
|
+
- ./node_modules/@bluewombat/slots/gates/e2e.mjs
|
|
513
|
+
- --up
|
|
514
|
+
- pnpm e2e:up
|
|
515
|
+
- --ready
|
|
516
|
+
- pnpm e2e:ready
|
|
517
|
+
- --run
|
|
518
|
+
- pnpm e2e
|
|
519
|
+
- --down
|
|
520
|
+
- pnpm e2e:down
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`skipWhen` is the one condition Host reads for a Gate, on a Feature's Gates only
|
|
524
|
+
(`feature.validate`, `feature.gates`, `feature.fix`); its one value today is
|
|
525
|
+
`skip-test-e2e`, whatever the tracker calls that label. A Gate left out is not a Gate that
|
|
526
|
+
passed: the journal gets `gate-skipped` and the trace says "`e2e` not run: the Feature carries
|
|
527
|
+
skip-test-e2e". The Gate knows nothing of the skip and no environment variable carries it.
|
|
528
|
+
|
|
529
|
+
The tracker side: a Ticket carrying the e2e label (`e2eLabel` of the TicketTracker manager) is
|
|
530
|
+
made by `builder.e2e` and has no tests written first; a Feature carrying the skip label
|
|
531
|
+
(`skipTestE2eLabel` of the FeatureTracker manager) has that Ticket cancelled, with a line in
|
|
532
|
+
the trace. The label is read again until the Feature is submitted. A Project that declares the
|
|
533
|
+
check refuses a Feature that holds neither an e2e Ticket nor the skip label, or whose e2e
|
|
534
|
+
Ticket is not blocked by every other Ticket, as `invalid`, with the reason as a comment.
|
|
447
535
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
536
|
+
What the Gate proves is a red/green verdict; what the operator reads is its text in the
|
|
537
|
+
journal (`gate-finished`) — a red suite is also in the reason of the refusal the fix is
|
|
538
|
+
given and in the escalation. A passing suite's text is not shown on the pull request: see
|
|
539
|
+
RESERVATIONS I17.
|
|
452
540
|
|
|
453
541
|
### Describer
|
|
454
542
|
|
|
455
|
-
How a delivered
|
|
543
|
+
How a delivered ticket describes itself — the words the manager puts on the
|
|
456
544
|
Submission and on the fold — is the Project's, not Host's. Three levels, one
|
|
457
545
|
key, `authority.describe`:
|
|
458
546
|
|
|
@@ -460,10 +548,10 @@ key, `authority.describe`:
|
|
|
460
548
|
| -------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------- |
|
|
461
549
|
| not set | the manager's own default: the issue's title, its body as the human wrote it | none |
|
|
462
550
|
| `messages/conventional.mjs` | `<type>: <title>` (`--scope`, `--type`), the body as written | none |
|
|
463
|
-
| `messages/git-producer.mjs -- <agent>` | an agent reads the git diff and writes both under `--rules-file` — the team's guideline | one agent run per
|
|
551
|
+
| `messages/git-producer.mjs -- <agent>` | an agent reads the git diff and writes both under `--rules-file` — the team's guideline | one agent run per ticket |
|
|
464
552
|
|
|
465
553
|
It is an argv like `publish` and `refresh`, bounded by the global `timeoutMs`.
|
|
466
|
-
Host spawns it once per
|
|
554
|
+
Host spawns it once per ticket, in the ticket workspace, when the work is
|
|
467
555
|
offered — with `--id`, `--title`, `--intention` and `--target` (the work line
|
|
468
556
|
it lands on) — and takes one line back: `{ "subject": "…", "body": "…" }`.
|
|
469
557
|
Host hands that description to the manager as is; what it becomes is the
|
|
@@ -471,12 +559,9 @@ manager's business (for GitHub: the squash's title and message, the pull
|
|
|
471
559
|
request's first paragraph). A slot that fails is skipped and the journal says
|
|
472
560
|
so.
|
|
473
561
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
else its first sentence cut at a word, no markdown. That headline is also what
|
|
478
|
-
the tracker's Plan, the progress comments and the Submission's change list
|
|
479
|
-
show, so a Planner is asked to write one.
|
|
562
|
+
The commit that takes up what the Builder made is worded from the title of the
|
|
563
|
+
issue (or the headline of its intention: a line, a blank line, the rest — the
|
|
564
|
+
shape of a commit), else its first sentence cut at a word, no markdown.
|
|
480
565
|
|
|
481
566
|
### Write another slot
|
|
482
567
|
|
|
@@ -486,8 +571,8 @@ otherwise repeat is [`@bluewombat/slot-kit`](../slot-kit/README.md).
|
|
|
486
571
|
|
|
487
572
|
## Submissions
|
|
488
573
|
|
|
489
|
-
Without an Authority, a finished
|
|
490
|
-
and that is the end of it. With one, the assembled
|
|
574
|
+
Without an Authority, a finished ticket is folded straight into WorkLineStable
|
|
575
|
+
and that is the end of it. With one, the assembled ticket is **offered** to an
|
|
491
576
|
outside judge first, and only an `accepted` verdict lets it into the work line.
|
|
492
577
|
|
|
493
578
|
It is **declared**, never inferred:
|
|
@@ -522,34 +607,37 @@ strategy does not know, a branch the remote does not have, a `workLine.branch`
|
|
|
522
607
|
that disagrees with the reference, or a `workLine.stable` that is not a copy of
|
|
523
608
|
it each stop `mason run` at boot, and `mason doctor` reports the same offline
|
|
524
609
|
(`reference`, `work line`). Without a reference, `workLine.stable` is required
|
|
525
|
-
and is the operator's directory
|
|
610
|
+
and is the operator's directory.
|
|
526
611
|
|
|
527
612
|
```
|
|
528
|
-
assembled → align →
|
|
529
|
-
|
|
613
|
+
assembled → align → recheck (builder.gates again, on the aligned work)
|
|
614
|
+
conflict or failed → ticket.fix, then align again
|
|
615
|
+
→ ticket.validate (optional, local)
|
|
616
|
+
refused → ticket.fix, then align again, recheck, validate again
|
|
530
617
|
accepted → Publisher places the work → manager opens the Submission
|
|
531
618
|
→ submitted, and the run comes back to it on each poll
|
|
532
|
-
|
|
533
|
-
refused →
|
|
534
|
-
accepted → the
|
|
619
|
+
ticket.gates judge what was published
|
|
620
|
+
refused → ticket.fix, then the same Submission is offered again
|
|
621
|
+
accepted → has the line moved on since the align? yes: align again
|
|
622
|
+
no: the Authority folds it → done (a refused fold also aligns again)
|
|
535
623
|
```
|
|
536
624
|
|
|
537
|
-
Neither half judges: that is what `
|
|
538
|
-
and folds; a Gate says whether the fold may happen. `
|
|
625
|
+
Neither half judges: that is what `ticket.gates` are for. The Authority places
|
|
626
|
+
and folds; a Gate says whether the fold may happen. `ticket.validate`, when
|
|
539
627
|
declared, judges before either half — the same verdict has the same fate
|
|
540
628
|
whether or not there is an Authority to offer the work to afterwards. After the
|
|
541
629
|
Authority folds, Host brings its copy of the work line up to date with a
|
|
542
630
|
fast-forward — the work line moved where the Authority is, and the next
|
|
543
631
|
Isolation must start from that version, not from a copy left behind.
|
|
544
632
|
|
|
545
|
-
A refusal is not a second failure path: its report reaches `
|
|
546
|
-
exactly as a Gate's report reaches `builder.repair`. After `maxRefusals`
|
|
547
|
-
refusals the
|
|
633
|
+
A refusal is not a second failure path: its report reaches `ticket.fix`
|
|
634
|
+
exactly as a Gate's report reaches `builder.repair`. After `ticket.maxRefusals`
|
|
635
|
+
refusals the ticket escalates with that report as its Trace, and `done` names
|
|
548
636
|
the Submission rather than a directory on the machine that ran.
|
|
549
637
|
|
|
550
638
|
## Write another manager
|
|
551
639
|
|
|
552
|
-
Host loads whatever `
|
|
640
|
+
Host loads whatever `featureTracker.package` and `ticketTracker.package` name, as long as it exports `createManager`. The
|
|
553
641
|
contract and a worked example are in
|
|
554
642
|
[`@bluewombat/manager-kit`](../manager-kit/README.md). Nothing in Host changes.
|
|
555
643
|
|
|
@@ -559,14 +647,20 @@ contract and a worked example are in
|
|
|
559
647
|
import { openHost } from "@bluewombat/runtime";
|
|
560
648
|
|
|
561
649
|
const host = await openHost({
|
|
562
|
-
|
|
563
|
-
|
|
650
|
+
featureTracker: {
|
|
651
|
+
package: "@bluewombat/manager-fake",
|
|
652
|
+
options: { source: "./.mason/source", target: "./.mason/threads" },
|
|
653
|
+
},
|
|
654
|
+
ticketTracker: {
|
|
655
|
+
package: "@bluewombat/manager-fake",
|
|
656
|
+
options: { source: "./.mason/source", target: "./.mason/threads" },
|
|
657
|
+
},
|
|
564
658
|
// …paths and slots
|
|
565
659
|
});
|
|
566
660
|
await host.runOnce();
|
|
567
661
|
```
|
|
568
662
|
|
|
569
|
-
`
|
|
663
|
+
Each `package` also takes an already-loaded module, which is how a test drives Host
|
|
570
664
|
without resolving a package.
|
|
571
665
|
|
|
572
666
|
## Stack
|
|
@@ -618,7 +712,7 @@ change that breaks it does not merge.
|
|
|
618
712
|
Host's code names no plugin. A manager, an isolation strategy, a persistence
|
|
619
713
|
backend, the shipped slots: each is loaded by the name a config gives, through
|
|
620
714
|
`plugins/`, never imported. The names Host falls back to — the two isolation
|
|
621
|
-
strategies `init` writes, the default `persist
|
|
715
|
+
strategies `init` writes, the default `persist` — are
|
|
622
716
|
strings in `config/defaults.ts` and nowhere else; `check-host.mjs` refuses an
|
|
623
717
|
import of any of them and a spelled-out name anywhere else. Host's
|
|
624
718
|
`package.json` declares exactly those defaults, so a config `init` wrote runs
|
|
@@ -667,6 +761,6 @@ src/
|
|
|
667
761
|
config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
|
|
668
762
|
plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
|
|
669
763
|
loop/ open-host (composition), tick (the loop), deliveries, drive, probe, report, authority, work-line, cursor, lock, journal (the film), streams (what a child said), trace, transformers
|
|
670
|
-
operator/ init, setup, doctor, live (watch / status), log, film, cancel, release, board, prompt, git-remote
|
|
764
|
+
operator/ run, init, setup, doctor, explain, live (watch / status), log, film, cancel, release, board, prompt, git-remote
|
|
671
765
|
fixtures/ stand-in slots for the loop's tests
|
|
672
766
|
```
|