Initial commit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
BattleTag
2026-05-09 17:36:26 +08:00
commit 097d2ce472
25 changed files with 5944 additions and 0 deletions

88
agents/timeline.py Normal file
View 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}"