Newer
Older
navi-1 / tests / unit / core / test_subagent_prompt.py
from types import SimpleNamespace

import pytest

from navi.config import Settings, settings
from navi.core.subagent_runner import _SCOPE_ANCHOR, build_subagent_system_prompt


def profile(system_prompt="You are the main agent.", subagent_system_prompt=""):
    return SimpleNamespace(system_prompt=system_prompt, subagent_system_prompt=subagent_system_prompt)


class TestBuildSubagentSystemPrompt:
    def test_uses_the_worker_prompt_when_the_profile_has_one(self):
        text = build_subagent_system_prompt(
            profile("main persona", "You are a worker.")
        )

        assert "You are a worker." in text
        assert "main persona" not in text
        assert _SCOPE_ANCHOR in text

    def test_falls_back_to_the_main_prompt_without_a_worker_prompt(self):
        """The anchor used to be appended unconditionally, which made the
        fallback dead code: a profile without a subagent prompt reached the
        model as a bare scope rule with no role."""
        text = build_subagent_system_prompt(profile("main persona"))

        assert "main persona" in text
        assert _SCOPE_ANCHOR in text
        assert text.index("main persona") < text.index(_SCOPE_ANCHOR)

    def test_inherit_adds_the_main_prompt_on_top_of_the_worker_prompt(self):
        text = build_subagent_system_prompt(
            profile("main persona", "You are a worker."),
            inherit_system_prompt=True,
        )

        assert "main persona" in text
        assert "You are a worker." in text

    def test_custom_prompt_sits_before_the_anchor(self):
        text = build_subagent_system_prompt(
            profile("main persona"),
            custom_system_prompt="You are a security auditor.",
        )

        assert "security auditor" in text
        assert text.index("security auditor") < text.index(_SCOPE_ANCHOR)

    def test_briefing_and_parent_context_are_appended_last(self):
        text = build_subagent_system_prompt(
            profile("main persona"),
            briefing="Host: 10.0.0.5",
            parent_session_id="s1",
        )

        assert "## Task context" in text
        assert "Host: 10.0.0.5" in text
        assert f"{settings.session_files_dir}/s1/" in text
        assert text.index(_SCOPE_ANCHOR) < text.index("## Task context")

    def test_every_part_is_separated(self):
        text = build_subagent_system_prompt(
            profile("main persona", "You are a worker."),
            custom_system_prompt="Custom.",
            briefing="Brief.",
        )

        assert text.count("\n\n---\n\n") == 3

    @pytest.mark.parametrize(
        ("name", "expected"),
        [
            ("subagent_thinking_stall_chars", 48_000),
            ("subagent_timeout_seconds", 600.0),
        ],
    )
    def test_settings_raise_the_old_guards(self, name, expected):
        # 12k thinking chars / 300s cut healthy cloud-model runs; a regression
        # here silently turns every long reasoning step into "stalled".
        assert Settings.model_fields[name].default == expected