Roles, capability profiles and help on the new loop (M2.6)
- roles.driver (unset: the planner's model) and roles.classifier (unset: the expert's); the mapping is pinned when an item opens, and the driver and the go-ahead reader run on the item's own (L9). - profiles: in configuration (tools, strengths, weaknesses), used in briefings: the driver's instructions say what its helper and expert are good at, the helper's brief who reads its handback; never a model name. - delegate(task) and ask_expert(question) for the driver. A helper works on a branch off the milestone's; on a handback its work comes back as uncommitted changes, otherwise it is discarded, so harness branches never hold commits only the clone has. ask_expert keeps its budget. - Triggers: failure_limit failed commands in a row (Driver's new "stuck" outcome) or the turn cap, mid-milestone, call a helper in once per milestone; if it fails too, the item waits on the operator. A delegation cut off by a crash is discarded at boot. - Evaluation capture: delegations and ask_expert as before, and the reader's verdicts as classifier pairs. Tests: help tests (merge-back, discard, needs a milestone, the stuck trigger and its once-per-milestone limit, the expert and its budget, pinning, briefings by profile, boot discarding a cut-off delegation), config tests for roles and profiles, and the walk delegating with a scripted helper that sometimes fails, with a delegations_settle property. The walk found helper commits stranded on a merged PR's branch, fixed by returning a helper's work uncommitted. 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
04c352b30c
commit
59fd00ce8c
19 files changed
+890
-38
No files matched your search
@@ -0,0 +1,73 @@
|
||||
"""Help for the driver (L9): a helper for a sub-task (delegation), an expert
|
||||
for one question, both by role, briefed from capability profiles.
|
||||
|
||||
The driver asks when it chooses to (its instructions treat asking as
|
||||
normal), and the harness asks for it on observable triggers: commands failing
|
||||
in a row, or the turn cap. Delegation runs on a branch off the milestone's,
|
||||
merged back on a handback and discarded otherwise (L9: never build on a
|
||||
half-edited tree).
|
||||
"""
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from nisse_dev.config import Profile
|
||||
from nisse_dev.delegation import Caps
|
||||
from nisse_dev.expert import DEFAULT_BUDGET
|
||||
from nisse_dev.llm import LLMClient, RoleMap
|
||||
from nisse_dev.loop.tools import _tool
|
||||
|
||||
DELEGATE = _tool(
|
||||
"delegate",
|
||||
"Hand a sub-task of the current milestone to a helper, who works on the "
|
||||
"milestone's branch with the same tools and reports back what changed, "
|
||||
"what is now true and what next. Use it when the sub-task is beyond what "
|
||||
"you can do reliably, or you are going round in circles. Needs a "
|
||||
"milestone started.",
|
||||
task="The sub-task, self-contained: what to do, where, and how to know it's done.",
|
||||
)
|
||||
ASK_EXPERT = _tool(
|
||||
"ask_expert",
|
||||
"Ask an expert one self-contained question: a design trade-off, how a "
|
||||
"library behaves. It can't see the repository. The budget per item is "
|
||||
"small.",
|
||||
question="The question, with whatever context it needs.",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Helpers:
|
||||
client: LLMClient # for the expert
|
||||
roles: RoleMap # the configured mapping; each item pins its own (L9)
|
||||
# The helper's client by model (vendor passthrough where it fits, L9);
|
||||
# unset, ``client``.
|
||||
client_for: Callable[[str], LLMClient] | None = None
|
||||
caps: Caps = field(default_factory=Caps)
|
||||
expert_budget: int = DEFAULT_BUDGET
|
||||
profiles: Mapping[str, Profile] = field(default_factory=dict)
|
||||
|
||||
def delegate_client(self, model: str) -> LLMClient:
|
||||
return self.client_for(model) if self.client_for else self.client
|
||||
|
||||
def profile(self, model: str) -> str:
|
||||
"""What one role is told about another: its capability profile,
|
||||
never its name (L9)."""
|
||||
found = self.profiles.get(model)
|
||||
return found.describe() if found else "no profile recorded"
|
||||
|
||||
def briefing(self, roles: RoleMap) -> str:
|
||||
"""The driver's instructions on asking for help."""
|
||||
return (
|
||||
"Asking for help is normal, not a failure. delegate hands a "
|
||||
"sub-task to a helper ("
|
||||
+ self.profile(roles.resolve("helper"))
|
||||
+ "); ask_expert asks one question of an expert ("
|
||||
+ self.profile(roles.resolve("expert"))
|
||||
+ "). If commands keep failing, a helper is called in for you."
|
||||
)
|
||||
|
||||
def reader(self, roles: RoleMap) -> str:
|
||||
"""Who acts on a helper's handback: the driver, by its profile."""
|
||||
return "the driver, a model with this profile: " + self.profile(
|
||||
roles.resolve("driver")
|
||||
)
|
||||
Reference in new issue
Block a user