Antigravity Trajectory Sidecar Parsing Mechanism: Extracting Full Transcripts in AgentsView

The AgentsView parser extracts high-fidelity transcripts from .trajectory.json sidecars using a defensive, size-capped pipeline that converts daemon-written steps into structured messages while guarding against resource exhaustion.

The kenn-io/agentsview repository implements a robust parsing pipeline for Antigravity CLI sessions. When the agy-reader daemon writes session data, it accompanies each .db or .pb file with a .trajectory.json sidecar containing detailed execution metadata. This article explains the antigravity trajectory sidecar parsing mechanism that transforms these JSON files into full transcripts suitable for analytics and UI rendering.

File Discovery and Resource Protection

Before parsing begins, the system locates the sidecar file and enforces strict resource limits to prevent denial-of-service attacks.

Path Resolution and Size Guarding

When processing a .db or .pb session file, the parser constructs the sidecar path by replacing the extension with .trajectory.json. In internal/parser/antigravity_cli.go, the code defines a hard-coded maximum size constant: maxTrajectorySidecarBytes = 64 MiB. This limit prevents the parser from attempting to load maliciously large JSON files that could exhaust memory.

Stream Reading with Cap Enforcement

The file is opened and read through io.LimitReader (lines 24-33 of internal/parser/antigravity_cli.go). If the sidecar content exceeds the 64 MiB cap, the parser returns an error immediately and aborts processing. This defensive read ensures that oversized files never reach the JSON unmarshaler.

JSON Unmarshaling and Schema Validation

Once the size check passes, the parser deserializes the sidecar content into a strongly-typed Go structure.

Struct Mapping

The entire JSON payload is unmarshaled into the internal agyTrajectory struct, which mirrors the schema generated by the daemon. This struct includes fields such as Steps and Metadata that map directly to the daemon's output format (lines 34-38 of internal/parser/antigravity_cli.go).

Error Handling

Any JSON parsing error aborts sidecar handling entirely. The parser treats the sidecar as untrusted structured input and falls back to lower-fidelity sources (such as the native DB decode or shell history) if the JSON is malformed. The parser never executes or echoes raw JSON content; it only deserializes into Go structs and discards unknown step types.

Step-by-Step Transcript Conversion

The core parsing logic iterates over traj.Steps to reconstruct the conversation flow. Each step is dispatched based on its step.Type to generate ParsedMessage objects that preserve roles, content, timestamps, and tool-call metadata.

Planner Response Handling

For steps of type CORTEX_STEP_TYPE_PLANNER_RESPONSE, the parser extracts tool calls, normalizes categories, and populates ParsedToolCall structs. If the planner's textual response is empty, the parser concatenates tool headers to form the assistant message content. This logic ensures that tool invocations remain visible in the transcript even when the assistant provides no additional commentary (lines 83-99 of internal/parser/antigravity_cli.go).

Tool Execution and Synthetic Message Generation

Tool execution steps—including command-run, file-view, code-action, grep, directory-list, and error steps—capture raw output or error text. The parser marshals this data to JSON and stores it as pending ParsedToolResult objects.

When a new user or assistant turn begins, the parser emits a synthetic empty-content user message containing the pending tool results. This synthetic message is later filtered out by the sync engine but is essential for proper UI pairing, enabling the interface to render tool output inline with the conversation flow. Any remaining pending results are flushed into a final synthetic message at the end of parsing (lines 124-144 of internal/parser/antigravity_cli.go).

Coverage Validation and Fallback Strategy

The parser implements intelligent selection logic in the higher-level parseSessionWithStatus routine (around line 140 of internal/parser/antigravity_cli.go). This function compares the sidecar transcript against the database decode to determine which source provides the most complete record.

Selection Criteria

The sidecar transcript is preferred only when two conditions are met:

  • The sidecar contains displayable messages
  • The sidecar covers at least as many raw steps as the DB decode

Retry Logic

If the sidecar parses successfully but does not fully cover the DB steps, the parser uses the DB transcript but sets the NeedsRetry status flag. This signals the sync engine to re-parse the session on a subsequent pass once the sidecar has caught up with the full execution trace.

Implementation Example

The following Go code demonstrates how to invoke the trajectory parser and handle the results:

// Example: parsing a trajectory sidecar for a given session file.
func exampleParseSidecar(sidecarPath string) (*ParsedSession, []ParsedMessage, []ParsedUsageEvent, error) {
    // The top-level parse function returns a struct with messages, raw step count,
    // and usage events extracted from the side-car.
    result, err := parseAntigravityCLITrajectory(sidecarPath)
    if err != nil {
        return nil, nil, nil, fmt.Errorf("failed to parse sidecar: %w", err)
    }

    // The caller can now store the messages and usage events in the DB.
    // result.messages holds the fully-fledged transcript.
    // result.usageEvents holds any token-usage information present in the side-car.
    return nil, result.messages, result.usageEvents, nil
}

// In the sync engine (simplified):
func (p *antigravityCLIProvider) parseSessionWithStatus(path, project, machine string) (*ParsedSession, []ParsedMessage, []ParsedUsageEvent, AntigravityCLIParseStatus, error) {
    sidecarPath := strings.TrimSuffix(path, ".db") + ".trajectory.json"
    sidecarRes, sidecarErr := parseAntigravityCLITrajectory(sidecarPath)

    // Prefer side-car when it provides displayable content and covers the DB.
    if sidecarErr == nil && hasDisplayableAntigravityCLITrajectoryMessage(sidecarRes.messages) && sidecarRes.rawSteps >= dbResult.rawStepCount {
        return nil, sidecarRes.messages, sidecarRes.usageEvents, AntigravityCLIParseStatus{}, nil
    }
    // …fallback handling omitted for brevity…
}

Key Source Files

The parsing pipeline spans four primary files in the codebase:

Summary

  • Size Protection: The parser enforces a 64 MiB limit on .trajectory.json files using io.LimitReader to prevent resource exhaustion.
  • Defensive Parsing: JSON is unmarshaled into strictly typed structs; malformed sidecars trigger immediate fallback to DB sources.
  • Rich Transcript Generation: The parser converts daemon steps into structured messages, handling planner responses and tool executions with full metadata preservation.
  • Synthetic Messages: Tool results are bundled into synthetic empty-content user messages to maintain UI pairing, then filtered by the sync engine.
  • Intelligent Fallback: The parseSessionWithStatus routine selects the sidecar only when it offers complete coverage, setting NeedsRetry for partial data.

Frequently Asked Questions

What is the maximum file size for trajectory sidecars?

The parser enforces a hard limit of 64 MiB via the maxTrajectorySidecarBytes constant in internal/parser/antigravity_cli.go. Files exceeding this size are rejected before JSON parsing begins to protect against resource-exhaustion attacks.

How does the parser handle tool execution steps?

Tool execution steps (such as command-run, file-view, and code-action) are converted to ParsedToolResult objects and accumulated in a temporary slice. When a new conversation turn begins, the parser emits a synthetic user message containing these results, ensuring tool outputs remain paired with their invocations in the UI timeline.

What happens when the sidecar does not cover all database steps?

If the sidecar parses successfully but contains fewer raw steps than the database decode, the parser uses the DB transcript instead and sets the NeedsRetry flag in the status return. This allows the sync engine to automatically re-parse the session later when the sidecar has been fully written by the daemon.

Why does the parser create empty-content synthetic messages?

Synthetic empty-content user messages serve as containers for ParsedToolResult payloads that need to be delivered between assistant tool calls and the next user input. These messages are essential for proper UI rendering of tool output inline with the conversation flow, even though they are filtered from the final display by the sync engine.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →