Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6188a90
feat(herdr): wire mode-aware AUDIT_PHASE2_PARALLELISM through the dis…
Sep 24, 2026
88a1445
WL-0MU6MXCZZ007ZXT9: gate cheap mode switch on downtime leader and sh…
Sep 24, 2026
e6e5560
WL-0MU4URK4H006OFCE: extend dispatch log schema and add pane lifecycl…
Sep 24, 2026
24b1e57
WL-0MU4US5MP001JFEN: implement auto-close logic with implement exception
Sep 24, 2026
191f634
WL-0MU88086A0089US4: Implementation complete
Sep 24, 2026
f2d31cb
WL-0MU8808ZY0091JIA: log no-dispatch decision reason and surface held…
Sep 24, 2026
7bfc2c0
WL-0MU8809SZ0022VZG: correct proxy lease-TTL assumption; own-pane lea…
Sep 24, 2026
69e8731
WL-0MU4USJ07009JYL8: add fail-closed error handling and comprehensive…
Sep 24, 2026
227cbe1
WL-0MUFRFLRQ004KNDG: fix plugin init TDZ crash on modeSwitchWorker
Sep 24, 2026
555e41b
WL-0MUFP30T2003OX1F: fall back off per-slot mode on stale/empty slots…
Sep 24, 2026
635d423
test(herdr): regression-test needsProducerReview on the live dispatch…
Sep 24, 2026
ef71b33
WL-0MUFSQMH90086I7W: import PaneLifecycleKind and guard herdr plugin …
Sep 24, 2026
0de7330
WL-0MU9Z7L8X00117BI: Implementation complete
Sep 24, 2026
bf3f83d
fix(WL-0MSLW8GHQ0092PJK): remove id from error message valid-fields list
Sep 25, 2026
41e46e6
fix(WL-0MSLW8GHQ0092PJK): accept space-separated --fields values
Sep 25, 2026
e1bea03
WL-0MU9Z7L8X00117BI: Implementation complete
Sep 25, 2026
7470e5d
WL-0MT1KJNGA006EEHU: Add memwatch memory pressure monitoring and aler…
Sep 25, 2026
02f549b
WL-0MU55UDBJ008DJ67: Implementation complete
Sep 26, 2026
444a058
Bump version to v1.0.16
Sep 27, 2026
7ebbae7
Update CHANGELOG.md for v1.0.16
Sep 27, 2026
4bff952
Merge origin/dev into main (automated)
Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## v1.0.16 (2026-09-27)
### Features
- Projects now take turns getting work done, and urgent tasks jump to the front. (WL-0MTJ7IEI80055V2V)
- Downtime tasks now run two at a time, so more work gets done overnight. (WL-0MT50LKAK001EF5Q)
- Sync now protects your completed work from being silently reset by stale data. (WL-0MSOYWZTH003JFVD)
- Query worklogs faster with smaller, focused results. (WL-0MSLW8GHQ0092PJK)
- Answer interview questions in one guided step instead of searching through notes. (WL-0MU55UDBJ008DJ67)
- Stale in-progress items now return to the queue automatically when no agent is working on them. (WL-0MSOJLZD9004P8PI)
- Ship mode now blocks if any work is still in progress, preventing half-finished releases. (WL-0MUD6DDZC007ZSIW)
- Project setup now uses your global agent guidance instead of duplicating it. (WL-0MSIXMKOX0052514)
### Bug Fixes
- Stalled tasks now retry automatically instead of getting stuck forever. (WL-0MU6UL0RJ008IHGT)
- Agents no longer clash over the same files when work is dispatched twice. (WL-0MUBEZ6PE002WLP4)
- Overnight jobs now keep running instead of stalling when one task is active. (WL-0MU87ZGPP0029V28)

## v1.0.15 (2026-09-24)
### Features
- Import and export help now clearly warns that import replaces your entire database. (WL-0MTD0IVNN003WCOB)
Expand Down
29 changes: 22 additions & 7 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -1170,22 +1170,37 @@ wl --json status

### `interview` [options] <id>

