Threads, commands and labels on the new loop (M2.3)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 3m45s

- Commands that name their item ("go #14", "abandon #14", "stop #14")
  apply to it wherever they're said; go, approve and abandon alone at top
  level are asked about (L5). stop is never taken as an answer.
- A plain-language reply to a restatement is read by GoAheadReader, one
  model call that agrees only on a clear yes; questions never reach it. The
  agent says "taking that as a go-ahead on rev-N" first, bound to the
  revision on screen at arrival (L3, L5, D1).
- Labels steer (D1): `bug` goes ahead after tuning.loop.bug_grace_s
  (default 600 s) unless the operator replies; `discuss` isn't offered
  restate until the operator has spoken; `bug`, `discuss` and `design` add
  to the driver's instructions. A restatement asks no questions.

Tests: label and command unit tests, the property walk with labelled
issues, plain yeses, named gos and time passing (grace_only_unanswered,
discuss_talks_first), and e2e tests for a bug proceeding after its grace
period and a plain-language go-ahead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-04 16:13:22 +00:00
1 parent a504526da2
commit 4d9c1d7529
13 files changed
+793 -35

No files matched your search

+4
View File
@@ -40,6 +40,9 @@ class Loop:
fixes: int = 0 # review fixes pushed, for follow-up branch names
blocked_by: tuple[str, ...] = () # open dependencies, "owner/repo#N" (L1)
first: int = 0 # the operator's "do #N next": higher runs sooner
labels: tuple[str, ...] = () # the issue's labels at intake: they steer (D1)
grace_until: float = 0.0 # a `bug` restatement proceeds then, unanswered
talked: bool = False # the operator has spoken in the item's thread
# Where the operator last spoke (D5: answer where asked): "" for the
# Matrix thread, or the PR it came from, with the inline location and a
# little of what they said, for quoting.
@@ -55,6 +58,7 @@ def loop_of(item: WorkItem) -> Loop:
data = dict(item.data.get("loop", {}))
data["notes"] = tuple(data.get("notes", ()))
data["blocked_by"] = tuple(data.get("blocked_by", ()))
data["labels"] = tuple(data.get("labels", ()))
return Loop(**data)