Back to list
lanegrid

agtrace-provider-normalization

by lanegrid

See What Your AI Agent Is Actually Doing

20🍴 1📅 Jan 21, 2026

SKILL.md


name: agtrace-provider-normalization description: Investigate AI agent provider tool schemas (Claude Code, Codex, Gemini) and design unified domain abstractions in agtrace architecture.

Agtrace Provider Normalization Expert

This skill provides deep knowledge of how agtrace normalizes diverse AI agent log formats into unified domain types. Use this when working with provider implementations, tool normalization, or understanding the schema-on-read architecture.

Three-Tier Provider Architecture

Tier 1: Trait-Based Adapter Pattern

Every provider implements three core traits bundled in a ProviderAdapter:

LogDiscovery  → File discovery and session location
SessionParser → Raw log parsing to AgentEvent timeline
ToolMapper    → Tool call normalization and classification

Key Files:

  • crates/agtrace-providers/src/traits.rs - trait definitions
  • crates/agtrace-providers/src/registry.rs - adapter factory

Tier 2: Provider-Specific Implementation

Each provider has this structure:

provider/
├── discovery.rs      # LogDiscovery trait impl
├── parser.rs         # SessionParser trait impl
├── mapper.rs         # ToolMapper trait impl
├── io.rs             # Raw file I/O
├── schema.rs         # Provider-specific schemas
├── tools.rs          # Provider-specific tool args
├── tool_mapping.rs   # Tool classification
└── models.rs         # Data structures

Provider directories:

  • crates/agtrace-providers/src/claude/
  • crates/agtrace-providers/src/codex/
  • crates/agtrace-providers/src/gemini/

Tier 3: Unified Domain Types

All providers normalize to common types in agtrace-types:

TypeLocationVariants
EventPayloadevent/payload.rsUser, Reasoning, ToolCall, ToolResult, Message, TokenUsage, Notification, SlashCommand
ToolCallPayloadtool/payload.rsFileRead, FileEdit, FileWrite, Execute, Search, Mcp, Generic
ToolKindtool/types.rsRead, Write, Execute, Plan, Search, Ask, Other
ToolOrigintool/types.rsSystem, Mcp

Schema-on-Read Architecture

The architecture enforces Schema-on-Read where:

  1. Raw logs are source of truth - Original files never modified
  2. Lazy parsing - Files parsed on demand
  3. Type-safe conversion - Raw JSON → Provider Args → Domain ToolCallPayload

Provider-Specific Args Pattern

Each provider defines schema-specific argument types:

// Claude: crates/agtrace-providers/src/claude/tools.rs
ClaudeReadArgs, ClaudeGlobArgs, ClaudeEditArgs, ClaudeWriteArgs,
ClaudeBashArgs, ClaudeGrepArgs, ClaudeTodoWriteArgs

// Codex: crates/agtrace-providers/src/codex/tools.rs
ShellArgs, ApplyPatchArgs, ReadMcpResourceArgs

// Gemini: crates/agtrace-providers/src/gemini/tools.rs
GeminiReadFileArgs, GeminiWriteFileArgs, GeminiReplaceArgs,
GeminiRunShellCommandArgs, GeminiGoogleWebSearchArgs

Each has conversion methods:

impl ClaudeReadArgs {
    fn to_file_read_args(&self) -> FileReadArgs { ... }
}

Tool Call Normalization Flow

Raw JSON Arguments
    ↓
Provider-specific Args deserialization (strict)
    ↓
Optional: Semantic reclassification (e.g., shell → Read/Write/Search)
    ↓
Convert to typed ToolCallPayload variant
    ↓
If parse fails → Generic variant (safe fallback)

Semantic Reclassification Example

Codex shell commands are intelligently classified:

// File: codex/mapper.rs
match tool_name {
    "shell" => {
        if let Ok(shell_args) = parse::<ShellArgs>(arguments) {
            match classify_execute_command(&command) {
                Some(ToolKind::Search) => return Search variant
                Some(ToolKind::Read) => return FileRead variant
                _ => return Execute variant
            }
        }
    }
}

MCP Tool Handling

Different providers use different MCP naming conventions:

ProviderFormatExample
Claudemcp__server__toolmcp__filesystem__read_file
Codexmcp__server__toolmcp__memory__store
Geminitool-name + display_nameDisplay: "(ServerName MCP Server)"

All normalize to ToolCallPayload::Mcp with McpArgs:

pub struct McpArgs {
    pub server: Option<String>,
    pub tool: Option<String>,
    pub inner: Value,
}

Event Building

The EventBuilder in builder.rs provides deterministic event construction:

