@seanyao/roll 4.630.2 → 4.702.2
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/CHANGELOG.md +64 -0
- package/README.md +65 -56
- package/conventions/global/AGENTS.md +8 -7
- package/dist/roll.mjs +12909 -8532
- package/docs/INDEX.md +32 -0
- package/docs/architecture.md +444 -0
- package/docs/difftest-freeze-paradigm.md +113 -0
- package/docs/live-console.md +203 -0
- package/docs/manifesto.md +65 -0
- package/docs/migration/role-taxonomy-v4.md +60 -0
- package/docs/verification.md +83 -0
- package/guide/INDEX.md +86 -0
- package/guide/assets/layouts/cards-2.png +0 -0
- package/guide/assets/layouts/cards-3.png +0 -0
- package/guide/assets/layouts/cards-4.png +0 -0
- package/guide/assets/layouts/compare.png +0 -0
- package/guide/assets/layouts/highlight.png +0 -0
- package/guide/assets/layouts/pipeline.png +0 -0
- package/guide/assets/layouts/plain.png +0 -0
- package/guide/assets/layouts/quote.png +0 -0
- package/guide/assets/layouts/timeline.png +0 -0
- package/guide/en/acceptance-evidence.md +231 -0
- package/guide/en/ai-agents.md +185 -0
- package/guide/en/backlog-github-sync.md +108 -0
- package/guide/en/changelog.md +66 -0
- package/guide/en/configuration.md +112 -0
- package/guide/en/consistency.md +58 -0
- package/guide/en/conventions.md +113 -0
- package/guide/en/dream.md +121 -0
- package/guide/en/faq.md +855 -0
- package/guide/en/feedback.md +31 -0
- package/guide/en/getting-started.md +103 -0
- package/guide/en/installation.md +86 -0
- package/guide/en/legacy-onboarding.md +195 -0
- package/guide/en/loop-data-layout.md +256 -0
- package/guide/en/loop-driven-architecture.md +186 -0
- package/guide/en/loop.md +1324 -0
- package/guide/en/methodology.md +715 -0
- package/guide/en/migration-2.0.md +154 -0
- package/guide/en/overview.md +190 -0
- package/guide/en/pairing.md +151 -0
- package/guide/en/patterns/README.md +76 -0
- package/guide/en/patterns/graft-pattern.md +110 -0
- package/guide/en/patterns/replant-pattern.md +114 -0
- package/guide/en/patterns/seed-pattern.md +132 -0
- package/guide/en/peer.md +71 -0
- package/guide/en/pr-review.md +62 -0
- package/guide/en/practices/engineering-common-sense.md +395 -0
- package/guide/en/pricing.md +116 -0
- package/guide/en/project-setup.md +126 -0
- package/guide/en/roll-doc-audit.md +98 -0
- package/guide/en/skills.md +206 -0
- package/guide/en/test-isolation.md +51 -0
- package/guide/en/testing/quality-rubric.md +340 -0
- package/guide/en/testing.md +123 -0
- package/guide/en/tools.md +173 -0
- package/guide/skills.md +30 -0
- package/guide/zh/acceptance-evidence.md +194 -0
- package/guide/zh/ai-agents.md +170 -0
- package/guide/zh/backlog-github-sync.md +105 -0
- package/guide/zh/changelog.md +57 -0
- package/guide/zh/configuration.md +99 -0
- package/guide/zh/consistency.md +48 -0
- package/guide/zh/conventions.md +96 -0
- package/guide/zh/dream.md +97 -0
- package/guide/zh/faq.md +773 -0
- package/guide/zh/feedback.md +30 -0
- package/guide/zh/getting-started.md +96 -0
- package/guide/zh/installation.md +83 -0
- package/guide/zh/legacy-onboarding.md +192 -0
- package/guide/zh/loop-data-layout.md +236 -0
- package/guide/zh/loop-driven-architecture.md +186 -0
- package/guide/zh/loop.md +1124 -0
- package/guide/zh/methodology.md +702 -0
- package/guide/zh/migration-2.0.md +154 -0
- package/guide/zh/overview.md +186 -0
- package/guide/zh/pairing.md +117 -0
- package/guide/zh/patterns/README.md +74 -0
- package/guide/zh/patterns/graft-pattern.md +108 -0
- package/guide/zh/patterns/replant-pattern.md +112 -0
- package/guide/zh/patterns/seed-pattern.md +130 -0
- package/guide/zh/peer.md +63 -0
- package/guide/zh/pr-review.md +54 -0
- package/guide/zh/practices/engineering-common-sense.md +393 -0
- package/guide/zh/pricing.md +97 -0
- package/guide/zh/project-setup.md +114 -0
- package/guide/zh/roll-doc-audit.md +90 -0
- package/guide/zh/skills.md +191 -0
- package/guide/zh/test-isolation.md +46 -0
- package/guide/zh/testing/quality-rubric.md +284 -0
- package/guide/zh/testing.md +116 -0
- package/guide/zh/tools.md +173 -0
- package/package.json +4 -1
- package/skills/README.md +1 -0
- package/skills/roll-.qa/SKILL.md +1 -1
- package/skills/roll-.review/SKILL.md +1 -1
- package/skills/roll-build/SKILL.md +1 -1
- package/skills/roll-build/references/full-contract.md +16 -13
- package/skills/roll-design/SKILL.md +3 -3
- package/skills/roll-design/references/full-contract.md +17 -13
- package/skills/roll-fix/SKILL.md +1 -1
- package/skills/roll-fix/references/full-contract.md +13 -10
- package/skills/roll-peer/SKILL.md +1 -1
- package/skills/roll-prime/SKILL.md +77 -0
- package/skills/roll-prime/references/explorer-annex.md +39 -0
- package/skills/roll-prime/references/supervisor-prompt.md +165 -0
- package/skills/route-cases/skills.json +10 -0
- package/template/AGENTS.md +3 -1
package/guide/en/loop.md
ADDED
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
# roll loop — Autonomous BACKLOG Executor
|
|
2
|
+
|
|
3
|
+
`roll loop` schedules and manages the autonomous execution of BACKLOG stories.
|
|
4
|
+
When enabled, it wakes up on a configurable schedule (within your active
|
|
5
|
+
window), picks the top pending story, and executes it — committing changes in
|
|
6
|
+
TCR micro-steps.
|
|
7
|
+
|
|
8
|
+
`roll loop on` is the explicit switch into **autonomous** mode. `roll loop off`
|
|
9
|
+
or `roll loop pause` returns the project to **guided** operation, where the
|
|
10
|
+
owner asks `roll supervisor next/why` and starts any long-running Story work
|
|
11
|
+
explicitly. `roll loop resume` switches back to autonomous without changing
|
|
12
|
+
agent bindings.
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
1. Reads `BACKLOG.md`, picks the highest-priority `📋 Todo` item.
|
|
17
|
+
2. Marks it `🔨 In Progress` and commits.
|
|
18
|
+
3. Invokes `$roll-build <story-id>` or `$roll-fix <bug-id>`.
|
|
19
|
+
4. On success: marks `✅ Done`, commits, appends to `runs.jsonl`.
|
|
20
|
+
5. On failure: reverts to `📋 Todo`, writes an `ALERT.md` entry.
|
|
21
|
+
|
|
22
|
+
Loop runs inside a **tmux session** named `roll-loop-<project-slug>`.
|
|
23
|
+
When not muted, a terminal window pops up automatically so you can watch in real time.
|
|
24
|
+
|
|
25
|
+
## Scheduling
|
|
26
|
+
|
|
27
|
+
Loop is scheduled via **launchd** (macOS). By default, every hour at a
|
|
28
|
+
project-derived minute (different projects get different offsets to avoid
|
|
29
|
+
collisions).
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Active window: 0–24 (always on — the shipped default)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
By default the active window is the full day (`loop_active_start=0`,
|
|
36
|
+
`loop_active_end=24`), so loop fires on every scheduled tick. Narrow it to a
|
|
37
|
+
working window with `roll config loop-window` — e.g. `roll config loop-window
|
|
38
|
+
10-18` for 10am – 6pm. Outside the configured window, loop silently exits
|
|
39
|
+
without doing anything.
|
|
40
|
+
|
|
41
|
+
## Configuring the schedule
|
|
42
|
+
|
|
43
|
+
Instead of hand-editing `~/.roll/config.yaml` and `.roll/local.yaml` and then
|
|
44
|
+
hoping the launchd plist picks up your change, use the `roll config` command
|
|
45
|
+
family. Each write lands in the right yaml file **and** automatically
|
|
46
|
+
regenerates the runner, re-bootstraps the launchd plist, and is reflected
|
|
47
|
+
immediately by `roll loop status` — no manual reload step.
|
|
48
|
+
|
|
49
|
+
| Command | What it sets |
|
|
50
|
+
|---------|--------------|
|
|
51
|
+
| `roll config loop-window <start>-<end>` | loop active window hours (`loop_active_start` + `loop_active_end`) |
|
|
52
|
+
| `roll config loop-schedule <period>[/<offset>]` | fire interval (`loop_schedule.period_minutes` + `offset_minute`) |
|
|
53
|
+
| `roll config dream-time <HH:MM>` | dream daily fire time (`loop_dream_hour` + `loop_dream_minute`) |
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
roll config loop-window 9-18 # active 9am – 6pm; start < end, both in [0,24]
|
|
57
|
+
roll config loop-schedule 30 # fire every 30 minutes (period in [1,1440])
|
|
58
|
+
roll config loop-schedule 30/7 # every 30 minutes, offset :07 (offset in [0, period-1])
|
|
59
|
+
roll config dream-time 03:20 # dream fires at exactly 03:20; HH in [0,23], MM in [0,59]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Reading the current value.** Run any facade with no value to print the
|
|
63
|
+
effective combination and where it comes from:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
roll config loop-window # loop-window: 0-24 (from default)
|
|
67
|
+
roll config dream-time # dream-time: 03:20 (from ~/.roll/config.yaml)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Range validation.** Out-of-range or non-numeric input is rejected with a
|
|
71
|
+
locale-selected error and exit code 2. For example, with `ROLL_LANG=en`,
|
|
72
|
+
`roll config loop-window 9-25` prints `loop-window end must be <= 24`.
|
|
73
|
+
|
|
74
|
+
**`--global` vs `--project`.** Writes default to `--project` (`.roll/local.yaml`,
|
|
75
|
+
this project only). Pass `--global` to write `~/.roll/config.yaml` as the
|
|
76
|
+
default for every project that has no project-level override.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
roll config dream-time 03:20 # this project (.roll/local.yaml)
|
|
80
|
+
roll config dream-time 03:20 --global # all projects (~/.roll/config.yaml)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Auto-reload.** After writing a schedule key, `roll config` re-installs the
|
|
84
|
+
launchd plists for loop / pr / dream so the change fires on the next
|
|
85
|
+
window. If reload fails (e.g. in a sandbox), the yaml is still the source of
|
|
86
|
+
truth — run `roll loop on` to apply it by hand. See `roll config --help` for
|
|
87
|
+
the full key list and ranges.
|
|
88
|
+
|
|
89
|
+
### Per-project frequency
|
|
90
|
+
|
|
91
|
+
Set the fire interval with one command:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
roll config loop-schedule 30 # every 30 minutes (period 1–1440, any interval)
|
|
95
|
+
roll config loop-schedule 45 # every 45 minutes (no longer restricted to divisors of 60)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This writes a `loop_schedule` block to `.roll/local.yaml`:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
loop_schedule:
|
|
102
|
+
period_minutes: 30 # 1-1440 (any minute interval)
|
|
103
|
+
offset_minute: 7 # 0–(period-1) (deprecated; for backward compat only)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `period_minutes` — how often loop fires. Any value 1–1440.
|
|
107
|
+
- `offset_minute` — (deprecated since US-LOOP-032) no longer affects timing.
|
|
108
|
+
Kept for backward compat.
|
|
109
|
+
|
|
110
|
+
If no `.roll/local.yaml` or no `loop_schedule` block is present, Roll falls
|
|
111
|
+
back to the global value (set with `roll config loop-schedule … --global`), or
|
|
112
|
+
derives a per-project default from the project path hash.
|
|
113
|
+
|
|
114
|
+
`roll loop status` and `roll loop on` display the actual schedule frequency
|
|
115
|
+
so you can verify it at a glance. An invalid value (e.g. `period_minutes: 0` or
|
|
116
|
+
`1441`) is rejected at write time with exit code 2.
|
|
117
|
+
|
|
118
|
+
### Global defaults (backward-compatible)
|
|
119
|
+
|
|
120
|
+
For a single global default across all projects, write with `--global`:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
roll config loop-window 10-18 --global # active window for every project
|
|
124
|
+
roll config loop-schedule 60 --global # default interval for every project
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(Agent selection is no longer a global config key. It is resolved from Machine
|
|
128
|
+
Scope and Project Scope agent files. See [Autonomous Role Resolution](#autonomous-role-resolution).)
|
|
129
|
+
|
|
130
|
+
Project-level `.roll/local.yaml` always takes priority over the global default.
|
|
131
|
+
|
|
132
|
+
## Subcommands
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
roll loop on # Install launchd scheduler (loop + pr + dream)
|
|
136
|
+
roll loop off # Remove launchd scheduler
|
|
137
|
+
|
|
138
|
+
roll loop now # Run one cycle immediately (same as launchd fires)
|
|
139
|
+
roll loop test # Quick smoke test: verify tmux/popup/stream chain works
|
|
140
|
+
|
|
141
|
+
roll loop status # Show scheduler state and current loop state
|
|
142
|
+
roll loop watch # Default owner view: phase, quiet time, TCR count, last signal, plus live activity
|
|
143
|
+
roll loop watch -n 50 # Look back 50 lines before following (default 200; 'all' = whole log)
|
|
144
|
+
roll loop watch --events # Compact developer event stream from .roll/loop/events.ndjson
|
|
145
|
+
roll loop watch --raw-events # Raw JSON event stream for audit/debug only
|
|
146
|
+
roll loop watch --verbose # Also show the raw agent transcript (default folds it away)
|
|
147
|
+
roll loop watch --attach # Read-only attach to the loop's tmux observe window (tmux attach -r)
|
|
148
|
+
roll loop go # Run goal mode manually for all backlog until complete/pause/guardrail
|
|
149
|
+
roll loop go --epic <name> # Limit the goal to one epic
|
|
150
|
+
roll loop go --cards US-1,FIX-2 # Limit the goal to selected cards
|
|
151
|
+
roll loop go --budget 10 # Stop conservatively when goal cost reaches $10
|
|
152
|
+
roll loop go --usage-threshold 0.85 # Pause when five-hour or weekly usage reaches this ratio
|
|
153
|
+
roll loop go --no-wait # On usage limit, pause and return instead of waiting for reset
|
|
154
|
+
roll loop go --for 5h # Stop after the current cycle once the timebox is reached
|
|
155
|
+
roll loop go --max-cycles 3 # Stop after this many cycles
|
|
156
|
+
roll loop go --review <auto|hetero|self|off> # Set the final review policy
|
|
157
|
+
roll loop goal # Show persisted goal status, scope, review mode, usage, limits, and safety gate
|
|
158
|
+
|
|
159
|
+
roll loop runs # Show last 10 run summaries (story IDs, TCR count, duration, slowest phase)
|
|
160
|
+
roll loop runs 20 # Show last 20 runs
|
|
161
|
+
roll loop runs --all # Show all projects' run history
|
|
162
|
+
roll loop runs --detail <cycle_id> # Phase Breakdown panel for a single cycle
|
|
163
|
+
|
|
164
|
+
roll loop story <ID> # Per-story rollup: cycles, span, duration, tokens, cost, PRs
|
|
165
|
+
roll loop story <ID> --json # Machine-readable form for scripting
|
|
166
|
+
|
|
167
|
+
roll loop eval # Objective result-eval trend over the last 14 scored cycles
|
|
168
|
+
roll loop eval 30 # Widen the window to the last 30 scored cycles
|
|
169
|
+
roll loop signals # Surface repeated low-score patterns as improvement signals
|
|
170
|
+
roll loop signals --streak 4 # Require 4 consecutive low cycles before firing
|
|
171
|
+
|
|
172
|
+
roll loop watch # Recommended daily live view
|
|
173
|
+
tmux attach -t roll-loop-<project-slug> # Read-only tmux observe window; use when you want the pane
|
|
174
|
+
roll loop mute # Suppress auto-attach popup (loop still runs in tmux)
|
|
175
|
+
roll loop unmute # Re-enable auto-attach popup
|
|
176
|
+
|
|
177
|
+
roll loop pause # Pause scheduling (keep plist, skip execution)
|
|
178
|
+
roll loop resume # Resume scheduling after pause
|
|
179
|
+
|
|
180
|
+
roll loop reset # Clear loop state (start fresh on next fire)
|
|
181
|
+
|
|
182
|
+
roll loop gc # GC orphan slugs, tmp debris, expired backups (default: keep 30 days)
|
|
183
|
+
roll loop gc --dry-run # Preview what would be removed without deleting
|
|
184
|
+
roll loop gc --keep-days 14 # Override retention (also: loop_gc.retention_days in .roll/local.yaml)
|
|
185
|
+
# See guide/en/loop-data-layout.md for the full gc manual
|
|
186
|
+
|
|
187
|
+
# loop-related branches: `git ls-remote --heads origin 'loop/*'` (the `branches` subcommand is retired)
|
|
188
|
+
|
|
189
|
+
roll loop events # Show last 20 cycle events
|
|
190
|
+
roll loop events 50 # Show last 50 events
|
|
191
|
+
|
|
192
|
+
roll agent # Show scopes, roles, agent pool, and legacy inputs
|
|
193
|
+
roll agent list # Show agents installed on this machine
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Goal Mode vs Scheduled Mode
|
|
197
|
+
|
|
198
|
+
`roll loop go` is a manual goal session, not a launchd scheduler tick. While it
|
|
199
|
+
runs, Roll holds `.roll/loop/go.lock`; scheduled ticks yield when they see that
|
|
200
|
+
lock, record `goal:tick_skipped`, and do not start another `roll loop run-once`.
|
|
201
|
+
|
|
202
|
+
Goal mode can run when the scheduler is off because it starts its own session
|
|
203
|
+
and does not depend on launchd. For paused projects, run `roll loop resume`
|
|
204
|
+
first: the `PAUSE-<slug>` marker is still respected at cycle boundaries.
|
|
205
|
+
|
|
206
|
+
### Goal Mode Safety Gates
|
|
207
|
+
|
|
208
|
+
Budget and run limits are explicit per `roll loop go`. `--budget`,
|
|
209
|
+
`--max-cycles`, and `--for` apply to THIS invocation only; omitting one means no
|
|
210
|
+
limit for this run — Roll never silently inherits a budget or cap from a prior
|
|
211
|
+
session's persisted goal, so a flagless `roll loop go` can neither be capped nor
|
|
212
|
+
bricked by a limit you set days ago. Scope (`--epic`/`--cards`) and `--review`
|
|
213
|
+
still persist when unspecified, because they are the goal's identity, not a
|
|
214
|
+
per-run safety knob.
|
|
215
|
+
|
|
216
|
+
`roll loop go` enforces safety only at cycle boundaries. `--budget <usd>` uses
|
|
217
|
+
the effective run cost ledger and moves the goal to `budget_limited` when the
|
|
218
|
+
budget is reached. An idle or aborted cycle that ran no agent counts as a known
|
|
219
|
+
$0, not as an unknown-cost row. Only a row where an agent actually executed but
|
|
220
|
+
left no parseable usage is recorded as unknown; those still stop conservatively
|
|
221
|
+
rather than being counted as zero. Usage headroom is checked against five-hour
|
|
222
|
+
and weekly windows; by default Roll pauses at 85% and waits for the reset
|
|
223
|
+
window, while `--no-wait` leaves the goal paused for the owner. The recovery
|
|
224
|
+
wait is bounded — a hung usage API cannot stall the session forever; on timeout
|
|
225
|
+
Roll records a `usage_wait_timeout` audit event and leaves the goal paused.
|
|
226
|
+
`--for <duration>` is a wall-clock box: the in-flight cycle finishes, then the
|
|
227
|
+
goal pauses with reason `timebox`.
|
|
228
|
+
|
|
229
|
+
Each safety trip records `goal:gate_tripped`, and `roll loop goal` shows the
|
|
230
|
+
last safety gate reading.
|
|
231
|
+
|
|
232
|
+
### `roll loop goal` Fields
|
|
233
|
+
|
|
234
|
+
`roll loop goal` is the read face for `.roll/loop/goal.yaml` plus the latest
|
|
235
|
+
goal events. The key fields are:
|
|
236
|
+
|
|
237
|
+
| Field | Meaning |
|
|
238
|
+
|-------|---------|
|
|
239
|
+
| `Status` | `active`, `paused`, `budget_limited`, or `complete`. |
|
|
240
|
+
| `Scope` | All backlog, one epic, or an explicit card list. |
|
|
241
|
+
| `Review` | Completion review policy: `auto`, `hetero`, `self`, or `off`. |
|
|
242
|
+
| `Usage` | Goal cycle count, effective cost, and unknown-cost-row count. |
|
|
243
|
+
| `Limits` | Explicit `--budget`, `--max-cycles`, and `--for` settings. |
|
|
244
|
+
| `Safety gate` | The latest budget, usage, or timebox trip and its reading. |
|
|
245
|
+
| `Last decision` | Why the goal continued, paused, became budget-limited, or completed. |
|
|
246
|
+
|
|
247
|
+
When `auto` final review degrades to same-provider review, the status view
|
|
248
|
+
shows the recorded degradation reason from `goal:review_degraded`. When a goal
|
|
249
|
+
cannot complete, `Last decision` carries the unmet truth or review reason.
|
|
250
|
+
|
|
251
|
+
### Goal Mode Final Review
|
|
252
|
+
|
|
253
|
+
`roll loop go` persists its goal state in `.roll/loop/goal.yaml` and runs a
|
|
254
|
+
final review gate before a goal can become `complete`. The default policy is
|
|
255
|
+
`--review auto`: Roll tries heterogeneous reviewers in ranked order, and records
|
|
256
|
+
`goal:review_degraded` when it must fall back to self review because every
|
|
257
|
+
heterogeneous candidate failed or because only one provider is installed.
|
|
258
|
+
|
|
259
|
+
Final review uses the same structured adapter as `-peer` skill. The
|
|
260
|
+
`goal:final_review` event records reviewer agent, provider, command family,
|
|
261
|
+
verdict, findings, timeout/error state, duration, and transcript/evidence
|
|
262
|
+
paths when available. A transient review crash rotates to the next ranked
|
|
263
|
+
candidate; if every candidate fails Roll records the real error reason on the
|
|
264
|
+
`ERROR` verdict and raises an ALERT, instead of collapsing into a reasonless
|
|
265
|
+
generic error.
|
|
266
|
+
|
|
267
|
+
Use `--review hetero` when completion must fail closed unless a heterogeneous
|
|
268
|
+
reviewer is installed. Use `--review self` to allow same-provider review. Use
|
|
269
|
+
`--review off` only as an explicit operator waiver; Roll skips the gate but
|
|
270
|
+
still records a `goal:final_review` event with `verdict: SKIPPED`.
|
|
271
|
+
|
|
272
|
+
## Autonomous Role Resolution
|
|
273
|
+
|
|
274
|
+
Loop resolves agents through the scoped Agent model:
|
|
275
|
+
|
|
276
|
+
```text
|
|
277
|
+
Scope -> Role -> Binding -> Agent -> optional Model
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Machine Scope lives in `~/.roll/agents.yaml`; Project Scope lives in
|
|
281
|
+
`.roll/agents.yaml`. A loop cycle resolves `story.execute` for the Builder and
|
|
282
|
+
`story.evaluate` for review/score, using inherited machine declarations when the
|
|
283
|
+
project says `inherits: machine`.
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
schema: roll-agents/v1
|
|
287
|
+
scope: project
|
|
288
|
+
inherits: machine
|
|
289
|
+
defaults:
|
|
290
|
+
story:
|
|
291
|
+
roles:
|
|
292
|
+
execute:
|
|
293
|
+
kind: select
|
|
294
|
+
from: [kimi, codex, pi]
|
|
295
|
+
require: [execute]
|
|
296
|
+
strategy: first-available
|
|
297
|
+
evaluate:
|
|
298
|
+
kind: select
|
|
299
|
+
from: [claude, codex, kimi, pi, agy, reasonix]
|
|
300
|
+
require: [evaluate]
|
|
301
|
+
strategy: health-aware
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`roll agent` shows the effective Machine Scope, Project Scope, resolved roles,
|
|
305
|
+
candidate pool, runtime health notes, and any legacy compatibility inputs. Use
|
|
306
|
+
`roll agent migrate --dry-run` to preview conversion from old agent files.
|
|
307
|
+
|
|
308
|
+
Runtime health is not static policy. Auth, network, VPN, account, or missing
|
|
309
|
+
binary failures skip a candidate for the current resolution and are recorded as
|
|
310
|
+
runtime facts. If no candidate remains, loop pauses and writes an ALERT instead
|
|
311
|
+
of silently rewriting the pool.
|
|
312
|
+
|
|
313
|
+
For open casting, `health-aware` ranks the same visible pool across Builder,
|
|
314
|
+
Designer, Evaluator, and Peer Reviewer roles. `roll supervisor route --role
|
|
315
|
+
builder --story <id> [--json]` exposes the trace: candidates, ranked scores,
|
|
316
|
+
warnings, skipped runtime facts, selected agent, source binding, recent-use
|
|
317
|
+
input, and capability/health ranking.
|
|
318
|
+
|
|
319
|
+
### Agent toolchain health check (US-V4-022)
|
|
320
|
+
|
|
321
|
+
Before scheduling, Supervisor also classifies agent toolchain health signals
|
|
322
|
+
from the durable event stream. It distinguishes auth blocks, network blocks,
|
|
323
|
+
setup/skill-root pollution, and worktree permission failures. Pollution signals
|
|
324
|
+
are routed to the delta team as a FIX rather than misclassified as auth failures.
|
|
325
|
+
Use `roll supervisor health` for the dedicated board, or read the summary from
|
|
326
|
+
`roll supervisor next` / `roll supervisor why`.
|
|
327
|
+
|
|
328
|
+
### Agent self-downgrade (too_big verdict)
|
|
329
|
+
|
|
330
|
+
The picked agent runs a **pre-flight self-check** (in `roll-build` / `roll-fix`
|
|
331
|
+
SKILL.md). When it decides the story is over capacity it emits:
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
verdict: too_big
|
|
335
|
+
reason: est_min=20 > pi.max=8
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
并触发 self-downgrade 流程:调 `roll-design --from-story <id>` 把故事拆成
|
|
339
|
+
chain_depth + 1 的子故事写回 BACKLOG,原故事翻 🚫 Hold,cycle 干净退出。
|
|
340
|
+
下一轮 loop 接力第一个更小的子故事。
|
|
341
|
+
|
|
342
|
+
The chain is capped at **2 auto re-splits**. The third would be refused and a
|
|
343
|
+
`StorySplitCapHit` ALERT is written, forcing human triage instead of letting
|
|
344
|
+
the chain grow indefinitely.
|
|
345
|
+
|
|
346
|
+
## Execution profiles (standard / verified / designed)
|
|
347
|
+
|
|
348
|
+
Role resolution above chooses the concrete Agent and optional Model for the
|
|
349
|
+
`execute` and `evaluate` roles. Separately, Roll chooses an **execution profile** per
|
|
350
|
+
Story — the cheapest *sufficient* role pipeline for that Story's risk and ROI.
|
|
351
|
+
You do not declare it; it is selected once at cycle start from the story's risk
|
|
352
|
+
signals and recorded in an `execution:profile` event. These are **not
|
|
353
|
+
user-facing "team shapes"** — they are risk/ROI tiers:
|
|
354
|
+
|
|
355
|
+
- **`standard` = execute only** — low-risk, local scope, clear acceptance, low
|
|
356
|
+
evidence risk (a copy fix, a small parser bug, an internal rename).
|
|
357
|
+
- **`verified` = execute → evaluate** — user-visible behavior, screenshot/visual
|
|
358
|
+
evidence required, or a history of weak/missing evidence. An independent
|
|
359
|
+
`evaluate` role (fresh session) judges the delivery; blocking review, score, and
|
|
360
|
+
attest stay three separate dimensions.
|
|
361
|
+
- **`designed` = Designer -> Builder -> Evaluator** — the risk is doing the *wrong*
|
|
362
|
+
work: unclear requirements, cross-module change, or truth/release/routing/state
|
|
363
|
+
semantics. A design artifact is written before `execute`; `evaluate` maps
|
|
364
|
+
design-contract-vs-delivered. evaluate → execute repair rounds are strictly bounded
|
|
365
|
+
(max rounds, repeated-finding signature, budget, timeout) and escalate on a
|
|
366
|
+
bound trip.
|
|
367
|
+
|
|
368
|
+
Roles hand off through artifacts only (design contract, builder evidence,
|
|
369
|
+
eval-report) — never a shared raw session. Project-level coordination across
|
|
370
|
+
Stories (ordering, conflicts, budget, release readiness) belongs to the
|
|
371
|
+
**Supervisor** (`roll supervisor`), not to any single Story's execution.
|
|
372
|
+
|
|
373
|
+
When the owner asks to clear the backlog, Supervisor uses a backlog-clearing
|
|
374
|
+
standard rather than a fix-only queue. The default scope is every live non-Hold
|
|
375
|
+
`FIX-*`, `US-*`, and `REFACTOR-*` row. Before starting another card it reconciles
|
|
376
|
+
backlog truth, open PRs, CI/evaluator gates, recent cycle endings, manual-merge
|
|
377
|
+
PRs, and `.roll` meta state. Each card gets a fresh Builder and, when its
|
|
378
|
+
execution profile requires it, an independent Evaluator/Scorer from the current
|
|
379
|
+
Agent roster. Repeated failure, zero TCR, missing evidence, parser failure,
|
|
380
|
+
auth/permission blocks, or `[roll:manual-merge]` PRs stop scheduling until the
|
|
381
|
+
owner action shown by `roll supervisor status/next/why` is resolved.
|
|
382
|
+
|
|
383
|
+
## Cycle Role Visibility
|
|
384
|
+
|
|
385
|
+
A v4 cycle is a multi-agent collaboration: a Builder writes the code, one or
|
|
386
|
+
more Peer Reviewers re-check the risky diff, an Evaluator/Scorer judges the
|
|
387
|
+
delivery, and the Attest Gate decides whether the result is admissible. The
|
|
388
|
+
underlying truth lives in `events.ndjson` and the peer/evidence artifacts, but
|
|
389
|
+
you should never have to grep those by hand to answer the first-run question:
|
|
390
|
+
**who was Builder and who was Evaluator?**
|
|
391
|
+
|
|
392
|
+
Three surfaces answer that question.
|
|
393
|
+
|
|
394
|
+
### `roll loop cycle <id> --roles`
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
roll loop cycle <id> --roles # human-readable execution cast for one cycle
|
|
398
|
+
roll loop cycle <id> --roles --json # the same facts as cycle-role-summary.v1 JSON
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
The roles view renders the full role chain — Builder, Peer Review,
|
|
402
|
+
Evaluator / Score, and Gates — for a single cycle:
|
|
403
|
+
|
|
404
|
+
```text
|
|
405
|
+
# Cycle Role Summary — 20260629-112437-39253
|
|
406
|
+
|
|
407
|
+
Story: US-TASK-001
|
|
408
|
+
Execution profile: standard
|
|
409
|
+
|
|
410
|
+
## Builder
|
|
411
|
+
- pi / deepseek-v4-pro
|
|
412
|
+
- log: .roll/loop/cycle-logs/20260629-112437-39253.agent.log
|
|
413
|
+
|
|
414
|
+
## Peer Review
|
|
415
|
+
- reasonix: accepted verdict=refine findings=0
|
|
416
|
+
- kimi: returned reviewed, no structured verdict accepted
|
|
417
|
+
- codex: returned reviewed, no structured verdict accepted
|
|
418
|
+
|
|
419
|
+
## Evaluator / Score
|
|
420
|
+
- reasonix: accepted score=10 verdict=good
|
|
421
|
+
- agy: failed unparseable (control characters before SCORE)
|
|
422
|
+
- kimi: selected, no accepted score
|
|
423
|
+
|
|
424
|
+
## Gates
|
|
425
|
+
- peer: consulted
|
|
426
|
+
- attest: produced
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The command reads the cached summary artifact first and rebuilds from
|
|
430
|
+
`events.ndjson` when it is missing or corrupt, so it works for both fresh and
|
|
431
|
+
archived cycles.
|
|
432
|
+
|
|
433
|
+
### The `summary.md` / `summary.json` artifacts
|
|
434
|
+
|
|
435
|
+
Every cycle writes the same role cast to disk so a delivery report or a teammate
|
|
436
|
+
can read it without re-running the CLI:
|
|
437
|
+
|
|
438
|
+
```text
|
|
439
|
+
.roll/loop/cycle-logs/<cycle-id>/summary.md # the markdown shown above
|
|
440
|
+
.roll/loop/cycle-logs/<cycle-id>/summary.json # cycle-role-summary.v1, machine-readable
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`summary.json` carries one `CycleRoleAttempt` per agent participation, each with
|
|
444
|
+
its role, agent, model, session id, stage, state, and (when relevant) verdict,
|
|
445
|
+
score, findings, parse-failure cause, and artifact path.
|
|
446
|
+
|
|
447
|
+
### The Execution Cast report block
|
|
448
|
+
|
|
449
|
+
The story attest report embeds an **Execution Cast** block (🎭) that projects
|
|
450
|
+
the same summary into the delivery view, so the role chain travels with the
|
|
451
|
+
evidence. When no role summary exists the block degrades gracefully to "Role
|
|
452
|
+
summary unavailable". Accepted artifacts are linked directly — e.g. the
|
|
453
|
+
`accepted evaluator artifact` link points at the scorer output the gate actually
|
|
454
|
+
used.
|
|
455
|
+
|
|
456
|
+
### Selected vs returned vs accepted
|
|
457
|
+
|
|
458
|
+
The per-agent `state` is the key to reading the cast correctly. The states form
|
|
459
|
+
a ladder, and **being selected or returning output is not the same as being
|
|
460
|
+
accepted by the gate**:
|
|
461
|
+
|
|
462
|
+
| State | Meaning |
|
|
463
|
+
|-------|---------|
|
|
464
|
+
| `selected` | The agent was chosen for the stage but produced no accepted result. |
|
|
465
|
+
| `started` | The agent began the stage. |
|
|
466
|
+
| `returned` | The reviewer returned output, but no structured verdict was accepted. |
|
|
467
|
+
| `parsed` | Structured output was parsed. |
|
|
468
|
+
| `accepted` | The gate accepted this attempt — the verdict/score that counts. |
|
|
469
|
+
| `rejected` / `failed` | Rejected, errored, or produced unparseable output. |
|
|
470
|
+
| `not_required` / `not_available` | The role was not needed (e.g. `standard` profile) or had no candidate. |
|
|
471
|
+
|
|
472
|
+
**Only one evaluator/scorer is gate-accepted, even when several agents were
|
|
473
|
+
consulted.** A cycle may select reasonix, kimi, and codex for review and ask
|
|
474
|
+
reasonix and agy to score, but exactly one score is `accepted` and stamped into
|
|
475
|
+
the Attest Gate. The others show as `returned`, `selected`, or `failed` — they
|
|
476
|
+
are recorded for transparency, not because they all gate the delivery. When you
|
|
477
|
+
need the verdict that actually decided the cycle, read the `accepted` row (and
|
|
478
|
+
the `accepted evaluator` artifact link), not whichever agent happens to be
|
|
479
|
+
listed first.
|
|
480
|
+
|
|
481
|
+
When a reviewer or scorer produced unparseable output, its row is `failed` with
|
|
482
|
+
a `cause` (e.g. `unparseable`) and a `raw artifact:` pointer to the captured
|
|
483
|
+
attempt under `.roll/loop/peer/` — see
|
|
484
|
+
[Troubleshooting unparseable score/review](../../docs/live-console.md#故障排查).
|
|
485
|
+
|
|
486
|
+
## Collaboration View
|
|
487
|
+
|
|
488
|
+
US-OBS-032 writes the role cast (`summary.md` / `summary.json`), US-OBS-033
|
|
489
|
+
exposes that cast with `roll loop cycle <id> --roles`, and the collaboration view is
|
|
490
|
+
the upper layer over CycleRoleSummary. It turns the same facts into a protocol
|
|
491
|
+
relay: who designed, who built, who reviewed, who evaluated, and where the
|
|
492
|
+
baton ended.
|
|
493
|
+
|
|
494
|
+
Use these entry points:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
roll loop cycle <id> --collab # one cycle as a protocol relay
|
|
498
|
+
roll loop cycle <id> --collab --json # collab-view.v1 JSON
|
|
499
|
+
roll supervisor live --collab # live multi-cycle collaboration stream
|
|
500
|
+
roll supervisor live --collab --once
|
|
501
|
+
roll loop cycle --legend # Layer A protocol legend
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Read the protocol as:
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
Supervisor/Designer -> Builder -> independent Peer Reviewer/Evaluator -> Gate
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
The Supervisor can intervene at three levels. `observe/advise` means the
|
|
511
|
+
Supervisor is watching, asking for evidence, or routing owner attention without
|
|
512
|
+
changing the Builder's work. `design/split` means the Supervisor turns unclear
|
|
513
|
+
scope into a design artifact or smaller follow-up actions. `Builder override`
|
|
514
|
+
is explicit and exceptional: the Supervisor chooses or replaces the Builder
|
|
515
|
+
binding, and that decision must remain visible in the cast.
|
|
516
|
+
|
|
517
|
+
Role independence is session-based, not brand-based: separate fresh sessions
|
|
518
|
+
and artifact handoff are the rule. The same agent brand can serve more than
|
|
519
|
+
one role only when each role runs in its own session and hands off through
|
|
520
|
+
artifacts: a design contract, diff, review, score, AC map, or report.
|
|
521
|
+
A shared transcript is not an independent review. Agent diversity is useful
|
|
522
|
+
evidence and a ranking signal, especially when capability or shortcoming
|
|
523
|
+
profiles are visible in role summaries, but it is not a default hard exclusion
|
|
524
|
+
rule; capability, availability, and the required role contract still decide.
|
|
525
|
+
|
|
526
|
+
`handoff`, `escalation`, and `terminus` are separate reading keys. A handoff is
|
|
527
|
+
the normal baton pass between roles, such as Builder -> Peer Reviewer with a diff and evidence.
|
|
528
|
+
Escalation is a visible break in the normal path: the Supervisor, Gate, or owner
|
|
529
|
+
needs attention because the ordinary relay could not finish cleanly. The
|
|
530
|
+
`terminus` says where the baton ended, not whether the Story passed or failed:
|
|
531
|
+
`walked_full`, `escalated`, `split`, and `supervisor_fix` describe the relay's
|
|
532
|
+
ending point. Use the Gate and attest result for pass/fail.
|
|
533
|
+
|
|
534
|
+
## Status Dashboard
|
|
535
|
+
|
|
536
|
+
`roll loop status` prints a compact dashboard with per-cycle rows and daily rollup totals.
|
|
537
|
+
|
|
538
|
+
### Token column
|
|
539
|
+
|
|
540
|
+
Each cycle row shows token usage in a 4-component format:
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
· 19:18 13m 164/498.2K↑ 12.7M↓/63.3K opus-4-7 $11.07 US-VIEW-012
|
|
544
|
+
↑ in cw↑ cr↓ out
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
| Segment | Meaning |
|
|
548
|
+
|---------|---------|
|
|
549
|
+
| `164` (before first `/`) | Base input tokens |
|
|
550
|
+
| `498.2K↑` | Cache write tokens (billed at write rate) |
|
|
551
|
+
| `12.7M↓` | Cache read tokens (billed at read rate, much cheaper) |
|
|
552
|
+
| `63.3K` (after last `/`) | Output tokens |
|
|
553
|
+
|
|
554
|
+
When a cycle has no cache data (older cycles or non-Opus models), the column
|
|
555
|
+
falls back to the two-part `in/out` format.
|
|
556
|
+
|
|
557
|
+
**Per-agent coverage.** Token/cost capture depends on a per-agent usage plugin.
|
|
558
|
+
|
|
559
|
+
| Agent | Token/cost in dashboard |
|
|
560
|
+
|-------|-------------------------|
|
|
561
|
+
| Claude | ✅ supported |
|
|
562
|
+
| pi (DeepSeek) | ✅ supported |
|
|
563
|
+
| OpenAI (codex) | ✅ supported |
|
|
564
|
+
| Gemini | ✅ supported |
|
|
565
|
+
| Kimi | ✅ supported |
|
|
566
|
+
|
|
567
|
+
Agents without a plugin fall back to a `—/—` placeholder. Adding a new agent is
|
|
568
|
+
a small per-agent plugin (`lib/agent_usage/<agent>.py`) — it does not happen
|
|
569
|
+
automatically. See `lib/agent_usage/README.md` for the five-step howto.
|
|
570
|
+
|
|
571
|
+
### Rollup rows
|
|
572
|
+
|
|
573
|
+
Below the cycle list, daily totals break out all four components:
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
input tokens 164
|
|
577
|
+
cache writes 498.2K
|
|
578
|
+
cache reads 12.7M
|
|
579
|
+
output tokens 63.3K
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
This lets you verify that the displayed per-cycle cost (e.g. `$11.07`) matches
|
|
583
|
+
your Anthropic invoice — 86 % of the cost in that example comes from cache.
|
|
584
|
+
|
|
585
|
+
## Per-Story Rollup
|
|
586
|
+
|
|
587
|
+
`roll loop status` shows the rolling window (default 3 days). When you want the
|
|
588
|
+
**full lifetime** of a single story — every cycle that ever ran on it, including
|
|
589
|
+
cycles that have aged out of the dashboard window — use `roll loop story`.
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
roll loop story US-LOOP-004 # Compact panel for one story
|
|
593
|
+
roll loop story us-loop-004 # Case-insensitive
|
|
594
|
+
roll loop story US-LOOP-004 --days 90 # Widen the event-stream lookback
|
|
595
|
+
roll loop story US-LOOP-004 --json # Emit JSON for scripting / dashboards
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The panel reports the totals you'd otherwise have to hand-add across multiple
|
|
599
|
+
`status` runs:
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
── US-LOOP-004 · 把每轮 cycle 成本/token/耗时写进事件流 ──
|
|
603
|
+
cycles 3 (✓ 2 ✗ 1 ⏵ 0)
|
|
604
|
+
span 2026-05-18 14:22 → 2026-05-19 09:11
|
|
605
|
+
duration 1h 47m tokens in 412k out 18.3k cache w 1.2M r 7.8M
|
|
606
|
+
cost $4.92 model claude-opus-4-7
|
|
607
|
+
PRs #128 ✓ #131 ✓ #134 ✗
|
|
608
|
+
recent 20260518-142233-91 ✓ $2.10
|
|
609
|
+
20260518-203045-12 ✗ $1.71
|
|
610
|
+
20260519-091112-44 ✓ $1.11
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**How history is preserved:** the loop runner rotates `events-<slug>.ndjson` at 10 MB,
|
|
614
|
+
keeping `.1` … `.4` archives. `roll loop status` and `roll loop story` both read
|
|
615
|
+
the head plus all rotated files, so cycles never disappear once they're on disk.
|
|
616
|
+
|
|
617
|
+
**Exit codes:** `0` when at least one cycle is found, `2` when no cycles match
|
|
618
|
+
the story id in the lookback window. The `--json` form follows the same exit
|
|
619
|
+
code contract so scripts can detect missing data.
|
|
620
|
+
|
|
621
|
+
## Cycle Result Eval
|
|
622
|
+
|
|
623
|
+
Every cycle is scored *objectively* at finish against a fixed multi-dimensional
|
|
624
|
+
rubric, with **zero extra tokens** — the score is computed from facts the loop
|
|
625
|
+
already has (merge state, CI verdict, TCR count, duration, alerts, orphans). The
|
|
626
|
+
result is written into that cycle's `runs.jsonl` record under a `result_eval`
|
|
627
|
+
block:
|
|
628
|
+
|
|
629
|
+
```json
|
|
630
|
+
{ "version": 1, "score": 8, "dims": { "outcome": 1.0, "correctness": 1.0,
|
|
631
|
+
"scope_fidelity": 1.0, "quality": 1.0, "efficiency": 0.6, "cleanliness": 1.0 } }
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
> **Result-eval is NOT skill self-scoring.** Skill self-scoring (the agent's
|
|
635
|
+
> *subjective* review of one skill run, written into `.roll/notes/*.md`) and
|
|
636
|
+
> result-eval (this *objective* per-cycle result score, computed from facts) are
|
|
637
|
+
> two different signals. They are reported on separate dashboard lines and never
|
|
638
|
+
> merged.
|
|
639
|
+
|
|
640
|
+
### The rubric (six dimensions)
|
|
641
|
+
|
|
642
|
+
Each dimension is scored on `0.0`–`1.0`, or `unknown` when its facts are absent.
|
|
643
|
+
Unknown dimensions are excluded from the roll-up and the remaining weights are
|
|
644
|
+
renormalised, so a missing fact never silently scores `0`.
|
|
645
|
+
|
|
646
|
+
| Dimension | Weight | Meaning | 1.0 when… |
|
|
647
|
+
|-----------|--------|---------|-----------|
|
|
648
|
+
| `outcome` | 3 | Did the cycle merge into `main`? | merged · `0.0` not merged |
|
|
649
|
+
| `correctness` | 2 | Is the produced PR's CI green? | green · `0.0` red |
|
|
650
|
+
| `scope_fidelity` | 2 | Did it complete the story it was routed to? | completed · `0.0` idle / off-scope |
|
|
651
|
+
| `quality` | 1 | Tests added, no immediate rework? | TCR ≥ 1, no rework FIX · `0.5` rework landed · `0.0` no tests |
|
|
652
|
+
| `efficiency` | 1 | Duration vs the story's `est_min` budget | within budget · grades down past it |
|
|
653
|
+
| `cleanliness` | 1 | No orphan worktrees/branches and no ALERTs | clean · `0.0` orphans / alerts |
|
|
654
|
+
|
|
655
|
+
The dimensions roll up into a single **1–10 cycle score**:
|
|
656
|
+
|
|
657
|
+
```
|
|
658
|
+
weighted = Σ(score_i × weight_i for known dims) / Σ(weight_i for known dims)
|
|
659
|
+
cycle_score = round(1 + weighted × 9) # 0.0 → 1, 1.0 → 10
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Weights are a centralised constant in `lib/loop_result_eval.py` — tunable there,
|
|
663
|
+
but deliberately not a high-frequency user knob.
|
|
664
|
+
|
|
665
|
+
### Reading the trend — `roll loop eval [N]`
|
|
666
|
+
|
|
667
|
+
`roll loop eval` aggregates the `result_eval` of the last `N` scored cycles
|
|
668
|
+
(default 14) and prints mean / minimum cycle score, each dimension's hit-rate,
|
|
669
|
+
and a trend arrow. Older records without a `result_eval` block are skipped; with
|
|
670
|
+
fewer than 3 scored cycles it prints an `(n/a) need 3` notice.
|
|
671
|
+
|
|
672
|
+
```
|
|
673
|
+
$ roll loop eval
|
|
674
|
+
Loop result-eval — last 14 cycles
|
|
675
|
+
循环结果评分 — 最近 14 轮
|
|
676
|
+
|
|
677
|
+
mean 6.8 / 10 ↓
|
|
678
|
+
min 4 / 10
|
|
679
|
+
n 4
|
|
680
|
+
|
|
681
|
+
dimension hit-rate / 各维度命中率
|
|
682
|
+
outcome 75%
|
|
683
|
+
correctness 67%
|
|
684
|
+
scope_fidelity 75%
|
|
685
|
+
quality 75%
|
|
686
|
+
efficiency 50%
|
|
687
|
+
cleanliness 100%
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
The `roll loop status` dashboard also carries a one-line result-eval summary,
|
|
691
|
+
shown **separately** from the Review Score line so the two are never
|
|
692
|
+
confused:
|
|
693
|
+
|
|
694
|
+
```
|
|
695
|
+
result-eval: mean 6.8↓ / min 4 / out 75% ci 67% scope 75% qual 75% eff 50% clean 100% (last 14)
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Self-evolution signals — `roll loop signals`
|
|
699
|
+
|
|
700
|
+
When a dimension stays low (`0.0`) for `N` cycles in a row (default 3,
|
|
701
|
+
`--streak` to change), the loop surfaces it as an **improvement signal**: it
|
|
702
|
+
appends a *candidate* backlog draft — an `IDEA` or `FIX`, marked `📋 待人确认`
|
|
703
|
+
— to `.roll/signals/candidates.md`, and `roll loop signals` (and the
|
|
704
|
+
`roll loop status` dashboard) reports it. Signals are deduped per pattern, so a
|
|
705
|
+
standing issue is raised once, not every cycle.
|
|
706
|
+
|
|
707
|
+
A signal is advisory only. It never edits the real backlog, never activates a
|
|
708
|
+
story, and never changes code — it only exposes what keeps going wrong so a
|
|
709
|
+
human can decide. The cycle-finish hook runs the detector once per cycle;
|
|
710
|
+
`roll loop signals` runs it on demand.
|
|
711
|
+
|
|
712
|
+
| Dimension stuck low | Surfaced as | Reading |
|
|
713
|
+
|---------------------|-------------|---------|
|
|
714
|
+
| `outcome` | FIX | cycles keep failing to merge into main |
|
|
715
|
+
| `correctness` | FIX | produced PRs keep failing CI |
|
|
716
|
+
| `scope_fidelity` | IDEA | cycles keep going idle or off-scope |
|
|
717
|
+
| `quality` | FIX | cycles keep landing without test activity |
|
|
718
|
+
| `efficiency` | IDEA | cycles keep blowing past their `est_min` budget |
|
|
719
|
+
| `cleanliness` | FIX | cycles keep leaving orphans / raising ALERTs |
|
|
720
|
+
|
|
721
|
+
## TerminalOutcome vocabulary
|
|
722
|
+
|
|
723
|
+
User-facing cycle projections use TerminalOutcome values, not legacy summary
|
|
724
|
+
text. The stable vocabulary is:
|
|
725
|
+
|
|
726
|
+
`delivered`, `published_pending_merge`, `failed`, `blocked`,
|
|
727
|
+
`aborted_no_delivery`, `aborted_with_delivery`, `orphan_timeout`,
|
|
728
|
+
`idle_no_work`, `unknown`.
|
|
729
|
+
|
|
730
|
+
Older `runs.jsonl` records may contain free-form result strings. Readers
|
|
731
|
+
convert them through the truth adapter before rendering dashboards, archives,
|
|
732
|
+
or summaries.
|
|
733
|
+
|
|
734
|
+
## Visibility (tmux + popup)
|
|
735
|
+
|
|
736
|
+
Every loop run lives in a detached tmux session.
|
|
737
|
+
When you're not muted, a terminal window opens automatically.
|
|
738
|
+
|
|
739
|
+
Use `roll loop watch` first for daily observation. It combines the live agent
|
|
740
|
+
feed with the structured event stream into a concise status layer. Use
|
|
741
|
+
`roll loop watch --events` when debugging event ordering or phase/TCR facts.
|
|
742
|
+
Use `roll loop watch --raw-events` only when you need the unchanged audit JSON.
|
|
743
|
+
All watch modes are read-only; Ctrl-C stops only the view.
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
roll loop watch # default status layer
|
|
747
|
+
roll loop watch --events # compact events
|
|
748
|
+
roll loop watch --raw-events # raw audit stream
|
|
749
|
+
tmux attach -t roll-loop-<project-slug> # join the running observe pane at any time
|
|
750
|
+
# Ctrl-B D # detach (loop continues)
|
|
751
|
+
|
|
752
|
+
roll loop mute # 🔇 stop popup (mute file: ~/.shared/roll/mute)
|
|
753
|
+
roll loop unmute # 🔔 re-enable popup
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
The `mute` file is shared across all projects and all autonomous activity
|
|
757
|
+
(loop + peer review). One switch controls everything.
|
|
758
|
+
|
|
759
|
+
### Environment drift & session lifecycle
|
|
760
|
+
|
|
761
|
+
The tmux session is long-lived, but a cycle's **network environment always
|
|
762
|
+
follows the invoker**, not the session: the proxy family
|
|
763
|
+
(`HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY` + lowercase) is re-injected
|
|
764
|
+
from the caller every time a cycle window opens. Agent secret env names declared
|
|
765
|
+
by runner profiles, such as Reasonix's `DEEPSEEK_API_KEY`, are also forwarded by
|
|
766
|
+
name; file-backed profile credentials such as `~/.reasonix/.env` are loaded by
|
|
767
|
+
the spawn layer. So turning a local proxy on or off between cycles just works —
|
|
768
|
+
a session created under a proxy that has since died can no longer starve every
|
|
769
|
+
agent into `Connection error` (FIX-230), and env-only agent credentials do not
|
|
770
|
+
depend on the stale tmux session. Each cycle also logs its effective proxy env as
|
|
771
|
+
an `env:` line in `.roll/loop/cron.log`, so an environment-shaped failure is
|
|
772
|
+
readable straight from the log. Other variables still come from the session
|
|
773
|
+
created at `roll loop on` time; if you rotate something exotic, `roll loop off &&
|
|
774
|
+
roll loop on` rebuilds the session fresh.
|
|
775
|
+
|
|
776
|
+
### Edit fold
|
|
777
|
+
|
|
778
|
+
When the live tmux stream shows the agent editing the same file several times
|
|
779
|
+
in a row, Roll no longer repeats the identical long path on N separate lines
|
|
780
|
+
(which used to look like a hang). Instead it folds consecutive same-file edits
|
|
781
|
+
into a single line and refreshes it in place:
|
|
782
|
+
|
|
783
|
+
```text
|
|
784
|
+
✏ <basename> | <hint> ×N
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
- **Trigger** — two or more *adjacent* `Edit` / `Write` calls targeting the
|
|
788
|
+
same `file_path`. The path is shown as `os.path.basename(file_path)`, never
|
|
789
|
+
the full path. A single edit shows no `×N` counter.
|
|
790
|
+
- **`<hint>`** — a ≤20-char feature derived from the edit input so you can tell
|
|
791
|
+
*what* is changing, not just count:
|
|
792
|
+
- `replace_all=true` → the literal `replace-all`.
|
|
793
|
+
- otherwise the first non-blank token of `new_string`'s first line, with
|
|
794
|
+
leading whitespace and comment markers (`#`, `//`, `/*`, `*`, `--`, `;`)
|
|
795
|
+
stripped.
|
|
796
|
+
- tokens longer than 20 characters are truncated to `token[:20] + "…"`
|
|
797
|
+
(counted by unicode char, so 中文 / emoji are not byte-split).
|
|
798
|
+
- an empty / all-whitespace `new_string` yields no hint, and the ` | <hint>`
|
|
799
|
+
segment is omitted entirely.
|
|
800
|
+
- **Cross-file flush** — switching to a different file (or any other event:
|
|
801
|
+
`Bash`, `Skill`, an error, or cycle end) first flushes the previous file's
|
|
802
|
+
final streak line so it stays in the scrollback, then starts a fresh line for
|
|
803
|
+
the new file. Edits never fold across a non-edit line.
|
|
804
|
+
|
|
805
|
+
Three examples (ANSI-stripped):
|
|
806
|
+
|
|
807
|
+
```text
|
|
808
|
+
# single edit
|
|
809
|
+
✏ auth.ts | export
|
|
810
|
+
|
|
811
|
+
# folded ×N (7 edits of the same file)
|
|
812
|
+
✏ auth.ts | export ×7
|
|
813
|
+
|
|
814
|
+
# cross-file switch — two lines, the first flushed before the second starts
|
|
815
|
+
✏ auth.ts | export ×3
|
|
816
|
+
✏ router.ts | replace-all
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## Cycle exit summary
|
|
820
|
+
|
|
821
|
+
When a cycle ends and the tmux session detaches, the macOS `.command` window
|
|
822
|
+
no longer leaves you staring at a bare `press enter to close` line. Just before
|
|
823
|
+
that prompt, the window renders a compact recap block so you can see what the
|
|
824
|
+
cycle did without scrolling back through tmux scrollback or opening the cron
|
|
825
|
+
log:
|
|
826
|
+
|
|
827
|
+
```text
|
|
828
|
+
─── Cycle 20260530-2301-94839 Summary ───
|
|
829
|
+
outcome: delivered · story: US-LOOP-040 · tcr commits: 4
|
|
830
|
+
ci: green
|
|
831
|
+
todo remaining: 7
|
|
832
|
+
phases (top 5 by time):
|
|
833
|
+
build 612s
|
|
834
|
+
ci 94s
|
|
835
|
+
pr 31s
|
|
836
|
+
press enter to close.
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
The summary covers five signals:
|
|
840
|
+
|
|
841
|
+
1. **Result** — the cycle outcome from `runs.jsonl`, rendered as
|
|
842
|
+
TerminalOutcome in the summary:
|
|
843
|
+
`delivered`, `published_pending_merge`, `failed`, `blocked`,
|
|
844
|
+
`aborted_no_delivery`, `aborted_with_delivery`, `orphan_timeout`,
|
|
845
|
+
`idle_no_work`, or `unknown`.
|
|
846
|
+
2. **CI / build status** — the latest `ci` event outcome: `green`, `red`,
|
|
847
|
+
`heal-attempting`, or `ci: n/a` when the cycle never ran CI.
|
|
848
|
+
3. **Todo remaining** — count of `📋 Todo` lines still in `.roll/backlog.md`.
|
|
849
|
+
4. **Phase breakdown** — the top 5 cycle phases by elapsed time.
|
|
850
|
+
5. **Failure / alert highlights** — failed/aborted runs, red CI, active alerts,
|
|
851
|
+
and suspected zero-diff cycles are flagged with a `✗` (failure) or `⚠`
|
|
852
|
+
(warning) prefix and, on a colour terminal, red / yellow highlighting. A
|
|
853
|
+
fully green cycle prints in the default colour with no prefix.
|
|
854
|
+
|
|
855
|
+
The `press enter to close` prompt is preserved — the summary is printed *above*
|
|
856
|
+
it, nothing about the close interaction changes.
|
|
857
|
+
|
|
858
|
+
### Turning off colour
|
|
859
|
+
|
|
860
|
+
ANSI colour is only emitted when the output is a real terminal. Pipes,
|
|
861
|
+
redirects and captured output stay plain text. To force colour off on a TTY,
|
|
862
|
+
set `NO_COLOR=1` (per [no-color.org](https://no-color.org)):
|
|
863
|
+
|
|
864
|
+
```bash
|
|
865
|
+
NO_COLOR=1 roll loop now
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Troubleshooting: no summary appears
|
|
869
|
+
|
|
870
|
+
If the cycle exited early (aborted/idle) or `runs.jsonl` had not yet flushed,
|
|
871
|
+
the window prints a single placeholder line instead, and the `press enter`
|
|
872
|
+
prompt still works:
|
|
873
|
+
|
|
874
|
+
```text
|
|
875
|
+
(summary unavailable — see log: ~/.shared/roll/loop/cron-<slug>.log)
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
Summary rendering is always a silent best-effort step: if `python3` is missing
|
|
879
|
+
or the data is corrupt, the cycle skips the recap and falls straight through to
|
|
880
|
+
`press enter to close` — it never changes the `.command` exit code or blocks
|
|
881
|
+
the window from closing.
|
|
882
|
+
|
|
883
|
+
## Concurrency Safety
|
|
884
|
+
|
|
885
|
+
Loop has two layers:
|
|
886
|
+
|
|
887
|
+
- **LOCK file** (`<project>/.roll/loop/.LOCK-<slug>`): only one loop per project runs at a time.
|
|
888
|
+
If loop is already running, a new fire exits immediately.
|
|
889
|
+
- **🔨 In Progress status**: stories being worked by humans or other agents are skipped.
|
|
890
|
+
|
|
891
|
+
You can always run `$roll-build US-XXX` to take over a story manually.
|
|
892
|
+
Loop sees the `🔨 In Progress` marker and skips it.
|
|
893
|
+
|
|
894
|
+
## Failure Handling
|
|
895
|
+
|
|
896
|
+
| Condition | What happens |
|
|
897
|
+
|-----------|-------------|
|
|
898
|
+
| API error | Retry up to 3×, 30s backoff |
|
|
899
|
+
| Role candidate unavailable | Skip for current resolution, record runtime health |
|
|
900
|
+
| No role candidate available | Pause loop, write ALERT.md |
|
|
901
|
+
| TCR: 0 commits | Revert story to 📋 Todo, write ALERT.md |
|
|
902
|
+
| HEAD CI red | Hot-fix attempt (see below), or ALERT if exhausted |
|
|
903
|
+
|
|
904
|
+
ALERT entries surface in `roll loop status`, `roll loop alert`, and cycle/story evidence views.
|
|
905
|
+
|
|
906
|
+
## CI Self-Healing (US-LOOP-046..050)
|
|
907
|
+
|
|
908
|
+
When loop detects that HEAD CI is red before picking a story, it no longer
|
|
909
|
+
immediately writes an ALERT and stops. Instead it tries to fix CI autonomously.
|
|
910
|
+
|
|
911
|
+
**How it works:**
|
|
912
|
+
|
|
913
|
+
1. `roll loop precheck-ci` runs before each BACKLOG scan.
|
|
914
|
+
2. If CI is green → cycle proceeds normally.
|
|
915
|
+
3. If CI is red AND heal is allowed:
|
|
916
|
+
- Loop captures the failing CI log + commit diff via `roll loop hotfix-head-context`.
|
|
917
|
+
- Invokes `roll-fix` with that context as a brief.
|
|
918
|
+
- Waits for CI to go green.
|
|
919
|
+
- If still red after `ROLL_LOOP_HEAL_MAX` attempts (default 2): writes ALERT and stops.
|
|
920
|
+
4. If CI is red AND heal is exhausted or `ROLL_LOOP_NO_HEAL=1`: writes ALERT (existing behavior).
|
|
921
|
+
|
|
922
|
+
**Loop's own PRs (`loop/*`) that turn red after a cycle exits** are detected
|
|
923
|
+
(US-LOOP-049) and **background-healed** (US-LOOP-062a): classified as
|
|
924
|
+
`loop_self_ci_red`, the PR Loop routes them to `roll loop pr-heal-run`, which checks
|
|
925
|
+
out the PR branch and hands the failing-CI context to the project's agent
|
|
926
|
+
(`_project_agent`) — bounded by a per-PR heal budget (`ROLL_LOOP_HEAL_MAX`,
|
|
927
|
+
default 2) and a per-PR lock that prevents duplicate concurrent heals. The heal
|
|
928
|
+
runs in the background so the PR tick continues. When heal is disabled
|
|
929
|
+
(`ROLL_LOOP_NO_HEAL=1`) or the budget is exhausted, it writes a deduped
|
|
930
|
+
`[TYPE:loop-pr-ci-red]` ALERT instead of silently skipping.
|
|
931
|
+
|
|
932
|
+
**Environment variables:**
|
|
933
|
+
|
|
934
|
+
| Variable | Default | Effect |
|
|
935
|
+
|----------|---------|--------|
|
|
936
|
+
| `ROLL_LOOP_NO_HEAL=1` | unset | Disables all CI hot-fix attempts; reverts to fail-fast |
|
|
937
|
+
| `ROLL_LOOP_HEAL_MAX` | `2` | Maximum consecutive hot-fix attempts before ALERT |
|
|
938
|
+
|
|
939
|
+
## PR Inbox & Review
|
|
940
|
+
|
|
941
|
+
Each loop cycle processes open PRs before picking new stories.
|
|
942
|
+
|
|
943
|
+
**Review skill:**
|
|
944
|
+
|
|
945
|
+
PR review is dispatched through the `roll-review-pr` skill. It fetches the PR
|
|
946
|
+
title, body, and diff via `gh`, renders a review prompt, and routes it to the
|
|
947
|
+
project's configured agent (Claude, Kimi, DeepSeek, etc.). The agent outputs a
|
|
948
|
+
structured verdict:
|
|
949
|
+
|
|
950
|
+
| Verdict | Action |
|
|
951
|
+
|---------|--------|
|
|
952
|
+
| `APPROVE` | `gh pr review --approve` |
|
|
953
|
+
| `REQUEST_CHANGES` | `gh pr review --request-changes` with reason |
|
|
954
|
+
| `UNCERTAIN` | Writes an ALERT — human decides |
|
|
955
|
+
|
|
956
|
+
**Escape hatch:** Add `[skip-ai-review]` anywhere in the PR body to
|
|
957
|
+
auto-approve without invoking the agent.
|
|
958
|
+
|
|
959
|
+
**How the loop uses it:** `roll loop pr-inbox` classifies each open PR and routes
|
|
960
|
+
`eligible` PRs to the `roll-review-pr` skill. Loop's own PRs (`loop/*` branches)
|
|
961
|
+
are skipped to avoid same-source bias.
|
|
962
|
+
|
|
963
|
+
**Stale PR rebase:** PRs classified as `stale` (CI failed or branch behind/conflicting)
|
|
964
|
+
are automatically rebased onto `origin/main` by the runner's stale-PR rebase. A circuit
|
|
965
|
+
breaker limits rebase attempts to 3 within 24 hours — after that, an ALERT is raised.
|
|
966
|
+
Fork PRs are skipped (no write access) with an ALERT.
|
|
967
|
+
|
|
968
|
+
**Bot review detection:** If a GitHub Actions bot has already reviewed the PR
|
|
969
|
+
(e.g. via the optional GHA workflow), `roll loop pr-inbox` defers:
|
|
970
|
+
- Bot `APPROVED` → skip, let auto-merge proceed
|
|
971
|
+
- Bot `CHANGES_REQUESTED` → write ALERT (loop PR rejected by GHA reviewer)
|
|
972
|
+
|
|
973
|
+
**Active merge of approved PRs (US-LOOP-062b):** when a PR is human-approved,
|
|
974
|
+
CI-green, and mergeable, the PR Loop merges it directly (`gh pr merge --squash`)
|
|
975
|
+
through the runner's approved-PR merge rather than waiting for repo-level auto-merge
|
|
976
|
+
(which may be disabled). Merge failure is non-fatal — the PR is left open and
|
|
977
|
+
retried on the next tick.
|
|
978
|
+
|
|
979
|
+
### Optional: Event-driven PR review (GHA)
|
|
980
|
+
|
|
981
|
+
Without extra setup, `roll loop pr-inbox` reviews eligible PRs each loop cycle
|
|
982
|
+
(up to ~1 hour delay). For seconds-fast feedback on GitHub-hosted repos,
|
|
983
|
+
install the event-driven workflow:
|
|
984
|
+
|
|
985
|
+
```bash
|
|
986
|
+
cp templates/workflows/pr-review-event.yml .github/workflows/
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
This triggers the `roll-review-pr` skill on every PR open/update. Fork PRs and PRs
|
|
990
|
+
with `[skip-ai-review]` in the body are automatically skipped. The template
|
|
991
|
+
requires only one API key secret — the one matching your configured agent.
|
|
992
|
+
|
|
993
|
+
The two modes coexist: the GHA workflow provides instant feedback, and
|
|
994
|
+
`roll loop pr-inbox` acts as a safety net if the workflow is not installed.
|
|
995
|
+
|
|
996
|
+
## Session Cleanup
|
|
997
|
+
|
|
998
|
+
At the end of every cycle, loop automatically prunes stale local worktrees:
|
|
999
|
+
|
|
1000
|
+
- Any directory under `.claude/worktrees/` whose branch has been fully merged
|
|
1001
|
+
into `main` is removed (`git worktree remove --force` + `git branch -D`).
|
|
1002
|
+
- `git worktree prune` runs afterward to clear stale metadata.
|
|
1003
|
+
|
|
1004
|
+
This keeps `git worktree list` clean and prevents `.claude/worktrees/` from
|
|
1005
|
+
accumulating old entries over time. Active worktrees (branches ahead of `main`)
|
|
1006
|
+
are left untouched.
|
|
1007
|
+
|
|
1008
|
+
## Cycle phases
|
|
1009
|
+
|
|
1010
|
+
Every cycle is sliced into seven named phases. Each phase emits a `phase_start`
|
|
1011
|
+
event when entered and a `phase_end` event with duration + outcome when it
|
|
1012
|
+
exits. Long-silent phases (claude, PR-merge wait) emit a `phase_tick` heartbeat
|
|
1013
|
+
so the tmux viewer never looks frozen.
|
|
1014
|
+
|
|
1015
|
+
| # | Phase | When it runs | Typical duration |
|
|
1016
|
+
|---|-------|--------------|------------------|
|
|
1017
|
+
| 1 | `startup` | env / lock / heartbeat setup | < 1 s |
|
|
1018
|
+
| 2 | `preflight` | meta sync + stale-branch GC + orphan-worktree recovery | 0 s — 30 s |
|
|
1019
|
+
| 3 | `worktree_setup` | fetch origin + worktree create + meta sync | 2 – 10 s |
|
|
1020
|
+
| 4 | `agent_invoke` | Agent executes with up to 3 retries | 5 – 45 min |
|
|
1021
|
+
| 5 | `publish_push` | push branch + open PR (or doc-only merge) | 5 – 30 s |
|
|
1022
|
+
| 6 | `cleanup` | emit PR final state + worktree teardown | < 1 s |
|
|
1023
|
+
|
|
1024
|
+
> **US-AUTO-044**: the main loop exits after opening the PR and **no longer waits for merge**. Merge / rebase / close is handled asynchronously by the dedicated PR Loop (`com.roll.pr.<slug>`, every 5 min); a story with an open PR is skipped by the eligibility gate, so it is neither re-opened nor falsely marked Done.
|
|
1025
|
+
|
|
1026
|
+
Idle / failed / aborted cycles only emit the phases they actually entered.
|
|
1027
|
+
At cycle exit, the inner runner prints a phase breakdown panel sorted by
|
|
1028
|
+
duration descending, e.g.:
|
|
1029
|
+
|
|
1030
|
+
```
|
|
1031
|
+
─── Cycle 20260523-114502-12345 Phase Breakdown ───
|
|
1032
|
+
agent_invoke 723s ( 96.2%) ████████████████████
|
|
1033
|
+
publish_wait_merge 19s ( 2.5%) █
|
|
1034
|
+
worktree_setup 4s ( 0.5%)
|
|
1035
|
+
publish_push 2s ( 0.3%)
|
|
1036
|
+
preflight 2s ( 0.3%)
|
|
1037
|
+
cleanup 1s ( 0.1%)
|
|
1038
|
+
startup 1s ( 0.1%)
|
|
1039
|
+
──────────────────────────────────────
|
|
1040
|
+
Total 752s
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
Each phase duration is also persisted to `runs.jsonl` in a new top-level
|
|
1044
|
+
`phases` object (see [State Files](#state-files) below). `roll loop runs`
|
|
1045
|
+
appends `slowest=<phase> <pct>%` to each built row so you can see which step
|
|
1046
|
+
dominated without opening the cycle. For the full breakdown, use:
|
|
1047
|
+
|
|
1048
|
+
```bash
|
|
1049
|
+
roll loop runs --detail 20260523-114502-12345
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
## Cycle Log Archive
|
|
1053
|
+
|
|
1054
|
+
Every cycle's full agent output is archived to `.roll/cycle-logs/<cycle-id>.log`
|
|
1055
|
+
as an ANSI-stripped plain-text file — readable with `less`, `cat`, or any editor.
|
|
1056
|
+
|
|
1057
|
+
- **Per-cycle archive**: one `.log` file per cycle, saved to `.roll/cycle-logs/`
|
|
1058
|
+
- **ANSI-stripped**: color codes and control characters removed, clean plain text
|
|
1059
|
+
- **Retention**: keeps the 50 most recent cycles, older ones are auto-rotated
|
|
1060
|
+
- **Works muted or not**: logs are saved even when `roll loop mute` is on
|
|
1061
|
+
|
|
1062
|
+
```bash
|
|
1063
|
+
roll loop log # View the most recent cycle's full log
|
|
1064
|
+
roll loop log <cycle-id> # View a specific cycle (e.g. 20260525-231803-39799)
|
|
1065
|
+
roll loop log <prefix> # Prefix match (e.g. 20260525 matches all May 25 cycles)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Cycle logs are stored inside `.roll/` (the project's meta directory) and are
|
|
1069
|
+
gitignored — they won't pollute your repo.
|
|
1070
|
+
|
|
1071
|
+
## Cross-Machine Sync
|
|
1072
|
+
|
|
1073
|
+
When you run loop on multiple machines for the same project, Roll can sync cycle
|
|
1074
|
+
records to a shared git repo. Every machine writes its own events file, and the
|
|
1075
|
+
dashboard reads all of them — you get a unified view of every cycle across every
|
|
1076
|
+
machine.
|
|
1077
|
+
|
|
1078
|
+
### Setup
|
|
1079
|
+
|
|
1080
|
+
Add a `roll_records_remote` field to `~/.roll/config.yaml`:
|
|
1081
|
+
|
|
1082
|
+
```yaml
|
|
1083
|
+
roll_records_remote: "git@github.com:you/roll-loop-records.git"
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
**Private repo strongly recommended.** Cycle records contain prompt text, file
|
|
1087
|
+
paths, and other potentially sensitive information. Treat the records repo the
|
|
1088
|
+
same way you treat application logs — private, access-controlled, never public.
|
|
1089
|
+
|
|
1090
|
+
If `roll_records_remote` is not configured, cross-machine sync is entirely
|
|
1091
|
+
skipped — no records leave your machine.
|
|
1092
|
+
|
|
1093
|
+
### How it works
|
|
1094
|
+
|
|
1095
|
+
- Each machine generates a unique machine-id (UUID v4) on first run, cached at
|
|
1096
|
+
`~/.shared/roll/machine-id`.
|
|
1097
|
+
- Every cycle completion pushes an append-only `.ndjson` file to
|
|
1098
|
+
`<slug>/events/<machine-id>.ndjson` in the records repo. Each machine only
|
|
1099
|
+
writes to its own file — no merge conflicts.
|
|
1100
|
+
- Before rendering the dashboard, Roll runs `git pull --ff-only` on a local
|
|
1101
|
+
clone at `~/.shared/roll/sync/`, reads every `*.ndjson` file, and merges
|
|
1102
|
+
them sorted by timestamp with `run_id` deduplication.
|
|
1103
|
+
- Push and pull are best-effort background operations — if the remote is
|
|
1104
|
+
unreachable, the cycle continues normally and the dashboard shows only
|
|
1105
|
+
local data.
|
|
1106
|
+
|
|
1107
|
+
### Dashboard sync indicator
|
|
1108
|
+
|
|
1109
|
+
The dashboard footer shows one of three states:
|
|
1110
|
+
|
|
1111
|
+
| Indicator | Meaning |
|
|
1112
|
+
|-----------|---------|
|
|
1113
|
+
| `sync: ok (2m ago)` | Remote reachable, records merged successfully |
|
|
1114
|
+
| `sync: offline` | Remote unreachable (network issue, auth expired) — showing local data only |
|
|
1115
|
+
| `sync: not configured` | `roll_records_remote` not set — sync is off, this is expected |
|
|
1116
|
+
|
|
1117
|
+
### Fork caveat
|
|
1118
|
+
|
|
1119
|
+
Roll derives the project slug from `git remote get-url origin`. If you change
|
|
1120
|
+
`origin` to point to a fork, the slug changes — records from the original repo
|
|
1121
|
+
and the fork will land in different directories in the records repo. This is
|
|
1122
|
+
intentional (different repos, different identities), but if you're temporarily
|
|
1123
|
+
working from a fork, be aware that the dashboard won't show the upstream's
|
|
1124
|
+
cycle history.
|
|
1125
|
+
|
|
1126
|
+
## Loop Meta Sync
|
|
1127
|
+
|
|
1128
|
+
At the start of every cycle, roll automatically pulls the latest project metadata
|
|
1129
|
+
(`.roll/` directory — backlog, conventions, skills) from its git remote before
|
|
1130
|
+
scanning for stories.
|
|
1131
|
+
|
|
1132
|
+
**How it works**
|
|
1133
|
+
|
|
1134
|
+
1. Detects whether `.roll/` has a configured `origin` remote.
|
|
1135
|
+
If not, the step is silently skipped (no side-effects for standard roll installs).
|
|
1136
|
+
2. Runs `git fetch && git reset --hard origin/main` with a 15-second timeout.
|
|
1137
|
+
3. On success: emits a `meta_sync ok` event; cycle proceeds with fresh backlog.
|
|
1138
|
+
4. On failure: emits a `meta_sync stale` event; cycle continues with the existing
|
|
1139
|
+
`.roll/` contents (stale is better than no-op).
|
|
1140
|
+
|
|
1141
|
+
**After 3 consecutive failures** the loop writes an ALERT prompting you to check
|
|
1142
|
+
your SSH key or network connectivity.
|
|
1143
|
+
|
|
1144
|
+
**Manual sync**
|
|
1145
|
+
|
|
1146
|
+
```bash
|
|
1147
|
+
git -C .roll fetch && git -C .roll reset --hard origin/main
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**FAQ: the loop runs but the dashboard shows an empty backlog**
|
|
1151
|
+
|
|
1152
|
+
This usually means `.roll/` is out of date:
|
|
1153
|
+
- On a new machine: clone roll-meta manually into `.roll/` and configure the origin remote.
|
|
1154
|
+
- After reinstalling the OS: same as above — SSH key may also need re-authorization.
|
|
1155
|
+
- To confirm: `git -C .roll remote get-url origin` — if empty, no sync is attempted.
|
|
1156
|
+
|
|
1157
|
+
## Remote Monitoring
|
|
1158
|
+
|
|
1159
|
+
When you're away from your machine you can still watch the loop — backlog
|
|
1160
|
+
progress, Dream health, CI state — from a phone or any browser, without a local
|
|
1161
|
+
`roll` command. It works in two layers: a **data layer** (your machine pushes a
|
|
1162
|
+
status snapshot to the roll-meta repo) and a **prompt layer** (you paste a watch
|
|
1163
|
+
prompt into Claude Code, which reads roll-meta + the GitHub API).
|
|
1164
|
+
|
|
1165
|
+
### Configure `roll_meta_dir`
|
|
1166
|
+
|
|
1167
|
+
Tell roll where your roll-meta checkout lives, in `~/.roll/config.yaml`:
|
|
1168
|
+
|
|
1169
|
+
```yaml
|
|
1170
|
+
# ~/.roll/config.yaml
|
|
1171
|
+
roll_meta_dir: ~/projects/roll-meta
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
`~` is expanded. The key is optional — if it's unset, nothing changes and no
|
|
1175
|
+
snapshot is pushed. If the path doesn't exist, roll prints one WARNING to the
|
|
1176
|
+
cron log and skips the push (the cycle is never affected).
|
|
1177
|
+
|
|
1178
|
+
### How the automatic push works
|
|
1179
|
+
|
|
1180
|
+
Once `roll_meta_dir` is configured, the loop pushes a fresh snapshot after
|
|
1181
|
+
**every** cycle — including idle cycles where no story ran, so the snapshot
|
|
1182
|
+
doubles as a heartbeat. The cycle runner calls
|
|
1183
|
+
`${roll_meta_dir}/ops/push-loop-status.sh` in the background right after the
|
|
1184
|
+
`cycle_end` event. The script writes `status/loop.md` and commits + pushes it to
|
|
1185
|
+
roll-meta. Output goes to `~/.shared/roll/push-status.log` (rotated at 1MB, 2
|
|
1186
|
+
copies kept).
|
|
1187
|
+
|
|
1188
|
+
Because the loop runs on its normal schedule, `status/loop.md` stays **≤35min
|
|
1189
|
+
fresh** — the watch prompt always sees recent data. The push is best-effort: a
|
|
1190
|
+
network error, git conflict, or a >60s timeout is logged to push-status.log, the
|
|
1191
|
+
process is killed if it hangs, and the cycle continues. No ALERT, no retry.
|
|
1192
|
+
|
|
1193
|
+
### Manual push
|
|
1194
|
+
|
|
1195
|
+
You can push a snapshot by hand at any time:
|
|
1196
|
+
|
|
1197
|
+
```bash
|
|
1198
|
+
bash .roll/ops/push-loop-status.sh .roll
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
(`.roll` is your project's roll-meta checkout.) This is also how you confirm the
|
|
1202
|
+
push pipeline works before relying on the automatic hook.
|
|
1203
|
+
|
|
1204
|
+
### Watching from a phone or browser
|
|
1205
|
+
|
|
1206
|
+
Open `.roll/prompts/remote-watch.md`, copy its contents, and paste them into
|
|
1207
|
+
Claude Code (web, mobile, or a remote IDE). The prompt does a full health check
|
|
1208
|
+
on first run, then polls every 15 minutes and raises an ALERT immediately on
|
|
1209
|
+
conditions like CI failing twice in a row or `status/loop.md` going >60min
|
|
1210
|
+
stale. It only reads — it never modifies `seanyao/roll`.
|
|
1211
|
+
|
|
1212
|
+
### Troubleshooting: `status/loop.md` is stale
|
|
1213
|
+
|
|
1214
|
+
If the snapshot's timestamp is far older than 35 minutes:
|
|
1215
|
+
|
|
1216
|
+
1. Check `~/.shared/roll/push-status.log` — it records every push attempt and
|
|
1217
|
+
any timeout or git error.
|
|
1218
|
+
2. Confirm `roll_meta_dir` is set and the path exists
|
|
1219
|
+
(`roll config get roll_meta_dir`).
|
|
1220
|
+
3. Confirm `${roll_meta_dir}/ops/push-loop-status.sh` exists and is executable.
|
|
1221
|
+
4. Run the manual push above and watch for errors.
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
1225
|
+
远程不在本机时,依然可以从手机或任意浏览器查看 loop —— backlog 进度、Dream 健康、
|
|
1226
|
+
CI 状态 —— 无需本地 `roll` 命令。它分两层:**数据层**(本机把状态快照 push 到
|
|
1227
|
+
roll-meta 仓库)和 **prompt 层**(把巡检 prompt 粘贴进 Claude Code,读 roll-meta +
|
|
1228
|
+
GitHub API)。
|
|
1229
|
+
|
|
1230
|
+
### 配置 `roll_meta_dir`
|
|
1231
|
+
|
|
1232
|
+
在 `~/.roll/config.yaml` 里告诉 roll 你的 roll-meta 检出在哪:
|
|
1233
|
+
|
|
1234
|
+
```yaml
|
|
1235
|
+
# ~/.roll/config.yaml
|
|
1236
|
+
roll_meta_dir: ~/projects/roll-meta
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
`~` 会被展开。这个键是可选的——不配就什么都不变,也不会推快照。路径不存在时,roll 向
|
|
1240
|
+
cron 日志打一条 WARNING 并跳过推送(绝不影响 cycle)。
|
|
1241
|
+
|
|
1242
|
+
### 自动 push 的工作原理
|
|
1243
|
+
|
|
1244
|
+
配好 `roll_meta_dir` 后,loop 在**每一次** cycle 结束后推一份新快照——包括没跑故事
|
|
1245
|
+
的 idle cycle,所以快照同时充当心跳。cycle runner 在 `cycle_end` 事件之后,于后台调
|
|
1246
|
+
用 `${roll_meta_dir}/ops/push-loop-status.sh`。脚本写出 `status/loop.md` 并提交 +
|
|
1247
|
+
push 到 roll-meta。输出写到 `~/.shared/roll/push-status.log`(1MB 轮转,保留 2 份)。
|
|
1248
|
+
|
|
1249
|
+
因为 loop 按固定节奏运行,`status/loop.md` 始终保持 **≤35min 新鲜**——巡检 prompt 总
|
|
1250
|
+
能看到近期数据。推送是 best-effort:网络错误、git 冲突或 >60s 超时都记进
|
|
1251
|
+
push-status.log,进程卡住会被 kill,cycle 继续。不设 ALERT,不重试。
|
|
1252
|
+
|
|
1253
|
+
### 手动 push
|
|
1254
|
+
|
|
1255
|
+
随时可以手动推一份快照:
|
|
1256
|
+
|
|
1257
|
+
```bash
|
|
1258
|
+
bash .roll/ops/push-loop-status.sh .roll
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
(`.roll` 是你项目的 roll-meta 检出。)这也是在依赖自动 hook 前确认推送链路是否正常
|
|
1262
|
+
的方法。
|
|
1263
|
+
|
|
1264
|
+
### 在手机或浏览器上巡检
|
|
1265
|
+
|
|
1266
|
+
打开 `.roll/prompts/remote-watch.md`,复制全文,粘贴进 Claude Code(网页、手机或远端
|
|
1267
|
+
IDE)。该 prompt 首次执行做一次全量体检,之后每 15min 轮询一次,遇到「CI 连续两次失
|
|
1268
|
+
败」或「`status/loop.md` 超过 60min 未更新」等条件立即告警。它只读——绝不修改
|
|
1269
|
+
`seanyao/roll`。
|
|
1270
|
+
|
|
1271
|
+
### 排障:`status/loop.md` 不更新
|
|
1272
|
+
|
|
1273
|
+
若快照时间戳远早于 35 分钟:
|
|
1274
|
+
|
|
1275
|
+
1. 看 `~/.shared/roll/push-status.log`——它记录每次推送尝试以及任何超时或 git 错误。
|
|
1276
|
+
2. 确认 `roll_meta_dir` 已配置且路径存在(`roll config get roll_meta_dir`)。
|
|
1277
|
+
3. 确认 `${roll_meta_dir}/ops/push-loop-status.sh` 存在且可执行。
|
|
1278
|
+
4. 跑一次上面的手动 push,观察是否报错。
|
|
1279
|
+
|
|
1280
|
+
## State Files
|
|
1281
|
+
|
|
1282
|
+
Since Phase 2.0, a project's loop state lives **inside the project** at
|
|
1283
|
+
`<project>/.roll/loop/`. Only machine-level binding files (launchd runners,
|
|
1284
|
+
attach scripts) and the global mute switch stay in `~/.shared/roll/`. See
|
|
1285
|
+
[Loop Data Layout](loop-data-layout.md) for the full layout, migration, and
|
|
1286
|
+
`roll loop gc`.
|
|
1287
|
+
|
|
1288
|
+
自 Phase 2.0 起,项目的 loop 状态搬进了**项目目录** `<project>/.roll/loop/`。只有机
|
|
1289
|
+
器级绑定文件(launchd runner、attach 脚本)和全局静音开关留在 `~/.shared/roll/`。完
|
|
1290
|
+
整布局、迁移与 `roll loop gc` 见 [Loop 数据布局](loop-data-layout.md)。
|
|
1291
|
+
|
|
1292
|
+
| File | Content |
|
|
1293
|
+
|------|---------|
|
|
1294
|
+
| `<project>/.roll/loop/state-<slug>.yaml` | Current/last run: status, story, agent, run_id |
|
|
1295
|
+
| `<project>/.roll/loop/runs.jsonl` | Append-only run history (one JSON line per cycle); each record carries a `result_eval` block (see [Cycle Result Eval](#cycle-result-eval)) |
|
|
1296
|
+
| `<project>/.roll/loop/events.ndjson` | Per-cycle event stream (phase_start/phase_end, …) |
|
|
1297
|
+
| `.roll/signals/candidates.md` | Candidate backlog drafts from self-evolution signals (`📋 待人确认`, never auto-activated) |
|
|
1298
|
+
| `<project>/.roll/loop/ALERT-<slug>.md` | Accumulated alerts (failures, TCR violations) |
|
|
1299
|
+
| `<project>/.roll/loop/PAUSE-<slug>` | Pause marker (created by `roll loop pause`) |
|
|
1300
|
+
| `~/.shared/roll/mute` | Global auto-attach mute marker (shared across projects) |
|
|
1301
|
+
|
|
1302
|
+
## Degraded modes & observation
|
|
1303
|
+
|
|
1304
|
+
- **Offline**: when a cycle fails while the network is unreachable, the loop
|
|
1305
|
+
degrades to **local-only delivery** — TCR commits and green tests stay on the
|
|
1306
|
+
branch, a locale-selected notice is printed, the consecutive-failure counter
|
|
1307
|
+
is NOT ticked (offline never accumulates into an auto-PAUSE), and the
|
|
1308
|
+
scheduler keeps breathing. The next online cycle's push/PR catches up
|
|
1309
|
+
naturally.
|
|
1310
|
+
- **Live window for every agent**: non-claude agents (pi, kimi, codex, …) run
|
|
1311
|
+
under a pseudo-terminal on macOS so their output streams line-by-line into
|
|
1312
|
+
the observation window instead of buffering until exit; claude streams over
|
|
1313
|
+
its own protocol unchanged.
|
|
1314
|
+
|
|
1315
|
+
## Launchd lanes
|
|
1316
|
+
|
|
1317
|
+
Roll owns exactly three launchd jobs per project slug: `com.roll.loop.<slug>`
|
|
1318
|
+
(the cycle scheduler), `com.roll.dream.<slug>` (nightly scan) and
|
|
1319
|
+
`com.roll.pr.<slug>` (PR inbox). `roll loop on` installs them; `roll loop off`
|
|
1320
|
+
uninstalls them AND sweeps any other `com.roll.*.<slug>` plist it finds —
|
|
1321
|
+
retired shapes from older versions (ci/alert/brief) once survived for weeks as
|
|
1322
|
+
zombies pointing at deleted engines. `roll doctor` lists every `com.roll.*`
|
|
1323
|
+
job on the machine with its target directory and load state; a lane whose
|
|
1324
|
+
WorkingDirectory no longer exists is marked STALE in red.
|