Initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
agents/timeline.py
Normal file
88
agents/timeline.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Timeline Agent — correlates evidence across time."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from base_agent import BaseAgent
|
||||
from evidence_graph import EvidenceGraph
|
||||
from llm_client import LLMClient
|
||||
from tool_registry import TOOL_CATALOG
|
||||
|
||||
|
||||
class TimelineAgent(BaseAgent):
|
||||
name = "timeline"
|
||||
role = (
|
||||
"Timeline forensic analyst. You build chronological timelines from filesystem "
|
||||
"MAC timestamps and correlate events across all phenomena categories in the "
|
||||
"evidence graph to reconstruct the sequence of activities on the system."
|
||||
)
|
||||
|
||||
def __init__(self, llm: LLMClient, graph: EvidenceGraph) -> None:
|
||||
super().__init__(llm, graph)
|
||||
self._register_tools()
|
||||
|
||||
def _register_tools(self) -> None:
|
||||
# Filesystem timeline tool from catalog
|
||||
td = TOOL_CATALOG.get("build_filesystem_timeline")
|
||||
if td:
|
||||
self.register_tool(td.name, td.description, td.input_schema, td.executor)
|
||||
|
||||
# Custom tool to get all phenomena with timestamps for correlation
|
||||
self.register_tool(
|
||||
name="get_timestamped_phenomena",
|
||||
description="Get all phenomena that have timestamps, sorted chronologically. Use for timeline correlation.",
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
executor=self._get_timestamped_phenomena,
|
||||
)
|
||||
|
||||
# Tool to add temporal edges between phenomena
|
||||
self.register_tool(
|
||||
name="add_temporal_edge",
|
||||
description="Add a temporal relationship between two phenomena (before, after, or concurrent).",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source_id": {"type": "string", "description": "ID of the earlier/source phenomenon."},
|
||||
"target_id": {"type": "string", "description": "ID of the later/target phenomenon."},
|
||||
"relation": {
|
||||
"type": "string",
|
||||
"enum": ["before", "after", "concurrent"],
|
||||
"description": "Temporal relationship.",
|
||||
},
|
||||
},
|
||||
"required": ["source_id", "target_id", "relation"],
|
||||
},
|
||||
executor=self._add_temporal_edge,
|
||||
)
|
||||
|
||||
async def _get_timestamped_phenomena(self) -> str:
|
||||
items = [
|
||||
ph for ph in self.graph.phenomena.values()
|
||||
if ph.timestamp
|
||||
]
|
||||
items.sort(key=lambda ph: ph.timestamp or "")
|
||||
|
||||
if not items:
|
||||
return "No phenomena with timestamps found."
|
||||
|
||||
lines = []
|
||||
for ph in items:
|
||||
lines.append(f"{ph.timestamp} | [{ph.category}] {ph.title} ({ph.id})")
|
||||
lines.append(f" {ph.description[:150]}")
|
||||
return "\n".join(lines)
|
||||
|
||||
async def _add_temporal_edge(
|
||||
self, source_id: str, target_id: str, relation: str,
|
||||
) -> str:
|
||||
try:
|
||||
await self.graph.add_edge(
|
||||
source_id=source_id,
|
||||
target_id=target_id,
|
||||
edge_type="temporal",
|
||||
metadata={"relation": relation},
|
||||
created_by=self.name,
|
||||
)
|
||||
return f"Temporal edge added: {source_id} —[{relation}]→ {target_id}"
|
||||
except ValueError as e:
|
||||
return f"Error: {e}"
|
||||
Reference in New Issue
Block a user