"""Hypothesis Agent — generates investigative hypotheses from phenomena. Generates hypotheses only. Phenomenon→Hypothesis linking is handled centrally by Orchestrator._judge_new_phenomena. Tool set is restricted to read-only graph queries + add_hypothesis to prevent the agent from creating phenomena, leads, or entity links. """ from __future__ import annotations import logging from base_agent import BaseAgent from evidence_graph import EvidenceGraph from llm_client import LLMClient logger = logging.getLogger(__name__) class HypothesisAgent(BaseAgent): name = "hypothesis" role = ( "Hypothesis analyst. You review all phenomena discovered so far " "and formulate investigative hypotheses about what happened on this system. " "Your ultimate goal: build the most complete picture of events that occurred." ) def __init__(self, llm: LLMClient, graph: EvidenceGraph) -> None: super().__init__(llm, graph) self._register_hypothesis_tools() def _register_graph_tools(self) -> None: """Restrict to read-only graph tools. add_hypothesis is registered separately.""" self._register_graph_read_tools() def _register_hypothesis_tools(self) -> None: self.register_tool( name="add_hypothesis", description=( "Create a new investigative hypothesis about what happened on the system. " "Each hypothesis should be a specific, testable claim." ), input_schema={ "type": "object", "properties": { "title": { "type": "string", "description": "Short title for the hypothesis.", }, "description": { "type": "string", "description": "Detailed description of what this hypothesis claims.", }, }, "required": ["title", "description"], }, executor=self._add_hypothesis, ) def _build_system_prompt(self, task: str) -> str: """Focused prompt — no INVESTIGATE/RECORD/LINK workflow.""" return ( f"You are {self.name}, a forensic hypothesis analyst.\n" f"Role: {self.role}\n\n" f"Image: {self.graph.image_path}\n" f"Current investigation state: {self.graph.stats_summary()}\n\n" f"Your task: {task}\n\n" f"WORKFLOW:\n" f"1. Call list_phenomena and search_graph to review existing findings.\n" f"2. For each hypothesis you want to record, call add_hypothesis (title + description).\n" f"3. Wrap a short summary in when you have generated 3-7 hypotheses.\n\n" f"STRICT BOUNDARIES:\n" f"- Your only mutation tool is add_hypothesis. Do NOT attempt list_directory, " f"parse_registry_key, extract_file, or any disk-image investigation tools — " f"they are not yours and you will get 'unknown tool' errors.\n" f"- You CANNOT create phenomena, leads, or entity links. The orchestrator handles " f"all phenomenon↔hypothesis linking after you finish.\n" f"- Each hypothesis must be specific and testable. Avoid generic templates like " f"'Unauthorized Remote Access' or 'Malware Deployment' unless concrete phenomena " f"in the graph already point to them.\n" f"- If the graph is empty, generate broad starting hypotheses and mark them " f"clearly as exploratory in their description so downstream agents know they " f"still need evidence." ) async def _add_hypothesis(self, title: str, description: str) -> str: hid = await self.graph.add_hypothesis( title=title, description=description, created_by=self.name, ) return f"Hypothesis created: {hid} — {title} (confidence: 0.50)"