Event Schema
Every interceptor writes the same event format, so detection, the dashboards, and your own tools can treat events from Claude Code, Codex, Cowork, and Antigravity the same way.
Common fields
Section titled “Common fields”Every event has these fields:
interface BaseEvent { schema_version: 1; // Event format version event_id: string; // Unique event ID session_id: string; // Session the event belongs to occurred_at: string; // When it happened (ISO 8601) recorded_at: string; // When Omnodex recorded it (ISO 8601) interceptor: Interceptor; // What captured it event_type: EventType; // What kind of event it is platform?: string; // Agent runtime, when known (for example "codex") agent_id?: string; // The subagent the event happened in, when it ran inside one}interceptor
Section titled “interceptor”| Value | Source |
|---|---|
claude-code-hook | Claude Code hooks |
codex-hook | Codex hooks |
antigravity-hook | Antigravity hooks |
mcp-proxy | The Omnodex MCP proxy (Cowork, Codex MCP, Antigravity MCP, and any other MCP client) |
analyzer | Risk detection |
mock | omnodex spike test sessions |
Event types
Section titled “Event types”session.started
Section titled “session.started”{ event_type: "session.started"; user: string; // OS user running the agent or proxy project_path: string; // Working directory of the session mcp_servers: string[]; // Upstream servers (proxy sessions)}session.ended
Section titled “session.ended”{ event_type: "session.ended"; duration_ms: number; status: "completed" | "errored" | "interrupted";}session.renamed
Section titled “session.renamed”Written when a session’s title changes: you renamed it, or the agent named it. The latest one is the session’s title. Claude Code and Codex hooks write it; see Session titles for where each title comes from.
{ event_type: "session.renamed"; title: string;}tool.invoked
Section titled “tool.invoked”Written when a tool call starts.
{ event_type: "tool.invoked"; tool_call_id: string; // Links to the matching tool.completed tool_name: string; // For example "Bash", "Edit", or "filesystem__read_file" mcp_server: string; // Owning MCP server, or "builtin" for the agent's own tools parameters: Record<string, unknown>; // Values may be "[REDACTED]" if redaction is on cwd?: string; // Working directory, when the agent reports it}tool.completed
Section titled “tool.completed”Written when a tool call finishes. Tool output is not stored, only its size.
{ event_type: "tool.completed"; tool_call_id: string; duration_ms: number; status: "success" | "error"; response_bytes: number; error_message?: string;}file.read and file.written
Section titled “file.read and file.written”Written for file reads and writes the interceptor can identify, currently Claude Code’s file tools.
{ event_type: "file.read" | "file.written"; path: string; // File path, or the search pattern for Grep and Glob bytes: number;}prompt.submitted
Section titled “prompt.submitted”Written when you submit a prompt. The full text is kept, as tool parameters are. Claude Code hooks write it.
{ event_type: "prompt.submitted"; prompt: string; prompt_id?: string; // The agent's own id for the turn, when it sends one}subagent.started and subagent.stopped
Section titled “subagent.started and subagent.stopped”Written when the agent starts a subagent and when it finishes. On these two events agent_id names the subagent itself; the tool calls it makes carry the same agent_id. The subagent’s final message is not stored, only its size.
{ event_type: "subagent.started" | "subagent.stopped"; agent_id: string; agent_type?: string; // For example "Explore" duration_ms?: number; // subagent.stopped only status?: "completed" | "errored" | "interrupted"; // subagent.stopped only response_bytes?: number; // subagent.stopped only}permission.requested and permission.denied
Section titled “permission.requested and permission.denied”Written when the agent asks you to allow a tool call, and when a tool call is refused. Whether a call asks depends on your permission settings, so not every tool call has one.
{ event_type: "permission.requested" | "permission.denied"; tool_name: string; mcp_server: string; // As on tool.invoked parameters: Record<string, unknown>; tool_call_id?: string; // permission.denied, when the agent sends it reason?: string; // permission.denied only}risk.detected
Section titled “risk.detected”Written by detection when a rule matches.
{ event_type: "risk.detected"; severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; category: string; description: string; related_event_id: string; // tool_call_id of the call that triggered the rule rule_id: string;}Storage format
Section titled “Storage format”Events are stored as JSON Lines under the Omnodex home, one file per session:
$OMNODEX_HOME/event-log/ index.jsonl one line per session: session_id, registered_at, file sessions/<session_id>.jsonlEach line is one complete event:
{"schema_version":1,"event_id":"7c4137d2-1d73-44f4-89d6-312c04250883","session_id":"2bf17678-7b08-4f3f-805d-5058426114b5","occurred_at":"2026-09-16T20:20:39.350Z","recorded_at":"2026-09-16T20:20:39.350Z","interceptor":"mcp-proxy","event_type":"tool.invoked","tool_call_id":"f2cc6e27-457a-4b23-9643-77af2a68a534","tool_name":"filesystem__list_directory","mcp_server":"filesystem","parameters":{"path":"/home/case/project"}}Standard tools can process the files:
# Count events across all sessionscat ~/.omnodex/event-log/sessions/*.jsonl | wc -l
# Tool calls onlycat ~/.omnodex/event-log/sessions/*.jsonl | jq -c 'select(.event_type == "tool.invoked") | {tool_name, mcp_server}'
# Shell commandscat ~/.omnodex/event-log/sessions/*.jsonl | jq -c 'select(.event_type == "tool.invoked" and .tool_name == "Bash") | .parameters'Next steps
Section titled “Next steps”- Events and Traces - How events and sessions fit together
- Configuration - Where event data is stored