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
@@ -482,3 +482,56 @@ def test_a_two_milestone_feature(
|
||||
operator.merge_and_close(issues[0])
|
||||
finally:
|
||||
operator.tidy(issues, prs)
|
||||
|
||||
|
||||
def test_a_conversation_becomes_filed_work(
|
||||
running: tuple[RunningLoop, InMemoryTransport, str],
|
||||
) -> None:
|
||||
"""M2.5: a top-level message is a conversation; the work it turns into
|
||||
is proposed, agreed, filed as a record issue, and started as agreed."""
|
||||
agent, transport, run = running
|
||||
operator = Operator(agent, transport)
|
||||
gitea, repo = agent.gitea, agent.config.gitea_repo
|
||||
issues: list[int] = []
|
||||
prs: list[int] = []
|
||||
|
||||
def conversation() -> Any:
|
||||
items = agent.worker.call(agent.store.work_items)
|
||||
return next(i for i in items if i.scope == "general")
|
||||
|
||||
try:
|
||||
root = transport.receive(
|
||||
OPERATOR,
|
||||
f"I've been thinking the sandbox could use this: {task(run)} "
|
||||
"Could you file that as an issue and start on it?",
|
||||
)
|
||||
operator.settle()
|
||||
deadline = time.monotonic() + DEADLINE_S
|
||||
answers = 0
|
||||
while loop_of(conversation()).wait != GO_AHEAD:
|
||||
assert time.monotonic() < deadline, f"never proposed: {operator.last()}"
|
||||
answers += 1
|
||||
assert answers <= MAX_ANSWERS, f"never proposed: {operator.last()}"
|
||||
transport.receive(
|
||||
OPERATOR,
|
||||
"Yes, propose it as an issue in the sandbox project.",
|
||||
thread=root,
|
||||
)
|
||||
operator.settle()
|
||||
assert operator.last().startswith(f"rev-{loop_of(conversation()).rev}:")
|
||||
transport.receive(OPERATOR, "go", thread=root)
|
||||
operator.settle()
|
||||
|
||||
[filed] = loop_of(conversation()).became
|
||||
number = int(filed.split("#")[1])
|
||||
issues.append(number)
|
||||
data = gitea._request("GET", f"/repos/{repo}/issues/{number}")
|
||||
assert "<!-- nisse-item: " in data["body"]
|
||||
assert agent.account in [a["login"] for a in data["assignees"] or []]
|
||||
item = operator.drive_until(MERGE, number)
|
||||
assert loop_of(item).started # no second restatement
|
||||
prs.append(loop_of(item).pr)
|
||||
pr = gitea._request("GET", f"/repos/{repo}/pulls/{prs[0]}")
|
||||
assert f"Closes #{number}" in pr["body"]
|
||||
finally:
|
||||
operator.tidy(issues, prs)
|
||||
Reference in new issue
Block a user