Skip to content

[refactor] Single source of truth for trace span types#5465

Open
ardaerzin wants to merge 1 commit into
ts-chore/fix-tsc-issuesfrom
ts-chore/unify-span-types
Open

[refactor] Single source of truth for trace span types#5465
ardaerzin wants to merge 1 commit into
ts-chore/fix-tsc-issuesfrom
ts-chore/unify-span-types

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

A trace span was described by two type definitions: hand-written interfaces and TypeScript enums in oss/src/services/tracing/types, and the zod schemas in @agenta/entities/trace that actually validate the backend payload. They were structurally equivalent but nominally incompatible, so every place data crossed between them needed a cast. PR #5464 added seven such casts with // same backend span shape comments. They were safe, but they meant a real backend shape change could pass through untyped.

They had also already drifted. AGE-3788 canonicalised the backend catch-all category from "undefined" to "unknown", and the entities schema followed. The OSS enum did not.

Changes

@agenta/entities/trace is now the single source. The OSS module keeps only what the package cannot own: TraceSpanNode (the entities node plus the annotations the drawer stores attach at runtime, whose AnnotationDto lives in the app layer) and GenerationDashboardData. Consumers import span types from the package directly rather than through a forwarding module, which is what the app-layer no-re-export lint rule asks for.

The 25 enum value usages became string literals. StatusCode's values were already byte-identical between the two definitions, and SpanCategory differed only in that catch-all. All seven boundary casts are gone.

The drift was a live crash

spanTypeStyles was keyed "undefined" and both consumers destructure the lookup:

const {icon: Icon} = spanTypeStyles[type ?? "undefined"]

A span with span_type: "unknown" misses the record, and destructuring undefined throws. That path reaches the observability table (NodeNameCell) and the trace tree (AvatarTreeContent). The record is now keyed by SpanCategory, so it is exhaustive and a new backend category fails the build, and both call sites fall back instead of throwing.

Nullability

The entities schemas declare | null where the backend sends null. Rather than cast that away, the consumers that were under-declaring it were widened: NodeNameCell, StatusRenderer, TimestampCell and its atom, and ScannedExportRow in the ETL pipeline (which only reads those fields).

Tests / notes

  • pnpm --filter @agenta/oss exec tsc --noEmit and pnpm --filter @agenta/ee exec tsc --noEmit: 0 errors each.
  • pnpm turbo run build --filter=@agenta/entities and pnpm turbo run lint --filter=@agenta/oss: clean.
  • Net 224 deletions against 83 insertions; the OSS types module drops from 156 lines to 35.
  • Stacked on [chore] Zero out web tsc errors and fail builds on type regressions #5464, so review that one first. Base is ts-chore/fix-tsc-issues.

What to QA

  • Observability table and trace tree render for a project with traces. Span icons and status colors look unchanged.
  • Open a trace in the trace drawer, then the session drawer. Tree, overview tab, and annotations tab all populate.
  • Add a span to a testset from the drawer. The add-to-testset flow still reads span data.
  • If you can produce a span with no ag.type set (so the backend reports the unknown category), the observability table renders it with the generic icon instead of crashing. That is the bug this fixes.