Walk through outstanding interview questions on a work item interactively, capturing
answers and clearing the `needsProducerReview` flag. Interview questions are set by
producers when reviewing work items to request clarification or changes. This command
presents each question in sequence, allowing you to provide answers or dismiss questions
that are no longer relevant.
Walk through the outstanding clarifying questions on a work item interactively,
capturing answers and clearing the `needsProducerReview` flag.

The command reads the work item's clarifying-questions appendix (written by the
`intake` and `plan` skills) and prompts for every question that is still
outstanding — that is, questions with no answer or an explicit
`*(awaiting producer)*` placeholder. Answers are written back into the
appendix in place, so surrounding markup (`Source:` lines, attribution,
quotes, bold markers) is preserved.

Both appendix styles are recognised:

- the canonical bullet appendix (`## Appendix: Clarifying questions` with
`- Q: "…" — Answer (user): "…"` entries), and
- the legacy numbered form (`1. **Q:** …` / `**A:** …`).

Answers are persisted after each response, so an interrupted session can be
resumed by re-running the command: only the questions that remain outstanding
are asked. `needsProducerReview` is cleared once every question has an answer.

Options:

- `--prefix <prefix>` — Operate on a specific prefix (optional).
- `--json` — Output machine-readable JSON (optional).

> The command requires interactive (TTY) input; it cannot be used with
> `--json`.

Examples:

```sh
wl interview WL-ABC123
wl interview WL-ABC123 --json
```

### `help` [command]
Expand Down
26 changes: 24 additions & 2 deletions docs/design/mode-switch-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,33 @@ On plugin/pane restart the idle clock resets to "active now" (construction
time), so the proxy stays fast until a fresh full idle window passes
(fail-safe).

### Leader-only cheap switching + shared activity (WL-0MU6MXCZZ007ZXT9)

The mode-switch worker reuses the machine-wide downtime leader
(`DowntimeWorker.isLeader`) — there is no separate election:

- Only the leader runs the `tick()` cheap-switch logic. Non-leaders skip the
tick entirely, so an idle pane can no longer flip the shared proxy to cheap
while another pane is active (the flip-flop this fixed).
- Every instance (leader and non-leader alike) broadcasts its latest
operator-command timestamp to `mode-activity.json` in the coordination
directory (`getMachineCoordinationDir()`, the same dir as the leader lease).
- The leader's idle evaluation uses `max(localClock, sharedTimestamp)`, so the
proxy stays fast while *any* pane has recent activity.
- The fast switch still fires on the pane that received the command
(fail-open, idempotent) — the active pane may not be the leader.
- The leader probe is read live each tick, so a leadership handover is honoured
on the next tick: the new leader reads the shared activity and continues
from the correct state (no spurious switch during the election).
- **Fail-closed:** if the shared file cannot be read or written, the worker
falls back to its local clock (never a premature cheap switch).

## 3. Settings (re-read every tick — apply without plugin restart)

| Setting | Default | Clamp |
|---|---|---|
| `modeSwitchEnabled` | `true` | — (disabled ⇒ no scheduler task, no-op hook) |
| `modeSwitchIdleThresholdMs` | `1800000` (30 min) | floor `60000` |
| `modeSwitchIdleThresholdMs` | `3600000` (60 min) | floor `60000` |
| `modeSwitchPollIntervalMs` | `10000` | `[5000, 60000]` |

The proxy URL is **not** a new setting — it reuses `downtimeProxyUrl`.
Expand All @@ -71,7 +92,8 @@ time-based plan.
## 5. Key files

- `packages/herdr/src/mode-switch-worker.ts` — worker core (idle clock,
admin API client, tick logic, clamps)
leader-gated tick, shared `mode-activity.json` read/write, admin API
client, clamps)
- `packages/herdr/src/mode-switch-worker.test.ts` — worker core tests
- `packages/herdr/src/mode-switch-integration.test.ts` — wiring tests
(settings → worker, route classification, scheduler interval constants)
Expand Down
48 changes: 43 additions & 5 deletions docs/dev/downtime-dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,15 @@ leader path equally skips review-gated entries (retaining them for later re-offe
flag is cleared). The filter report is "no candidate" (not a `wl-error` strike) and the
no-candidate cooldown is not triggered while review-gated work exists.

