Linear-speed agent trace & trajectory platform
LangSmith and Langfuse treat traces as server-fetched CRUD data. Every span expansion, every filter change, every trajectory replay hits the network. TraceGraph inverts this: sync the full trace tree to the browser once, render from a local object pool, and let the server be a sync target — not the UI's source of truth.
Agent traces are structurally different from traditional APM spans:
- Deep nesting — a single run can produce 50–500 steps (tool calls, LLM invocations, sub-agent delegations, retries, human checkpoints)
- Heavy payloads — each step carries full prompt/completion text, token counts, tool schemas, intermediate reasoning
- Temporal scrubbing — evaluators replay trajectories step-by-step; this is a random-access read-heavy pattern, the worst case for fetch-on-demand
- Decision trace layer — the real value is capturing why the agent chose a path; current platforms don't model this as a first-class primitive
| Interaction | TraceGraph Target | LangSmith Baseline |
|---|---|---|
| Open app (warm cache) | < 500ms | ~2–4s |
| Open a trace | < 50ms | ~300–800ms (fetch) |
| Scrub between steps | < 16ms (one frame) | ~200–500ms (fetch/step) |
| Filter trace list | < 50ms | ~500ms–2s (server query) |
| Add annotation | < 16ms (local) | ~300ms (round-trip) |
| Live step (streaming) | < 50ms to render | ~1–3s (polling) |
graph TB
subgraph BROWSER["Browser"]
UI["React + observer tree"]
MOBX["MobX Observable Pool"]
IDB["IndexedDB (trace store)"]
SYNC_C["Sync Engine (client)"]
UI <-->|computed/reaction| MOBX
MOBX <-->|hydrate/persist| IDB
MOBX <-->|apply delta| SYNC_C
end
subgraph SERVER["Server"]
SYNC_S["Sync Server"]
INGEST["Ingestion API (OTLP)"]
PG["PostgreSQL (traces)"]
REDIS["Redis (event bus)"]
VEC["pgvector (similar traces)"]
INGEST --> PG
INGEST --> REDIS
REDIS --> SYNC_S
PG --> VEC
end
SYNC_C <-->|"WebSocket (deltas)"| SYNC_S
subgraph AGENT["Agent Harness"]
OTEL["OpenTelemetry SDK"]
SDK["TraceGraph SDK (optional)"]
end
OTEL -->|OTLP| INGEST
SDK -->|"decision_trace, overrides"| INGEST
erDiagram
TraceRun {
string id PK
string agent_id
string session_id
datetime started_at
datetime ended_at
enum status
json metadata
}
Step {
string id PK
string trace_id FK
string parent_step_id FK
enum step_type
datetime started_at
datetime ended_at
json input
json output
json decision_trace
}
Annotation {
string id PK
string step_id FK
string content
string author_id
datetime created_at
}
HumanOverride {
string id PK
string step_id FK
enum override_type
json original_value
json new_value
string reasoning
}
Eval {
string id PK
string trace_id FK
string step_id FK
string grader_type
float score
string grader_version
}
TraceRun ||--o{ Step : "has"
Step ||--o{ Step : "parent_step_id"
Step ||--o{ Annotation : "has"
Step ||--o{ HumanOverride : "has"
TraceRun ||--o{ Eval : "has"
Step ||--o{ Eval : "has"
tracegraph/
├── packages/
│ ├── data-model/ # MobX observable classes (TraceRun, Step, Annotation…)
│ ├── sync-engine/ # Delta protocol, TracePool, out-of-order buffering
│ ├── ingestion-api/ # OTLP-compatible Express API + Zod schemas
│ └── frontend/ # React + Vite app shell, TraceListStore, TraceDetailStore
├── docs/
│ ├── architecture.md # Architecture Design Doc
│ └── system-design.md # System Design Doc
└── assets/ # Rendered diagrams (SVG)
# Requires Node 25+ and pnpm
corepack enable
pnpm install
# Type-check all packages
pnpm -r run typecheck
# Lint all packages
pnpm -r run lint
# Run all tests
pnpm -r run test46/46 tests pass. TypeScript strict mode. ESLint clean.
from tracegraph import trace
@trace.run(agent="insurance-claims")
async def process_claim(claim_id: str):
with trace.step("retrieve_policy", step_type="tool_use") as s:
policy = await search_policies(claim_id)
s.record_output(policy)
s.record_decision_trace(
alternatives=[("search_claims", 0.3), ("search_policies", 0.7)],
reasoning="User mentioned 'coverage', routing to policy search"
)- Sync engine core: IndexedDB store, MobX observable pool, WebSocket delta protocol
- Data model: TraceRun, Step, Annotation in Postgres + IndexedDB schema
- Ingestion API: OTLP-compatible endpoint → Postgres + delta broadcast
- App shell: Vite build, inline CSS,
modulepreload, service worker scaffold - Trace list view: hydrate from local store,
j/knavigation, live status updates - Trace detail: timeline scrubber, step expansion, full payload rendering
- Context window replay view
- Annotation system: local-first ProseMirror annotations on steps
- Command palette (
⌘K): search traces, steps, commands from local store - Keyboard shortcut system (
j/k,←/→,a,e,t) - Step-level and run-level eval score ingestion
- Eval dashboard: aggregate charts, entropy scores, drill-down
- Human override capture and diff view
- Decision trace rendering (alternatives, confidence, reasoning)
- Similar trace detection via pgvector
- Lazy hydration tuning (10K, 50K, 100K traces)
- Offline mode: full read + annotate without network
- Python SDK (
@trace.run/trace.stepdecorators) - Animation audit: composited only, < 150ms, asymmetric
MIT