Skip to content

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.

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
}
ValueSource
claude-code-hookClaude Code hooks
codex-hookCodex hooks
antigravity-hookAntigravity hooks
mcp-proxyThe Omnodex MCP proxy (Cowork, Codex MCP, Antigravity MCP, and any other MCP client)
analyzerRisk detection
mockomnodex spike test sessions
{
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)
}
{
event_type: "session.ended";
duration_ms: number;
status: "completed" | "errored" | "interrupted";
}

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;
}

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
}

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;
}

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;
}

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
}

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
}

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;
}

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>.jsonl

Each 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:

Terminal window
# Count events across all sessions
cat ~/.omnodex/event-log/sessions/*.jsonl | wc -l
# Tool calls only
cat ~/.omnodex/event-log/sessions/*.jsonl | jq -c 'select(.event_type == "tool.invoked") | {tool_name, mcp_server}'
# Shell commands
cat ~/.omnodex/event-log/sessions/*.jsonl | jq -c 'select(.event_type == "tool.invoked" and .tool_name == "Bash") | .parameters'