Rename the delegate and consultant roles to helper and expert
Architecture L9's role names, for the two roles with a direct counterpart: the delegation role is now 'helper' and the ask_expert role 'expert', in RoleMap, configuration (roles.helper, roles.expert), recorded events and tests. 'planner' and 'executor' stay distinct for now; they become 'driver' when the new loop merges them (Development Plan M2.3). Configurations must rename their keys: nisse-dev.example.yaml and tests/integration/nisse-test.yaml are updated here. 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
e6aaa4783d
commit
3cf4cefacd
27 files changed
+68
-89
No files matched your search
@@ -263,7 +263,7 @@ def test_a_tool_less_model_warns_but_still_boots(tmp_path: Path, monkeypatch) ->
|
||||
logged = stream.getvalue()
|
||||
assert "model_tools_unsupported" in logged
|
||||
assert '"role": "executor"' in logged
|
||||
assert '"role": "consultant"' not in logged # ask_expert drives no tools
|
||||
assert '"role": "expert"' not in logged # ask_expert drives no tools
|
||||
|
||||
|
||||
def test_the_resolved_model_is_pinned_at_task_start(
|
||||
|
||||
@@ -20,7 +20,7 @@ from nisse_dev.planner import LLMPlanningAgent
|
||||
from nisse_dev.state import StateStore
|
||||
from nisse_dev.transport import InMemoryTransport
|
||||
|
||||
ROLES = RoleMap(executor="x", planner="y", delegate="y", consultant="y")
|
||||
ROLES = RoleMap(executor="x", planner="y", helper="y", expert="y")
|
||||
|
||||
|
||||
class FakeGitea:
|
||||
|
||||
@@ -27,7 +27,7 @@ ALL_EVENTS = [
|
||||
GateOpened(gate="arch", reason="plan submitted (rev-1)"),
|
||||
DelegationStarted(
|
||||
brief="fix the flaky test",
|
||||
role="delegate",
|
||||
role="helper",
|
||||
model="claude-opus-5",
|
||||
caps={"turns": 20, "tokens": 100000},
|
||||
),
|
||||
|
||||
@@ -130,13 +130,13 @@ def test_validate_names_every_bad_role_at_once() -> None:
|
||||
{
|
||||
"executor": "local-fast",
|
||||
"planner": "clause-opus-5",
|
||||
"delegate": "remote-architecture",
|
||||
"consultant": "local-fast",
|
||||
"helper": "remote-architecture",
|
||||
"expert": "local-fast",
|
||||
}
|
||||
)
|
||||
message = str(caught.value)
|
||||
assert "planner='clause-opus-5'" in message
|
||||
assert "delegate='remote-architecture'" in message
|
||||
assert "helper='remote-architecture'" in message
|
||||
assert "executor" not in message # the good ones are not noise
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ COMPLETE: dict[str, Any] = {
|
||||
"roles": {
|
||||
"executor": "claude-haiku-4-5",
|
||||
"planner": "claude-opus-5",
|
||||
"delegate": "claude-opus-5",
|
||||
"consultant": "claude-haiku-4-5",
|
||||
"helper": "claude-opus-5",
|
||||
"expert": "claude-haiku-4-5",
|
||||
},
|
||||
"matrix": {
|
||||
"homeserver": "https://matrix.test/",
|
||||
@@ -210,8 +210,8 @@ llm: {base_url: "https://litellm.test", token: "${TEST_LLM_TOKEN}"}
|
||||
roles:
|
||||
executor: local-fast
|
||||
planner: claude-opus-5
|
||||
delegate: claude-opus-5
|
||||
consultant: claude-haiku-4-5
|
||||
helper: claude-opus-5
|
||||
expert: claude-haiku-4-5
|
||||
matrix:
|
||||
homeserver: https://matrix.test
|
||||
user_id: "@nisse-dev-testing:test"
|
||||
|
||||
@@ -262,7 +262,7 @@ def test_the_executor_sees_only_its_milestone(tmp_path: Path) -> None:
|
||||
seen["system"] = system
|
||||
return [AssistantText(text="done", model=model)]
|
||||
|
||||
roles = RoleMap(executor="m", planner="m", delegate="m", consultant="m")
|
||||
roles = RoleMap(executor="m", planner="m", helper="m", expert="m")
|
||||
LLMExecutor(Client(), store, roles, Workspace()).run(1)
|
||||
|
||||
texts = [getattr(e, "text", None) for e in seen["events"]]
|
||||
@@ -286,7 +286,7 @@ def test_a_request_that_cannot_fit_stops_the_milestone(tmp_path: Path) -> None:
|
||||
|
||||
inner = RecordingClient()
|
||||
client = BudgetedClient(inner, lambda m: (4096, 1024))
|
||||
roles = RoleMap(executor="m", planner="m", delegate="m", consultant="m")
|
||||
roles = RoleMap(executor="m", planner="m", helper="m", expert="m")
|
||||
outcome = LLMExecutor(client, store, roles, Workspace()).run(1)
|
||||
|
||||
assert outcome.status == "escalate"
|
||||
|
||||
@@ -385,9 +385,7 @@ def test_clean_cut_after_a_tool_result_is_resumable(
|
||||
repo.checkout_new_branch("d")
|
||||
(repo.root / "partial.py").write_text("x")
|
||||
repo.commit_all("partial work")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolCall(call_id="c", tool="write_file", arguments={}))
|
||||
store.append_event(ToolResult(call_id="c", result="wrote", success=True))
|
||||
intent = pending_delegation_intent(store)
|
||||
@@ -400,9 +398,7 @@ def test_dangling_tool_call_gates(
|
||||
) -> None:
|
||||
# The effect of the call is unknown and not verifiable by inspection.
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolCall(call_id="c", tool="run_command", arguments={}))
|
||||
intent = pending_delegation_intent(store)
|
||||
|
||||
@@ -412,9 +408,7 @@ def test_dangling_tool_call_gates(
|
||||
def test_missing_branch_means_nothing_started(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolResult(call_id="c", result="ok", success=True))
|
||||
intent = pending_delegation_intent(store, branch="never-created")
|
||||
|
||||
@@ -425,9 +419,7 @@ def test_a_finished_delegation_settles_rather_than_resumes(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(
|
||||
DelegationCompleted(
|
||||
handback="done",
|
||||
@@ -448,9 +440,7 @@ def test_resume_continues_the_same_delegation(
|
||||
# Interrupt after one turn, then resume: no second delegation_started,
|
||||
# because L7 defines a session as the delegation, not the process.
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolResult(call_id="c", result="wrote a.py", success=True))
|
||||
intent = pending_delegation_intent(store)
|
||||
|
||||
@@ -484,9 +474,7 @@ def test_recover_resumes_a_clean_cut_with_a_notice(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolResult(call_id="c", result="wrote a.py", success=True))
|
||||
pending_delegation_intent(store)
|
||||
notices: list[str] = []
|
||||
@@ -505,9 +493,7 @@ def test_recover_settles_a_finished_delegation_without_resuming(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(
|
||||
DelegationCompleted(
|
||||
handback="done",
|
||||
@@ -539,9 +525,7 @@ def test_recover_leaves_an_unclean_cut_for_the_boot_gate(
|
||||
store: StateStore, ws: Workspace, repo: GitRepo
|
||||
) -> None:
|
||||
repo.checkout_new_branch("d")
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(ToolCall(call_id="c", tool="run_command", arguments={}))
|
||||
pending_delegation_intent(store)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def record_pair(
|
||||
*,
|
||||
brief: str = "add greet",
|
||||
model: str = "claude-haiku-4-5",
|
||||
role: str = "delegate",
|
||||
role: str = "helper",
|
||||
status: str = "handback",
|
||||
) -> int:
|
||||
store.append_event(
|
||||
@@ -56,15 +56,13 @@ def test_unpaired_start_is_skipped(store: StateStore) -> None:
|
||||
# A crash mid-delegation leaves a start with no completion; a half-formed
|
||||
# record would pollute the set L9 exists to make trustworthy.
|
||||
store.append_event(
|
||||
DelegationStarted(brief="died", role="delegate", model="m", caps={})
|
||||
DelegationStarted(brief="died", role="helper", model="m", caps={})
|
||||
)
|
||||
assert collect(store) == []
|
||||
|
||||
|
||||
def test_unrelated_events_between_the_pair_are_ignored(store: StateStore) -> None:
|
||||
store.append_event(
|
||||
DelegationStarted(brief="b", role="delegate", model="m", caps={})
|
||||
)
|
||||
store.append_event(DelegationStarted(brief="b", role="helper", model="m", caps={}))
|
||||
store.append_event(AssistantText(text="working", model="m"))
|
||||
store.append_event(
|
||||
DelegationCompleted(
|
||||
@@ -97,10 +95,10 @@ def test_a_verdict_can_be_revised(store: StateStore) -> None:
|
||||
|
||||
|
||||
def test_consults_and_delegations_share_the_set(store: StateStore) -> None:
|
||||
record_pair(store, role="delegate")
|
||||
record_pair(store, role="consultant", brief="which approach?")
|
||||
record_pair(store, role="helper")
|
||||
record_pair(store, role="expert", brief="which approach?")
|
||||
roles = [r.role for r in collect(store)]
|
||||
assert roles == ["delegate", "consultant"]
|
||||
assert roles == ["helper", "expert"]
|
||||
|
||||
|
||||
def test_collect_can_start_after_a_marker(store: StateStore) -> None:
|
||||
|
||||
@@ -20,8 +20,8 @@ from nisse_dev.tools import Workspace
|
||||
ROLES = RoleMap(
|
||||
executor="llama3.2",
|
||||
planner="claude-haiku-4-5",
|
||||
delegate="claude-haiku-4-5",
|
||||
consultant="claude-haiku-4-5",
|
||||
helper="claude-haiku-4-5",
|
||||
expert="claude-haiku-4-5",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ from nisse_dev.state import StateStore
|
||||
ROLES = RoleMap(
|
||||
executor="llama3.2",
|
||||
planner="haiku",
|
||||
delegate="haiku",
|
||||
consultant="claude-haiku-4-5",
|
||||
helper="haiku",
|
||||
expert="claude-haiku-4-5",
|
||||
)
|
||||
|
||||
GOOD = '{"answer": "Use SQLite; it is enough here.", "confidence": "high", "caveats": "Assumes one writer."}'
|
||||
@@ -137,7 +137,7 @@ def test_consults_are_recorded_as_composite_events(store: StateStore) -> None:
|
||||
events = [e.event for e in store.read_events()]
|
||||
started = next(e for e in events if isinstance(e, DelegationStarted))
|
||||
completed = next(e for e in events if isinstance(e, DelegationCompleted))
|
||||
assert started.role == "consultant"
|
||||
assert started.role == "expert"
|
||||
assert started.brief == "q"
|
||||
assert completed.exit_status == "answered"
|
||||
|
||||
|
||||
@@ -184,8 +184,8 @@ def test_malformed_response_raises_llm_error() -> None:
|
||||
ROLES = RoleMap(
|
||||
executor="local/qwen",
|
||||
planner="anthropic/claude-opus-5",
|
||||
delegate="anthropic/claude-opus-5",
|
||||
consultant="anthropic/claude-opus-5",
|
||||
helper="anthropic/claude-opus-5",
|
||||
expert="anthropic/claude-opus-5",
|
||||
)
|
||||
|
||||
|
||||
@@ -195,8 +195,8 @@ def test_role_map_resolves_and_pins() -> None:
|
||||
assert roles.pinned() == {
|
||||
"executor": "local/qwen",
|
||||
"planner": "anthropic/claude-opus-5",
|
||||
"delegate": "anthropic/claude-opus-5",
|
||||
"consultant": "anthropic/claude-opus-5",
|
||||
"helper": "anthropic/claude-opus-5",
|
||||
"expert": "anthropic/claude-opus-5",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ from nisse_dev.transport import InMemoryTransport
|
||||
ROLES = RoleMap(
|
||||
executor="local/qwen",
|
||||
planner="anthropic/claude-opus-5",
|
||||
delegate="anthropic/claude-opus-5",
|
||||
consultant="anthropic/claude-opus-5",
|
||||
helper="anthropic/claude-opus-5",
|
||||
expert="anthropic/claude-opus-5",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ from nisse_dev.transport import InMemoryTransport
|
||||
ROLES = RoleMap(
|
||||
executor="llama3.2",
|
||||
planner="haiku",
|
||||
delegate="haiku",
|
||||
consultant="haiku",
|
||||
helper="haiku",
|
||||
expert="haiku",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from nisse_dev.publish import PublishResult
|
||||
from nisse_dev.state import StateStore
|
||||
from nisse_dev.transport import InMemoryTransport
|
||||
|
||||
ROLES = RoleMap(executor="x", planner="y", delegate="y", consultant="y")
|
||||
ROLES = RoleMap(executor="x", planner="y", helper="y", expert="y")
|
||||
|
||||
|
||||
class ScriptedClient:
|
||||
|
||||
@@ -31,8 +31,8 @@ from nisse_dev.worker import SerialWorker
|
||||
ROLES = RoleMap(
|
||||
executor="llama3.2",
|
||||
planner="claude-haiku-4-5",
|
||||
delegate="claude-haiku-4-5",
|
||||
consultant="claude-haiku-4-5",
|
||||
helper="claude-haiku-4-5",
|
||||
expert="claude-haiku-4-5",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user