Files
nisse/nisse-dev/src/nisse_dev/loop/tools.py
T
dongarandClaude Opus 5.5 d9f2ebd5aa
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m36s
CI / e2e (pull_request) Successful in 11m13s
M4.2: the toolchain step
The preflight becomes the toolchain step (D3, as #135 describes it): a
bounded run of the item's driver model, in a transcript of its own,
that makes the build VM and the project's files agree.

- Started by the harness when the VM isn't set up for the project
  (M4.1's checks): it sets the VM up from the repository and edits
  nothing there (no write_file; anything a command changed is reported,
  left for the work).
- Requested by the driver with toolchain_step(reason) once work is
  agreed: it installs, then brings pins, setup docs and CI
  configuration in step; its edits land in the current milestone. The
  driver gets the step's report as the tool's result, not its turns.
- D1 made structural: until the go-ahead the fast path refuses what it
  knows changes something (and anything dangerous), with a pointer to
  the restatement; unknown commands still go to the classifier, so a
  bug can be reproduced before it's restated.
- A `toolchain` actor and view; the walk asks for steps now and then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 17:37:21 +00:00

131 lines
4.8 KiB
Python

"""Control tools (L13): handled in the harness, acting only on the agent's
own state, conversation and records. A *waiting* tool ends the driver's run:
the item then waits on the operator."""
from typing import Any
def _tool(tool: str, description: str, /, **params: str) -> dict[str, Any]:
return {
"type": "function",
"function": {
"name": tool,
"description": description,
"parameters": {
"type": "object",
"properties": {
p: {"type": "string", "description": d} for p, d in params.items()
},
"required": list(params),
},
},
}
RESTATE = _tool(
"restate",
"Put your understanding of the request to the operator, in their item's "
"thread: what you take it to mean, how it fits the project, your first "
"step, and why that way (and the main alternative you considered). Ends "
"your turn; work starts when they agree.",
text="Your restatement, in plain language.",
changed="What changed from your previous restatement, and why; leave it "
"empty for the first.",
)
ASK_HUMAN = _tool(
"ask_human",
"Ask the operator a question in the item's thread. Ends your turn; you "
"continue when they answer.",
question="The question.",
)
CLOSE_ITEM = _tool(
"close_item",
"The work is done: close the work item with a summary of what was done, "
"what was verified, and what is left open.",
summary="What was done, verified, and left open.",
)
START_MILESTONE = _tool(
"start_milestone",
"Start a milestone before changing code: the harness cuts its branch. "
"Never create or switch branches yourself.",
name="A short name for the milestone.",
)
FINISH_MILESTONE = _tool(
"finish_milestone",
"The milestone's work is done and its tests pass: the harness commits "
"the work, pushes it and opens a PR, or, after review comments, pushes "
"your fix to the PR. Ends your turn; you continue when the PR is "
"reviewed or merged.",
summary="What the milestone did and how it was verified, for the PR.",
title="A short title for the PR (ignored when pushing a review fix).",
final="yes if this milestone completes the issue, no if more milestones follow it.",
)
WAIT_FOR_REVIEW = _tool(
"wait_for_review",
"Nothing more to do until the operator reviews or merges the PR (you "
"can't merge it yourself): say so and wait. You continue when there's a "
"new comment, or when it merges or closes.",
note="What you're waiting on, for the operator.",
)
TOOLCHAIN_STEP = _tool(
"toolchain_step",
"The work needs a toolchain change (a new tool, library or version): a "
"step that installs it on the build VM and brings the project's pins, "
"setup docs and CI configuration in step. Its edits are part of the "
"current work. Returns its report.",
reason="What the work needs, and why.",
)
# A conversation's tools (L1, L5): it talks, and may propose work.
REPLY = _tool(
"reply",
"Answer the operator in the conversation's thread. Ends your turn; you "
"continue when they say more.",
text="Your answer, in plain language.",
)
PROPOSE_WORK = _tool(
"propose_work",
"The conversation has turned into work on one of the projects: propose "
"an issue for it. The operator agrees to it as to a restatement; then the "
"issue is opened, and work on it starts as its own item. Ends your turn.",
project="The project's name, from the list in your instructions.",
title="A short title for the issue.",
summary="What was decided, distilled: the request, what it means, and the "
"first step. Not the conversation; it is the issue's body and the work's "
"whole brief.",
)
PROPOSE_PROJECT = _tool(
"propose_project",
"The conversation has become a new project: propose it. The operator "
"agrees to it as to a restatement; then you list what it needs set up "
"(a repository, a build VM, the project's entry), and once it is ready "
"and checked, the project's architecture is your first piece of work. "
"Ends your turn.",
name="The project's name: lowercase letters, digits and -.",
purpose="What it is for, in a paragraph.",
decisions="The decisions setting it up depends on: language and toolchain "
"(which pick the build VM), services, secrets, network access beyond the "
"default; and anything else agreed so far.",
)
WAITING = frozenset(
{
"restate",
"ask_human",
"close_item",
"finish_milestone",
"wait_for_review",
"reply",
"propose_work",
"propose_project",
"toolchain_ready",
}
)
def names(tools: list[dict[str, Any]]) -> set[str]:
return {tool["function"]["name"] for tool in tools}