The OSS tracing module declared its own TraceSpan/TraceSpanNode plus four
TypeScript enums that mirrored the zod schemas in @agenta/entities. The two
were structurally equivalent but nominally incompatible, so every crossing
needed a cast (7 of them, added in #5464 as documented scar tissue).

- oss/src/services/tracing/types now owns only TraceSpanNode (entities node
  plus the annotations the drawer stores attach) and GenerationDashboardData;
  consumers import span types from @agenta/entities/trace directly, per the
  app-layer no-re-export rule
- 25 enum value usages become string literals; StatusCode values were already
  identical, SpanCategory differed only in the catch-all
- all 7 boundary casts removed

Fixes a live crash: AGE-3788 canonicalised the backend catch-all from
"undefined" to "unknown", but spanTypeStyles was still keyed "undefined" and
both consumers destructure the lookup, so any span typed "unknown" threw a
TypeError in the observability table and trace tree. The record is now keyed
by SpanCategory (exhaustive, so a new backend category fails the build) with a
fallback at both call sites.

Nullability now matches the payload: entities declares | null where the
backend sends null, so NodeNameCell/StatusRenderer/TimestampCell props and
ScannedExportRow were widened rather than cast.
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 23, 2026 3:02pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved observability handling for unknown span categories and missing status, message, and timestamp values.
    • Preserved trace navigation, filtering, tree rendering, and Playground actions while improving compatibility with varied trace data.
  • Refactor
    • Unified trace data handling across observability views, drawers, exports, and evaluation details.
    • Simplified trace transformations and strengthened type safety for trace-related data.

Walkthrough

Tracing types are sourced from shared entities, OSS extensions are retained for annotations, and redundant casts are removed across trace conversion and drawer flows. Observability components now handle literal status/category values and nullable fields. ETL scanned-row types accept optional or null values.

Changes

Tracing type alignment

Layer / File(s) Summary
Shared tracing contracts
web/oss/src/services/tracing/types/index.ts, web/oss/src/components/..., web/oss/src/components/pages/observability/...
OSS tracing types extend entity definitions, while consumers use type-only imports from the shared entity and local modules.
Trace conversion flow
web/oss/src/components/EvalRunDetails/..., web/oss/src/components/SharedDrawers/..., web/oss/src/state/newObservability/...
Trace response transformations, drill-in handling, and playground calls no longer use explicit boundary casts.
Observability UI mappings
web/oss/src/components/SharedDrawers/TraceDrawer/..., web/oss/src/components/pages/observability/...
Span categories and status codes use literal values, unknown-style fallbacks use unknown, and relevant props accept null values.
ETL row contract
web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts
Scanned export row identifiers, timestamps, and children are optional and nullable.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the refactor to use a single trace span type source.
Description check ✅ Passed The description matches the refactor details, including type consolidation, nullability updates, and the unknown-category fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ts-chore/unify-span-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin
ardaerzin requested a review from ashrafchowdury July 23, 2026 19:34
@ardaerzin
ardaerzin marked this pull request as ready for review July 23, 2026 19:34
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. refactoring A code change that neither fixes a bug nor adds a feature labels Jul 23, 2026
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/oss/src/services/tracing/types/index.ts (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shorten the source-of-truth comment.

This three-line explanation exceeds the repository rule for in-code comments; keep it to one short line or move the rationale to documentation.

As per coding guidelines, in-code comments must be at most one short line unless they document a genuinely surprising constraint.

Suggested wording
-// Span types have a single source of truth: the zod schemas in `@agenta/entities`,
-// which validate the backend payload at the boundary. Import them from
-// `@agenta/entities/trace` directly; this module owns only the OSS-only additions.
+// Span types come from `@agenta/entities`; this module adds OSS annotations.

Source: Coding guidelines

web/oss/src/components/pages/observability/assets/constants.ts (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial

Verify the required frontend lint-fix command.

Before committing this frontend cohort, run pnpm lint-fix from web; the supplied context does not confirm that exact command. As per coding guidelines, frontend changes must run pnpm lint-fix from web.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d6f21bdb-d182-4f40-b36c-46c0492e89b2

📥 Commits

Reviewing files that changed from the base of the PR and between cb415f6 and 9301719.

📒 Files selected for processing (19)
  • web/oss/src/components/EvalRunDetails/atoms/traces.ts
  • web/oss/src/components/SharedDrawers/SessionDrawer/store/sessionDrawerStore.ts
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/OverviewTabItem/index.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/TraceTypeHeader/index.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceHeader/index.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceTree/assets/spanVisibility.ts
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceTree/index.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/store/openInPlayground.ts
  • web/oss/src/components/SharedDrawers/TraceDrawer/store/traceDrawerStore.ts
  • web/oss/src/components/pages/observability/assets/constants.ts
  • web/oss/src/components/pages/observability/assets/exportUtils.ts
  • web/oss/src/components/pages/observability/components/AvatarTreeContent.tsx
  • web/oss/src/components/pages/observability/components/NodeNameCell.tsx
  • web/oss/src/components/pages/observability/components/StatusRenderer.tsx
  • web/oss/src/components/pages/observability/components/TimestampCell.tsx
  • web/oss/src/services/tracing/types/index.ts
  • web/oss/src/state/newObservability/atoms/queries.ts
  • web/oss/src/state/newObservability/atoms/queryHelpers.ts
  • web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts

Comment on lines +36 to +40
trace_id?: string | null
span_id?: string | null
parent_id?: string | null
start_time?: string | number | null
children?: readonly ScannedExportRow[] | null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts --items all --type function --match 'defaultSelectKey|exportMatchingTraces'
rg -n -C4 '\bdefaultSelectKey\b|new Map|new Set|selectKey' \
  web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts \
  web/packages/agenta-entities/tests/unit/export-matching-traces.test.ts

Repository: Agenta-AI/agenta

Length of output: 8255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file size =="
wc -l web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts
echo

echo "== relevant source =="
sed -n '1,120p' web/packages/agenta-entities/src/trace/etl/exportMatchingTraces.ts
echo

echo "== test relevant sections =="
sed -n '300,380p' web/packages/agenta-entities/tests/unit/export-matching-traces.test.ts
echo

echo "== type definitions and call sites =="
rg -n -C3 'type ScannedExportRow|interface ScannedExportRow|exportMatchingTraces\(' web/packages/agenta-entities/src web/packages/agenta-entities/tests/unit
echo

echo "== deterministic fallback key probe =="
node - <<'JS'
const rows = [
  {},
  {trace_id: null},
  {trace_id: null, parent_id: null},
  {trace_id: null, parent_id: null, start_time: null},
  {parent_id: "p", start_time: 1},
];
for (const trace of rows) {
  const fallback = `${trace.trace_id ?? ""}:${trace.parent_id ?? ""}:${trace.start_time ?? ""}`;
  console.log(JSON.stringify(trace), '=>', fallback);
}
JS

Repository: Agenta-AI/agenta

Length of output: 9329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ScannedExportRow definitions across repo =="
rg -n -C2 'export interface ScannedExportRow|type ScannedExportRow|interface ScannedExportRow|ScannedExportRow' .

echo
echo "== data shaping and mapping around scan/export matching paths =="
rg -n -C3 'scannedExport|scanned.*row|trace_id|span_id|parent_id|start_time' web/packages web/src -g '*.ts' -g '*.tsx' | head -n 300

Repository: Agenta-AI/agenta

Length of output: 36601


Avoid collapsing export rows when identifiers are missing.

The pipeline dedups with defaultSelectKey, but multiple rows with no selected trace_id, parent_id, or start_time all produce the same fallback key (::), so only the first such row is emitted. Use a stable per-row fallback key / index when the primary composite identity is incomplete.

@ashrafchowdury

Copy link
Copy Markdown
Contributor

Web build is failing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring A code change that neither fixes a bug nor adds a feature size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants