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

+11 -3
View File
@@ -72,12 +72,18 @@ def issue(number: int = 14, author: str = "don", **kw: Any) -> Issue:
title=kw.get("title", "Add count_words"),
body=kw.get("body", "count words in a string"),
author=author,
labels=(),
labels=kw.get("labels", ()),
)
class Harness:
def __init__(self, tmp_path: Path, model: ScriptedModel, gitea: FakeGitea) -> None:
def __init__(
self,
tmp_path: Path,
model: ScriptedModel,
gitea: FakeGitea,
**agent: Any, # more LoopAgent arguments: reader, clock, ...
) -> None:
tmp_path.mkdir(parents=True, exist_ok=True)
self.store = StateStore(
tmp_path / "state.db", oplog=OpLog("nisse-dev", io.StringIO())
@@ -100,6 +106,7 @@ class Harness:
worker=InlineWorker(),
cancel=self.cancel,
oplog=OpLog("nisse-dev", io.StringIO()),
**agent,
)
self.transport.on_message(self.agent.on_message)
@@ -215,7 +222,8 @@ def test_a_go_at_top_level_is_not_a_go_ahead(h: Harness) -> None:
h.agent.intake()
h.say("go", thread=None)
assert not loop_of(h.item()).started
assert "assign one and I'll pick it up" in h.posted()[-1]
# L5: an unnamed command at top level is ambiguous, so it asks.
assert "Which item?" in h.posted()[-1] and "#14" in h.posted()[-1]
# --- waiting points (L2) -------------------------------------------------------------