Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **OpenTelemetry-aligned rename of the storage entities and HTTP API.** The
`agent_turns` table is now `traces`, `llm_calls` is now `spans`, and
`traces.call_ids` is now `span_ids`; a new forward-looking `spans.kind`
column (always `'llm'` today) leaves room for wire-visible tool spans. The
Rust domain/query types (`AgentTurn`→`Trace`, `Turn*`/`Call*` DTOs →
`Trace*`/`Span*`) and `StorageBackend` methods (`write_calls`→`write_spans`,
`query_turns`→`query_traces`, …) follow suit. New canonical routes
`/api/traces*` and `/api/spans*`; the pre-rename `/api/agent-turns*` and
`/api/llm-calls*` keep working as deprecated aliases (RFC 8594 `Deprecation`
header). Retention config keys `calls`/`turns` are accepted as serde aliases
for `spans`/`traces`. Existing DuckDB/ClickHouse databases auto-migrate in
place on init() with no data loss (idempotent detect-then-rename).

## [0.6.0] — 2026-06-16

### Changed
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ See [docs/design/04-turn.md](docs/design/04-turn.md) for Turn (agent interaction

## Storage

Three entities: `agent_turns` (agent turn), `llm_calls` (per-call detail + full body), `llm_metrics` (pre-aggregated time-series). Relation: `agent_turns 1─N llm_calls`. Pluggable backends:
Three entities: `traces` (agent turn), `spans` (per-call detail + full body), `llm_metrics` (pre-aggregated time-series). Relation: `traces 1─N spans`. Pluggable backends:

**Query rule — no JOIN.** Read-path SQL MUST NOT use any `JOIN`. Cross-entity reads are split into multiple point lookups: e.g. fetch `call_ids` from `agent_turns` by PK, then `SELECT ... FROM llm_calls WHERE id IN (?, ?, ...)`. See `query_turn_calls` in `h-storage-duckdb/src/turns.rs` for the canonical pattern. This keeps queries uniformly cheap across DuckDB/PG/ClickHouse and avoids planner surprises at scale.
**Query rule — no JOIN.** Read-path SQL MUST NOT use any `JOIN`. Cross-entity reads are split into multiple point lookups: e.g. fetch `span_ids` from `traces` by PK, then `SELECT ... FROM spans WHERE id IN (?, ?, ...)`. See `query_trace_spans` in `h-storage-duckdb/src/turns.rs` for the canonical pattern. This keeps queries uniformly cheap across DuckDB/PG/ClickHouse and avoids planner surprises at scale.

| Backend | Use case |
|---------|----------|
Expand Down
4 changes: 2 additions & 2 deletions console/src/hooks/use-agent-overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function useAgentSummary() {
return useQuery({
queryKey: ["agent-summary", { start, end }],
queryFn: () =>
apiFetch<AgentSummaryData>("/api/agent-turns/summary", { start, end }),
apiFetch<AgentSummaryData>("/api/traces/summary", { start, end }),
placeholderData: (prev) => prev,
})
}
Expand All @@ -20,7 +20,7 @@ export function useAgentActivity() {
return useQuery({
queryKey: ["agent-activity", { start, end }],
queryFn: () =>
apiFetch<AgentActivityData>("/api/agent-turns/activity", { start, end }),
apiFetch<AgentActivityData>("/api/traces/activity", { start, end }),
placeholderData: (prev) => prev,
})
}
8 changes: 4 additions & 4 deletions console/src/hooks/use-agent-turns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function useAgentTurns({ page, pageSize, sortBy, sortOrder, status, agent
status, agentKind, clientIp, serverPort, includeProxyHops,
}],
queryFn: () =>
apiFetch<AgentTurnsPage>("/api/agent-turns", {
apiFetch<AgentTurnsPage>("/api/traces", {
start,
end,
page,
Expand All @@ -55,7 +55,7 @@ export function useAgentTurns({ page, pageSize, sortBy, sortOrder, status, agent
export function useAgentTurnDetail(id: string | null) {
return useQuery({
queryKey: ["agent-turn-detail", id],
queryFn: () => apiFetch<AgentTurnDetail>(`/api/agent-turns/${id}`),
queryFn: () => apiFetch<AgentTurnDetail>(`/api/traces/${id}`),
enabled: id != null,
})
}
Expand All @@ -75,7 +75,7 @@ export function useAgentTurnCalls(id: string | null, lite = false) {
return useQuery({
queryKey: ["agent-turn-calls", id, lite],
queryFn: () =>
apiFetch<AgentTurnCallItem[]>(`/api/agent-turns/${id}/calls`, lite ? { lite: 1 } : {}),
apiFetch<AgentTurnCallItem[]>(`/api/traces/${id}/spans`, lite ? { lite: 1 } : {}),
enabled: id != null,
})
}
Expand All @@ -87,7 +87,7 @@ export function useAgentTurnCalls(id: string | null, lite = false) {
export function useAgentTurnProxyView(id: string | null, enabled = true) {
return useQuery({
queryKey: ["agent-turn-proxy-view", id],
queryFn: () => apiFetch<ProxyViewResponse>(`/api/agent-turns/${id}/proxy-view`),
queryFn: () => apiFetch<ProxyViewResponse>(`/api/traces/${id}/proxy-view`),
enabled: id != null && enabled,
})
}
2 changes: 1 addition & 1 deletion console/src/hooks/use-llm-call-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { LlmCallDetail } from "@/types/api"
export function useLlmCallDetail(id: string | null) {
return useQuery({
queryKey: ["llm-call-detail", id],
queryFn: () => apiFetch<LlmCallDetail>(`/api/llm-calls/${id}`),
queryFn: () => apiFetch<LlmCallDetail>(`/api/spans/${id}`),
enabled: id != null,
})
}
2 changes: 1 addition & 1 deletion console/src/hooks/use-llm-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function useLlmCalls({
statusCode, finishReason, clientIp, serverPort, requestPath, isStream,
}],
queryFn: () =>
apiFetch<LlmCallsPage>("/api/llm-calls", {
apiFetch<LlmCallsPage>("/api/spans", {
start,
end,
page,
Expand Down
18 changes: 10 additions & 8 deletions docs/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ optimize_on_sweep = false # OPTIMIZE TABLE ... FINAL after
```

The backend talks to ClickHouse over its HTTP interface and creates the schema
(MergeTree fact tables + a ReplacingMergeTree `agent_turns`) on first run.
(MergeTree fact tables + a ReplacingMergeTree `traces`) on first run.
Retention runs through the same `[storage.retention]` schedule as DuckDB, using
lightweight `DELETE`. `optimize_on_sweep` eagerly reclaims space after each
sweep at the cost of a full-table merge; off by default (TTL-style background
Expand Down Expand Up @@ -249,8 +249,8 @@ defaults** — you only need to add it to override:
[storage.retention]
enabled = true # set to false to opt out entirely
check_interval_secs = 3600 # how often to run the cleanup sweep
calls = 30 # keep llm_calls for N days; caps `turns`
turns = 30 # keep agent_turns for N days; must be <= calls
spans = 30 # keep spans (per-call detail) for N days; caps `traces`
traces = 30 # keep traces (agent turns) for N days; must be <= spans
http_exchanges = 7 # keep http_exchanges (bulkiest table) for N days

# Per-granularity retention for llm_metrics. Missing keys fall back to
Expand All @@ -274,11 +274,13 @@ Behavior:
- `http_exchanges` stores raw HTTP transport records (headers + bodies)
per request/response and is by far the bulkiest table; keep its TTL
short unless you specifically need a longer forensics window.
- `turns` must not outlive `calls`. `agent_turns.call_ids` references
rows in `llm_calls`, and the no-JOIN turn-detail read returns
empty/partial call lists once those references go stale. `heron
config validate` enforces `turns <= calls` (with `calls = 0` treated
as infinite, which satisfies any finite `turns`).
- `traces` must not outlive `spans`. `traces.span_ids` references
rows in `spans`, and the no-JOIN trace-detail read returns
empty/partial span lists once those references go stale. `heron
config validate` enforces `traces <= spans` (with `spans = 0` treated
as infinite, which satisfies any finite `traces`).
- The pre-rename keys `calls` / `turns` are still accepted as deprecated
aliases for `spans` / `traces`, so existing config files keep working.

## `[api]` — REST server

Expand Down
Loading
Loading