Newer
Older
navi-1 / navi / tools / reflect.py
"""
reflect — structured multi-perspective thinking tool.

Runs three parallel AIHelper calls with distinct advisor roles
(Critic, Pragmatist, Detailer) and returns their independent perspectives.
Use before planning a complex task or when stuck on a problem.

Designed to force explicit articulation of assumptions — the most common
source of planning errors — and get fresh perspectives unclouded by
accumulated conversation context.
"""

import asyncio

from navi.tools._internal.base import Tool, ToolContext, ToolResult

# ── Advisor system prompts ─────────────────────────────────────────────────

_CRITIC_SYSTEM = """\
You are a critical advisor. Your role is to challenge the situation description and expose weaknesses.

Focus on:
- Which stated assumptions are likely wrong or unverified?
- What risks and failure modes are being ignored?
- What logical gaps or contradictions exist in the approach?
- What is being taken for granted that should be questioned?

Be direct and specific. Do not be encouraging. Name concrete problems, not abstract concerns.
Do not invent missing facts. If evidence is needed, say exactly what to verify.
Keep your response concise: 3 to 6 sharp points."""

_PRAGMATIST_SYSTEM = """\
You are a pragmatic advisor. Your role is to find the simplest, most direct path to the goal.

Focus on:
- What is the minimal viable approach that actually solves the problem?
- What complexity can be eliminated without losing the core outcome?
- What alternatives or shortcuts have not been considered?
- Is the stated goal actually the real goal, or is there a simpler reframing?

Challenge over-engineering. Propose concrete simplifications.
Prefer actions that gather missing information before asking the user.
Keep your response concise: 3 to 6 actionable points."""

_DETAILER_SYSTEM = """\
You are a detail-oriented advisor. Your role is to find what is missing, ambiguous, or underspecified.

Focus on:
- What edge cases or failure scenarios have not been addressed?
- What requirements or constraints are implied but not stated?
- What implementation details will become blockers later if not resolved now?
- What information is missing before a good decision can be made?

Be specific about what is absent, not just that something might be missing.
For each important gap, say whether to resolve it by checking docs/files/tools/web or by asking the user.
Keep your response concise: 3 to 6 concrete gaps."""

# ── Prompt builder ─────────────────────────────────────────────────────────

def _build_user_prompt(situation: str, assumptions: list[str], tried: str | None) -> str:
    parts = [f"## Situation\n{situation.strip()}"]

    if assumptions:
        bullet_assumptions = "\n".join(f"- {a.strip()}" for a in assumptions if a.strip())
        parts.append(f"## Assumptions being made\n{bullet_assumptions}")
    else:
        parts.append("## Assumptions being made\n(none stated)")

    if tried and tried.strip():
        parts.append(f"## Already attempted\n{tried.strip()}")

    return "\n\n".join(parts)


# ── Tool ───────────────────────────────────────────────────────────────────

class ReflectTool(Tool):
    name = "reflect"
    description = (
        "Get three independent expert perspectives on a situation before planning or when stuck.\n\n"
        "Call this when:\n"
        "- About to plan a complex or ambiguous task\n"
        "- Stuck on a problem and need a fresh angle\n"
        "- Unsure whether your approach is right\n\n"
        "Three advisors analyse your situation in parallel:\n"
        "· Critic — challenges assumptions, surfaces risks and flaws\n"
        "· Pragmatist — finds the simplest path, cuts unnecessary complexity\n"
        "· Detailer — spots missing requirements, edge cases, and gaps\n\n"
        "IMPORTANT: The `assumptions` field is mandatory and is the most valuable input. "
        "List every belief you are acting on without having verified it. "
        "The act of listing assumptions often reveals the problem itself."
    )
    parameters = {
        "type": "object",
        "properties": {
            "situation": {
                "type": "string",
                "description": (
                    "Describe the goal and the current situation clearly. "
                    "Include: what you are trying to achieve, the approach you are considering, "
                    "and what specifically you are unsure about."
                ),
            },
            "assumptions": {
                "type": "array",
                "items": {"type": "string"},
                "description": (
                    "List every assumption you are making — things you believe are true "
                    "but have not verified. Be honest and thorough. "
                    "Example: 'the API returns data in this format', "
                    "'the user wants X not Y', 'this file will always exist'."
                ),
            },
            "tried": {
                "type": "string",
                "description": (
                    "Optional. What you have already attempted and why it did not work. "
                    "Provide this when you are stuck, not when planning from scratch."
                ),
            },
        },
        "required": ["situation", "assumptions"],
    }

    def __init__(self, ai_helper) -> None:
        self._ai = ai_helper

    async def execute(self, params: dict, ctx: ToolContext | None = None) -> ToolResult:
        situation  = (params.get("situation") or "").strip()
        assumptions = params.get("assumptions") or []
        tried      = (params.get("tried") or "").strip() or None

        if not situation:
            return ToolResult(success=False, output="", error="situation is required")

        user_prompt = _build_user_prompt(situation, assumptions, tried)

        # Run all three advisors in parallel
        critic_task     = self._ai.ask(_CRITIC_SYSTEM,     user_prompt)
        pragmatist_task = self._ai.ask(_PRAGMATIST_SYSTEM, user_prompt)
        detailer_task   = self._ai.ask(_DETAILER_SYSTEM,   user_prompt)

        critic, pragmatist, detailer = await asyncio.gather(
            critic_task, pragmatist_task, detailer_task
        )

        output = (
            "# Reflection\n\n"
            "## Critic\n"
            f"{critic}\n\n"
            "## Pragmatist\n"
            f"{pragmatist}\n\n"
            "## Detailer\n"
            f"{detailer}\n\n"
            "---\n"
            "Integrate these perspectives into your plan.\n"
            "First resolve missing information through available sources: connected MCP knowledge servers, docs, manuals, "
            "memory, files, tool schemas, command output, or web research. "
            "Ask the user only when the missing decision is genuinely theirs to make or cannot be "
            "recovered from available sources. "
            "Prioritise addressing points raised by the Critic before proceeding."
        )

        return ToolResult(success=True, output=output)