Skip to main content

Module projector

Module projector 

Source
Expand description

Translation of AgentEvent into Langfuse ingestion events.

TraceProjector is a stateful, per-session projector (isomorphic to defect-storage’s RecordProjector). The main loop calls TraceProjector::project for each incoming AgentEvent and returns 0..N IngestionEvents to the reporter.

§Hierarchy (one trace per turn)

trace (turn)
└── step (span)                 One per turn: one llm_call + the tools it triggers
    ├── llm_call (generation)
    └── tool (span)             Sibling of llm_call under the same step
        └── (spawn_agent) → subagent (span)
            └── step (span)     Recursive isomorphic structure inside subagent
                ├── llm_call
                └── tool / spawn_agent → subagent → ...

Key point: the step span is the container for each turn. Both llm_call (generation) and the tools triggered in that turn hang under it as siblings. This makes the generation duration reflect pure LLM call time (it ends at LlmCallFinished, no longer delayed to the next turn or wrapping tool execution time); tool duration lives inside the step.

§Recursive subagent (flattened ancestor_path)

AgentEvent::Subagent carries an ancestor_path (a chain of ToolCallIds from the top-level spawn_agent tool call to the current layer), and inner is always a leaf event. The projector deterministically derives all span ids from this chain, so no per-layer anchoring is needed; arbitrary depth is handled uniformly. Each subagent layer is an independent scope (sharing the same step/gen/tool projection logic as the top-level turn).

§ID strategy

  • traceId: Generated once with Uuid::new_v4() at TurnStarted, reused within the turn. Must not use an auto-incrementing {session}-turn-{seq} — resuming would cause id collisions.
  • scope prefix: Top-level = {trace}; subagent path [A,B] = {trace}-sub-A-sub-B. The subagent span’s id is its scope prefix.
  • step / generation / tool span id: Derived from the scope prefix + sequence number / ToolCallId, globally unique and deterministic — the parent of a subagent span (the tool span that spawned it) is computed directly from the path.
  • anchor: The only state that needs to be stored is the top-level spawn_agent tool call id → trace_id (trace_id is random and not derivable). All subagents in the same turn share that trace_id, so only the top-level anchor is needed.
  • envelope id: One Uuid::new_v4() per ingestion event, for Langfuse deduplication.

§Timestamps

AgentEvent carries no timestamps; the caller passes now (an RFC3339 string). The projector does not read the clock itself, making it easier to test and deterministic.

Structs§

TraceProjector
Per-session projection state.