"""
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.base import Tool, 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.
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.
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.
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) -> 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. "
"Prioritise addressing points raised by the Critic before proceeding."
)
return ToolResult(success=True, output=output)