pub struct EventBuilder {
    session_id: Uuid,
    stream_tips: HashMap<StreamId, Uuid>,  // Per-stream parent tracking
    tool_map: HashMap<String, Uuid>,        // Provider ID → UUID mapping
}

Key features:

  • Deterministic UUIDs: v5 UUID from session_id + "base_id:suffix"
  • Multi-stream support: Main, Sidechain, Subagent independent chains
  • Tool result linking: O(1) lookup from provider tool ID to UUID

Adding a New Provider

  1. Create provider module: crates/agtrace-providers/src/<provider_name>/

  2. Implement Discovery:

pub struct NewProviderDiscovery;

impl LogDiscovery for NewProviderDiscovery {
    fn id(&self) -> &'static str { "new_provider" }
    fn probe(&self, path: &Path) -> ProbeResult { ... }
    fn scan_sessions(&self, log_root: &Path) -> Result<Vec<SessionIndex>> { ... }
}
  1. Implement Parser:
pub struct NewProviderParser;

impl SessionParser for NewProviderParser {
    fn parse_file(&self, path: &Path) -> Result<Vec<AgentEvent>> { ... }
    fn parse_record(&self, content: &str) -> Result<Option<AgentEvent>> { ... }
}
  1. Implement ToolMapper:
pub struct NewProviderToolMapper;

impl ToolMapper for NewProviderToolMapper {
    fn classify(&self, tool_name: &str) -> (ToolOrigin, ToolKind) { ... }
    fn normalize_call(&self, name: &str, args: Value, call_id: Option<String>)
        -> ToolCallPayload { ... }
    fn summarize(&self, kind: ToolKind, args: &Value) -> String { ... }
}
  1. Register in registry.rs:
pub fn create_adapter(name: &str) -> Result<ProviderAdapter> {
    match name {
        "new_provider" => Ok(ProviderAdapter::new(
            Box::new(crate::new_provider::NewProviderDiscovery),
            Box::new(crate::new_provider::NewProviderParser),
            Box::new(crate::new_provider::NewProviderToolMapper),
        )),
        // ...
    }
}

Tool Classification Pattern

Provider-Specific Classification

Each provider defines in tool_mapping.rs:

fn classify_tool(tool_name: &str) -> Option<(ToolOrigin, ToolKind)>

Returns Some if recognized, None for fallback.

Common Heuristic Fallback

When provider doesn't recognize a tool (tool_analyzer.rs):

pub fn classify_common(tool_name: &str) -> (ToolOrigin, ToolKind) {
    // name.contains("search") → ToolKind::Search
    // name.contains("read") → ToolKind::Read
    // name.starts_with("mcp__") → ToolOrigin::Mcp
}

Key Architectural Decisions

AspectDecisionRationale
Schema LocationProvider Args in providers, ToolCallPayload in typesDomain model pure
Parsing ErrorsFall back to Generic, never failNo data loss
Tool ClassificationProvider-specific first, then heuristicsAccuracy + graceful fallback
Event IDsDeterministic v5 UUIDsReproducible sessions
Multi-StreamSeparate parent chains per StreamIdIndependent flows

Testing Patterns

Each provider has tests for:

  • Discovery: probe, scan, session extraction
  • Parser: record parsing, content blocks
  • Mapper: tool normalization per tool type
  • Edge cases: malformed data, unknown tools
#[test]
fn test_normalize_read() {
    let payload = normalize_claude_tool_call(
        "Read".to_string(),
        serde_json::json!({"file_path": "src/main.rs"}),
        Some("call_123".to_string()),
    );

    match payload {
        ToolCallPayload::FileRead { arguments, .. } => {
            assert_eq!(arguments.file_path, Some("src/main.rs".to_string()));
        }
        _ => panic!("Expected FileRead"),
    }
}

When to Use This Skill

This skill should be activated when:

  • Adding a new AI agent provider to agtrace
  • Modifying tool normalization logic
  • Understanding how provider logs become unified events
  • Debugging provider-specific parsing issues
  • Designing new tool classification strategies
  • Working with MCP tool handling
  • Understanding the schema-on-read architecture

Key Files Reference

PurposeFile Path
Trait definitionscrates/agtrace-providers/src/traits.rs
Provider registrycrates/agtrace-providers/src/registry.rs
Event buildercrates/agtrace-providers/src/builder.rs
Claude parsercrates/agtrace-providers/src/claude/parser.rs
Claude toolscrates/agtrace-providers/src/claude/tools.rs
Codex parsercrates/agtrace-providers/src/codex/parser.rs
Gemini parsercrates/agtrace-providers/src/gemini/parser.rs
Domain eventscrates/agtrace-types/src/event/payload.rs
Tool payloadcrates/agtrace-types/src/tool/payload.rs
Tool typescrates/agtrace-types/src/tool/types.rs

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon