Newer
Older
navi-1 / navi / profiles / base.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy on 1 May 5 KB Disable thinking stalls for 3D subagents
from pydantic import BaseModel, Field, field_validator


class AgentProfile(BaseModel):
    """
    Defines a complete agent configuration.
    A profile ties together a system prompt, an LLM backend, a model,
    and the set of tools the agent is allowed to use.
    """

    model_config = {"extra": "allow"}

    id: str
    name: str
    description: str
    system_prompt: str
    enabled_tools: list[str]  # tool names; resolved by ToolRegistry at runtime
    llm_backend: str = "ollama"  # backend key, e.g. "ollama", "openai"
    # Ordered list of preferred models; first available wins at runtime.
    # Accepts a plain string for backward compatibility (auto-wrapped in a list).
    model: list[str] = Field(default_factory=lambda: ["gemma4:31b-cloud"])

    max_iterations: int = 10
    temperature: float = 0.7
    top_k: int | None = None
    top_p: float | None = None
    # Number of CPU threads for local inference. None = Ollama default (physical cores).
    # Cloud models ignore this option.
    num_thread: int | None = None
    planning_enabled: bool = False  # if True, run a planning LLM call before the main loop

    # Profile discoverability — used for system prompt injection and list_profiles tool.
    # short_description: 1-line summary shown in every system prompt to all profiles.
    # full_description: structured dict with keys: specialization, when_to_use, key_tools.
    short_description: str = ""
    full_description: dict = Field(default_factory=dict)

    # ── Thinking mechanics ────────────────────────────────────────────────────
    # Each flag can be set per-profile in config.json to tune the balance
    # between reasoning depth and response latency.

    # Extended reasoning: passes think=True to the LLM on every main-loop call.
    # Disable for latency-sensitive profiles (e.g. smart_home).
    think_enabled: bool = True

    # Inject remaining iteration count at the end of every LLM context so the
    # model knows when to wrap up instead of hitting the limit blindly.
    iteration_budget_enabled: bool = True

    # ── Planning phases ───────────────────────────────────────────────────────
    # planning_mandatory: if True, the DIRECT shortcut is never offered to the
    #   model — planning always runs in full. If False, the model can output DIRECT
    #   to skip straight to execution (simple requests bypass planning).
    #   First-message planning is always forced regardless of this flag.
    planning_mandatory: bool = False

    # Individual phase switches — allow disabling expensive phases for profiles
    # that don't need them.
    # Phase 1: task analysis (TASK/GOAL/UNKNOWNS). Entry point for the pipeline.
    planning_phase1_enabled: bool = True
    # Phase 2: structured review (Critic/Pragmatist/Detailer + Plan Adjustments).
    #   Adds 1 LLM call only when Phase 1 outputs REFLECT: yes.
    planning_phase2_enabled: bool = False
    # Phase 3: structured execution plan (numbered steps with TOOL/AGENT/SELF).
    planning_phase3_enabled: bool = True

    # Inject a goal-reminder system message every N iterations to prevent drift
    # on long tasks. N = goal_anchoring_interval.
    goal_anchoring_enabled: bool = True
    goal_anchoring_interval: int = 5

    # Detect when the model is looping without todo progress for
    # anti_stall_threshold iterations and inject a hard warning.
    anti_stall_enabled: bool = True
    anti_stall_threshold: int = 8

    # After the model marks a todo step as done, run a lightweight LLM check:
    # "did the result actually satisfy the step goal?" Adds ~1 LLM call per step.
    step_validation_enabled: bool = False

    # When a todo step is marked as failed, trigger a lightweight re-planning
    # pass to adjust the remaining steps. Depends on step_validation.
    adaptive_replan_enabled: bool = False

    # Sub-agent configuration
    # subagent_tools: tool names available to sub-agents spawned from this profile.
    #   If empty, falls back to enabled_tools minus dangerous/irrelevant ones.
    # subagent_planning_enabled: if True, sub-agents run the planning phase before their tool loop.
    # subagent_think_enabled: controls extended reasoning for sub-agents. If None,
    #   sub-agents inherit think_enabled from the parent profile.
    # subagent_system_prompt: injected as an additional system message for sub-agents,
    #   after the profile's main system_prompt. Loaded from subagent_system_prompt.txt if present.
    subagent_tools: list[str] = Field(default_factory=list)
    subagent_planning_enabled: bool = False
    subagent_think_enabled: bool | None = None
    subagent_system_prompt: str = ""

    # Extra context providers to inject for this profile (by name).
    # Global providers (global_provider=True) are always injected regardless of this list.
    context_providers: list[str] = Field(default_factory=list)

    @field_validator("model", mode="before")
    @classmethod
    def _coerce_model(cls, v):
        if isinstance(v, str):
            return [v] if v else ["gemma4:31b-cloud"]
        return v