**Live Herdr-head thread (WL-0MU72WJ8C0005GIE).** The flag reaches
`classifyItemForDispatch` on BOTH dispatch paths. The normal scan in
`dispatchFromHerdrList` builds its `DowntimeItemInfo` field-by-field from the Herdr
head item, so it must thread `needsProducerReview` explicitly; the offer path
(`computeMostImportantItem`) passes the item object directly, so it always honoured
the flag. Before the threading fix (`022f1b24`) the two paths disagreed and the live
path auto-dispatched a `needsProducerReview: true` item — the regression suite
(`needsProducerReview === true blocks a non-critical item…`) pins the agreement.

### Review-queue depth gate — producer review policy (WL-0MT2UQWOR007CYY9; live-path rewire WL-0MTTSWC1X005P4VD)

The **producer review policy**: while the root-only `completed`/`in_review` queue is deep
Expand Down Expand Up @@ -720,18 +729,47 @@ an agent on a tool call (wl, bash, tests) left the slot "free", so multiple
1. **Owner-lease gate (AC2)** — a non-null Local Proxy owner lease
(`local_owner_session_id` / `local_owner_lease_remaining_seconds`) counts
as "slot busy" for the dispatch decision when the worker has a live
dispatch pane; only a truly unowned slot is dispatchable. The lease
signal **self-heals**: proxy dispatch leases carry an `expires_at`
(~180 s, `_get_lease_timeout_seconds`) refreshed on activity, are marked
inactive when a stream ends, and expired records are cleaned up — so an
idle-but-open pane stops holding a slot.
dispatch pane; only a truly unowned slot is dispatchable.
Dispatch outcome reason: `slot-owned`.

**Lease contract (WL-0MU8809SZ0022VZG; cross-repo dependency:
`llm-manager`).** The lease is **adaptive**, NOT a fixed ~180 s. The
implementation lives in `llm-manager` (`proxy/proxy/router_helpers.py`):
it extends a static base in proportion to the generation and caps it at
`local_dispatch_lease_max_seconds` (**default 1500 s**), with chunk/prefill
refresh buffers keeping it alive while a stream is active. A long agent
generation therefore holds the lease for its **whole run** — the RCA
observed a dispatched-pane lease run 836 s → 0 (WL-0MU87ZGPP0029V28).
The dispatcher does **not** wait out a lease: it dispatches into genuinely
free **unowned** slots via the per-slot gate (WL-0MU8807BI008C9ME), and a
lease held by a dispatched pane (which already owns its own slot) does not
block the OTHER free unowned slots. Only the **count-based single-slot**
path treats a held lease as blocking (a truly owned sole slot stays
protected). The maximum is pinned in code as
`LOCAL_DISPATCH_LEASE_MAX_SECONDS`; if `llm-manager` changes the default,
revisit that constant and this section so the assumption cannot drift
silently again.
2. **Per-slot owner tracking (AC5)** — `LlamaSlot` carries an optional
`owner_session_id`; `countFreeUnownedSlots` excludes owned slots from the
free count and the per-slot idle tracker resets an owned slot's timer, so
a slot with a live lease is never considered available for a new pane.
A single idle-but-owned slot (count-based path) fails closed via the
derived `local_lease_active`.

**Stale/empty per-slot data (WL-0MUFP30T2003OX1F).** The proxy serves
`slots: []` together with `slots_stale: true` when its fresh `/slots`
query fails: the slot COUNTS come from the last-known cache but the
per-slot detail is unavailable. An empty array is **not** "zero free
slots", so the worker treats stale OR empty `slots` as "no per-slot
identity" and falls back to the count-based path. In that path, with a
multi-slot config (`0 < N < total`) a held lease no longer blocks
dispatch into the proxy-reported spare capacity: the count-based gate
uses `available_slots`, reserving one slot per lease only when the owner
may be idle (`local_active_query !== true` — an active query's slot is
already processing and excluded from the count). A single-slot
(`total_slots = 1`) or `N <= 0` / `N >= total` setup keeps the strict
fail-closed gate. The proxy-side improvement (serve cached per-slot
detail when stale) is tracked separately in `llm-manager`.
3. **Contention feedback (AC6)** — the proxy's LIVE `contention_queue_depth`
is parsed; while > 0 the dispatcher backs off with outcome reason
`proxy-contention` until the queue drains. The sibling
Expand Down
Loading
Loading