Conversations on the new loop (M2.5, PR A)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m14s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m14s
- A top-level message that isn't a command is a conversation: an item in the `general` scope (L1), threaded under the operator's message, with `reply`, `ask_human` and `propose_work` and no workspace or execution. `general` is reserved as a project name. - propose_work(project, title, summary) goes up as a revision for a go-ahead (or a plain yes). On go, under a file_issue intent (L8), the record issue is opened in that project, assigned to the agent and carrying the new item's ID; its item starts agreed, with the summary as its brief. Intake skips any issue carrying an ID (D1); boot adopts a filed issue or puts the proposal up again. - A conversation closes after tuning.loop.general_idle_s (default a day); a reply in its thread then starts a new conversation that continues it. Tests: conversation unit tests (threading, filing, the marker, an unknown project, changes, idle close and continuation, a plain yes, both crash cases, commands at top level); the walk with conversations, proposals and idle time, and records_have_one_item / conversations_do_no_work; an e2e conversation that becomes filed work. 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:
1 parent
d70d43a8cc
commit
3f54896f65
14 files changed
+811
-35
No files matched your search
@@ -20,6 +20,7 @@ CLOSED = "closed"
|
||||
GO_AHEAD = "go-ahead" # a go on the restatement up (L3, D1)
|
||||
ANSWER = "answer" # an answer to a question
|
||||
MERGE = "merge" # the PR's review and merge (D4, D5)
|
||||
CHAT = "conversation" # a conversation waits for the operator to say more (L5)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -43,6 +44,14 @@ class Loop:
|
||||
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
|
||||
# A conversation (`general`, L1): when it last heard or said anything,
|
||||
# the conversation it continues, the work it turned into, and the work
|
||||
# proposed for a go-ahead.
|
||||
idle_since: float = 0.0
|
||||
follows: str = ""
|
||||
became: tuple[str, ...] = ()
|
||||
proposal_project: str = ""
|
||||
proposal_title: str = ""
|
||||
# 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.
|
||||
@@ -59,6 +68,7 @@ def loop_of(item: WorkItem) -> Loop:
|
||||
data["notes"] = tuple(data.get("notes", ()))
|
||||
data["blocked_by"] = tuple(data.get("blocked_by", ()))
|
||||
data["labels"] = tuple(data.get("labels", ()))
|
||||
data["became"] = tuple(data.get("became", ()))
|
||||
return Loop(**data)
|
||||
|
||||
|
||||
@@ -75,11 +85,13 @@ def save(
|
||||
)
|
||||
|
||||
|
||||
def item_for_thread(store: StateStore, thread: str) -> WorkItem | None:
|
||||
for item in store.work_items("open"):
|
||||
if loop_of(item).thread == thread:
|
||||
return item
|
||||
return None
|
||||
def item_for_thread(
|
||||
store: StateStore, thread: str, status: str | None = "open"
|
||||
) -> WorkItem | None:
|
||||
"""The item whose conversation is ``thread``; the latest, when a closed
|
||||
conversation's thread was picked up again."""
|
||||
found = [i for i in store.work_items(status) if loop_of(i).thread == thread]
|
||||
return found[-1] if found else None
|
||||
|
||||
|
||||
def item_for_issue(
|
||||
|
||||
Reference in new issue
Block a user