fix(tier3): adapt define/patch to b125 role schema (was model)
Live Worldtree b125 changed POST /agents/define: the request field is now 'role' (a model-role like 'thoughtful-character'), replacing 'model'; the response still echoes it as 'model'. Update define_agent/patch_agent request bodies + CLI (--model -> --role); response parse + LocalAgentEntry unchanged. Verified end-to-end against live (delete->define round-trip); 26 tier3 tests green. Full b22->b125 spec-pin bump remains a follow-up.
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ratatoskr"
|
||||
version = "0.21.0"
|
||||
version = "0.21.1"
|
||||
description = "Worldtree Conversation API debug console (web + headless CLI) — multi-pane observability"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
+17
-15
@@ -151,7 +151,7 @@ async def define_agent(
|
||||
*,
|
||||
agent_name: str,
|
||||
system_prompt: str,
|
||||
model: str,
|
||||
role: str,
|
||||
) -> Tier3AgentInfo:
|
||||
"""POST /agents/define — create a Tier 3 agent.
|
||||
|
||||
@@ -165,12 +165,14 @@ async def define_agent(
|
||||
f"agent_name must match [a-z][a-z0-9-]{{2,63}}: {agent_name!r}"
|
||||
)
|
||||
assert system_prompt, "system_prompt must be non-empty"
|
||||
assert model, "model must be non-empty"
|
||||
assert role, "role must be non-empty"
|
||||
|
||||
# b125 drift: /agents/define takes `role` (a model-role, e.g. "thoughtful-character")
|
||||
# in the request; the response echoes it back as `model`. See #15 follow-up.
|
||||
body = {
|
||||
"agent_name": agent_name,
|
||||
"system_prompt": system_prompt,
|
||||
"model": model,
|
||||
"role": role,
|
||||
}
|
||||
resp = await client.post("/agents/define", json=body)
|
||||
|
||||
@@ -198,7 +200,7 @@ async def patch_agent(
|
||||
agent_id: str,
|
||||
*,
|
||||
system_prompt: str | None = None,
|
||||
model: str | None = None,
|
||||
role: str | None = None,
|
||||
) -> Tier3AgentInfo:
|
||||
"""PATCH /agents/<id> — mutate system_prompt and/or model.
|
||||
|
||||
@@ -207,15 +209,15 @@ async def patch_agent(
|
||||
"""
|
||||
assert client is not None
|
||||
assert ":" in agent_id, f"tier 3 agent_id must contain ':': {agent_id!r}"
|
||||
assert system_prompt is not None or model is not None, (
|
||||
"patch requires at least one of system_prompt or model"
|
||||
assert system_prompt is not None or role is not None, (
|
||||
"patch requires at least one of system_prompt or role"
|
||||
)
|
||||
|
||||
body: dict[str, str] = {}
|
||||
if system_prompt is not None:
|
||||
body["system_prompt"] = system_prompt
|
||||
if model is not None:
|
||||
body["model"] = model
|
||||
if role is not None:
|
||||
body["role"] = role
|
||||
resp = await client.patch(f"/agents/{agent_id}", json=body)
|
||||
|
||||
if resp.status_code == 200:
|
||||
@@ -278,14 +280,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
help="System prompt the agent ships with.",
|
||||
)
|
||||
p_define.add_argument(
|
||||
"--model", required=True,
|
||||
help="Provider model ID (NOT a profile alias; e.g., qwen3.6-35-a3b).",
|
||||
"--role", required=True,
|
||||
help="Model role, e.g. thoughtful-character (see GET /models/available-for-characters).",
|
||||
)
|
||||
|
||||
p_patch = sub.add_parser("patch", help="Mutate system_prompt and/or model.")
|
||||
p_patch.add_argument("agent_id", help='Full "<user_id>:<agent_name>" form.')
|
||||
p_patch.add_argument("--system-prompt", dest="system_prompt", default=None)
|
||||
p_patch.add_argument("--model", default=None)
|
||||
p_patch.add_argument("--role", default=None)
|
||||
|
||||
p_delete = sub.add_parser("delete", help="Hard-delete a Tier 3 agent.")
|
||||
p_delete.add_argument("agent_id", help='Full "<user_id>:<agent_name>" form.')
|
||||
@@ -327,7 +329,7 @@ async def _run_define(ns: argparse.Namespace) -> int:
|
||||
client,
|
||||
agent_name=ns.name,
|
||||
system_prompt=ns.system_prompt,
|
||||
model=ns.model,
|
||||
role=ns.role,
|
||||
)
|
||||
# v0.8.0: persist to local index so the picker can show it.
|
||||
add_local_agent(
|
||||
@@ -352,9 +354,9 @@ async def _run_patch(ns: argparse.Namespace) -> int:
|
||||
update_local_agent,
|
||||
)
|
||||
|
||||
if ns.system_prompt is None and ns.model is None:
|
||||
if ns.system_prompt is None and ns.role is None:
|
||||
raise _Tier3UsageError(
|
||||
"patch requires at least one of --system-prompt or --model"
|
||||
"patch requires at least one of --system-prompt or --role"
|
||||
)
|
||||
async with httpx.AsyncClient(
|
||||
base_url=server_url,
|
||||
@@ -368,7 +370,7 @@ async def _run_patch(ns: argparse.Namespace) -> int:
|
||||
client,
|
||||
ns.agent_id,
|
||||
system_prompt=ns.system_prompt,
|
||||
model=ns.model,
|
||||
role=ns.role,
|
||||
)
|
||||
# v0.8.0: refresh local index with the post-patch state.
|
||||
update_local_agent(
|
||||
|
||||
+17
-17
@@ -43,7 +43,7 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="wizard",
|
||||
system_prompt="You are a wizard.",
|
||||
model="qwen3.6-35-a3b",
|
||||
role="qwen3.6-35-a3b",
|
||||
)
|
||||
assert isinstance(info, Tier3AgentInfo)
|
||||
assert info.agent_id == "ratatoskr:wizard"
|
||||
@@ -64,14 +64,14 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="wizard",
|
||||
system_prompt="You are a wizard.",
|
||||
model="qwen3.6-35-a3b",
|
||||
role="qwen3.6-35-a3b",
|
||||
)
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
# INV-001: exactly these three keys — no layer fields, no metadata.
|
||||
assert body == {
|
||||
"agent_name": "wizard",
|
||||
"system_prompt": "You are a wizard.",
|
||||
"model": "qwen3.6-35-a3b",
|
||||
"role": "qwen3.6-35-a3b",
|
||||
}
|
||||
|
||||
@respx.mock
|
||||
@@ -90,7 +90,7 @@ class TestDefineAgent:
|
||||
client,
|
||||
agent_name="overflow",
|
||||
system_prompt="x",
|
||||
model="m",
|
||||
role="m",
|
||||
)
|
||||
assert exc.value.retry_after == 0
|
||||
|
||||
@@ -105,7 +105,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(Tier3UserIdUnsupported):
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
|
||||
@respx.mock
|
||||
@@ -120,7 +120,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(Tier3LayerDeferred) as exc:
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert exc.value.field == "persona"
|
||||
|
||||
@@ -133,7 +133,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="Wizard", system_prompt="x", model="m"
|
||||
client, agent_name="Wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -146,7 +146,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="ab", system_prompt="x", model="m"
|
||||
client, agent_name="ab", system_prompt="x", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -159,7 +159,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="", model="m"
|
||||
client, agent_name="wizard", system_prompt="", role="m"
|
||||
)
|
||||
assert route.call_count == 0
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestDefineAgent:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await define_agent(
|
||||
client, agent_name="wizard", system_prompt="x", model="m"
|
||||
client, agent_name="wizard", system_prompt="x", role="m"
|
||||
)
|
||||
assert exc.value.status == 503
|
||||
|
||||
@@ -196,16 +196,16 @@ class TestPatchAgent:
|
||||
client,
|
||||
"ratatoskr:wizard",
|
||||
system_prompt="new prompt",
|
||||
model="different-model",
|
||||
role="different-model",
|
||||
)
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
assert body == {"system_prompt": "new prompt", "model": "different-model"}
|
||||
assert body == {"system_prompt": "new prompt", "role": "different-model"}
|
||||
assert info.system_prompt == "new prompt"
|
||||
assert info.model == "different-model"
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_patch_single_field(self) -> None:
|
||||
"""happy_patch_single_field: omit model → body has system_prompt only."""
|
||||
"""happy_patch_single_field: omit role → body has system_prompt only."""
|
||||
import json as _json
|
||||
|
||||
updated = {**_FULL_AGENT_RESP, "system_prompt": "only this"}
|
||||
@@ -349,7 +349,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "You are a wizard.",
|
||||
"--model", "qwen3.6-35-a3b",
|
||||
"--role", "qwen3.6-35-a3b",
|
||||
])
|
||||
out = capsys.readouterr()
|
||||
assert rc == 0
|
||||
@@ -429,7 +429,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 11
|
||||
@@ -449,7 +449,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 20
|
||||
@@ -473,7 +473,7 @@ class TestCli:
|
||||
"define",
|
||||
"--name", "wizard",
|
||||
"--system-prompt", "x",
|
||||
"--model", "m",
|
||||
"--role", "m",
|
||||
])
|
||||
err = capsys.readouterr().err
|
||||
assert rc == 20
|
||||
|
||||
Reference in New Issue
Block a user