2026-06-11 04:36:17 +02:00
|
|
|
"""The prompt composer injects the autogen verb table.
|
2026-05-05 03:19:43 +02:00
|
|
|
|
|
|
|
|
`compose_prompt` reads `agents/prompts/_generated/<role>.md` and
|
|
|
|
|
includes it as a composition layer (between role and team). This pins
|
|
|
|
|
that contract: when the file exists, its content appears in the
|
|
|
|
|
composed prompt.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from roboco.agents.factories._base import compose_prompt
|
|
|
|
|
from roboco.models.base import AgentRole, Team
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _write_layer(root: Path, rel: str, body: str) -> None:
|
|
|
|
|
target = root / rel
|
|
|
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
target.write_text(body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def fake_prompts(tmp_path: Path) -> Path:
|
|
|
|
|
"""Build a fake prompts directory with a known _generated/<role>.md."""
|
|
|
|
|
_write_layer(tmp_path, "base.md", "# BASE LAYER")
|
|
|
|
|
_write_layer(tmp_path, "roles/developer.md", "# ROLE LAYER (developer)")
|
|
|
|
|
_write_layer(tmp_path, "teams/backend.md", "# TEAM LAYER (backend)")
|
|
|
|
|
_write_layer(
|
|
|
|
|
tmp_path,
|
|
|
|
|
"_generated/developer.md",
|
|
|
|
|
"# AUTOGEN LAYER (developer)\n\n"
|
|
|
|
|
"## Verbs available to you (autogenerated source of truth)\n\n"
|
|
|
|
|
"| Verb | Body schema |\n|------|-------------|\n"
|
|
|
|
|
"| `give_me_work` | `give_me_work()` |\n",
|
|
|
|
|
)
|
|
|
|
|
_write_layer(tmp_path, "identities/be-dev-1.md", "# IDENTITY (be-dev-1)")
|
|
|
|
|
return tmp_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compose_prompt_includes_autogen_layer(fake_prompts: Path) -> None:
|
|
|
|
|
composed = compose_prompt(
|
|
|
|
|
role=AgentRole.DEVELOPER,
|
|
|
|
|
team=Team.BACKEND,
|
|
|
|
|
agent_slug="be-dev-1",
|
|
|
|
|
base_path=fake_prompts,
|
|
|
|
|
)
|
|
|
|
|
assert "AUTOGEN LAYER (developer)" in composed
|
|
|
|
|
assert "give_me_work()" in composed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compose_prompt_orders_layers_correctly(fake_prompts: Path) -> None:
|
|
|
|
|
"""base → role → autogen → team → identity (composer's documented order)."""
|
|
|
|
|
composed = compose_prompt(
|
|
|
|
|
role=AgentRole.DEVELOPER,
|
|
|
|
|
team=Team.BACKEND,
|
|
|
|
|
agent_slug="be-dev-1",
|
|
|
|
|
base_path=fake_prompts,
|
|
|
|
|
)
|
|
|
|
|
base_idx = composed.index("BASE LAYER")
|
|
|
|
|
role_idx = composed.index("ROLE LAYER (developer)")
|
|
|
|
|
autogen_idx = composed.index("AUTOGEN LAYER (developer)")
|
|
|
|
|
team_idx = composed.index("TEAM LAYER (backend)")
|
|
|
|
|
identity_idx = composed.index("IDENTITY (be-dev-1)")
|
|
|
|
|
assert base_idx < role_idx < autogen_idx < team_idx < identity_idx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compose_prompt_omits_autogen_when_file_missing(tmp_path: Path) -> None:
|
|
|
|
|
"""No _generated/<role>.md → composer skips that layer cleanly."""
|
|
|
|
|
_write_layer(tmp_path, "base.md", "# BASE")
|
|
|
|
|
_write_layer(tmp_path, "roles/developer.md", "# DEV ROLE")
|
|
|
|
|
_write_layer(tmp_path, "identities/be-dev-1.md", "# IDENTITY")
|
|
|
|
|
|
|
|
|
|
composed = compose_prompt(
|
|
|
|
|
role=AgentRole.DEVELOPER,
|
|
|
|
|
team=None,
|
|
|
|
|
agent_slug="be-dev-1",
|
|
|
|
|
base_path=tmp_path,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert "BASE" in composed
|
|
|
|
|
assert "DEV ROLE" in composed
|
|
|
|
|
assert "IDENTITY" in composed
|
|
|
|
|
assert "AUTOGEN" not in composed # no autogen layer file
|