Documentation
¶
Overview ¶
Package api provides an HTTP API server over the derived sessions/traces/spans read model.
Index ¶
- Constants
- func OpenAPISpec() []byte
- type Config
- type MainUsage
- type Metrics
- type ModelUsage
- type PayloadMode
- type RawTurnHeaderItem
- type RawTurnListResponse
- type Server
- type SessionDetailResponse
- type SessionItem
- type SessionListResponse
- type SessionRollup
- type SessionTracesResponse
- type SessionUsage
- type SpanItem
- type SpanLinkItem
- type SpanSearchOutput
- type SpanSearchResult
- type SpanSearcher
- type StatsResponse
- type TraceDetail
- type TraceItem
- type TraceListResponse
- type TraceUsage
- type TreeTask
Constants ¶
const ProjectionSchema = "2026-06-15"
ProjectionSchema is the compatibility date of the derived projection generation currently served (the dated *_20260615 table family). It is stamped onto the wire `schema` field; a future generation bumps this in lockstep with a new dated table family (derived_projection_schemas).
Variables ¶
This section is empty.
Functions ¶
func OpenAPISpec ¶ added in v0.25.0
func OpenAPISpec() []byte
OpenAPISpec returns the embedded OpenAPI 3.0.3 contract bytes. It is the same document served at /swagger/openapi.yaml, exposed so conformance tooling (e.g. `tapes dev check-openapi`) can validate served/rendered responses against the published schema without re-reading the file from disk.
Types ¶
type Config ¶
type Config struct {
// ListenAddr is the address to listen on (e.g., ":8081")
ListenAddr string
// Embedder for converting query text to vectors (optional, enables MCP server)
Embedder embeddings.Embedder
// SpanSearcher enables GET /v1/search/spans — semantic search over
// the span projection's embeddings (optional). Requires Embedder.
SpanSearcher SpanSearcher
// Pricing is the model pricing table used by /v1/sessions/summary to
// compute per-session cost. When nil, sessions.DefaultPricing() is used.
Pricing sessions.PricingTable
// EnableWebUI serves the minimal browser UI at /. It is disabled by default
// so API-only servers do not expose a human-facing development UI unless
// explicitly requested.
EnableWebUI bool
// SkillLLM* configure the LLM used by POST /v1/skills/generate. They are
// populated from the search/embedding credential so skill extraction
// reuses the same shared key the platform already mounts for search —
// no separate provider key. An empty Provider/APIKey falls back to the
// generator's env/credentials resolution at call time.
SkillLLMProvider string
SkillLLMModel string
SkillLLMAPIKey string
SkillLLMBaseURL string
}
Config is the API server configuration.
type MainUsage ¶ added in v0.25.0
type MainUsage struct {
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
}
MainUsage is the task token slice of a trace: the main agent and its subagents (call_kind=main across every thread), no cache split or cost (those live on the total Usage). Deliberately not spine-only — a subagent doing the user's work is task spend, not shadow (RFD 00007 §C).
type Metrics ¶ added in v0.7.0
type Metrics struct {
// contains filtered or unexported fields
}
Metrics is the Prometheus surface for the Tapes API server. Each Server owns its own registry so tests can scrape in isolation; the production path mounts /metrics on the Fiber app via NewServer.
func NewMetrics ¶ added in v0.7.0
func NewMetrics() *Metrics
NewMetrics constructs the Tapes API server's RED metrics. Labels stay templated (`route`) rather than per-URL so :hash path params don't blow up cardinality.
func (*Metrics) Handler ¶ added in v0.7.0
Handler returns a Fiber handler that serves Prometheus text exposition from this Metrics instance's registry. Mount it at /metrics with no auth. The handler is built once at NewMetrics time and cached — see the scrapeHandler field comment for why.
func (*Metrics) Middleware ¶ added in v0.7.0
Middleware returns a Fiber handler that records request count + duration per (route template, method, status). Templates like /v1/sessions/:hash stay as the label value so the :hash path param never expands cardinality.
Register this OUTSIDE recover.New() (i.e. via app.Use before recover) — see resolveStatus for why.
func (*Metrics) Registry ¶ added in v0.7.0
func (m *Metrics) Registry() *prometheus.Registry
Registry exposes the *prometheus.Registry so tests can scrape against the same registry the middleware writes to.
type ModelUsage ¶ added in v0.16.0
type ModelUsage struct {
Model string `json:"model"`
Calls int64 `json:"calls"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CostUsd float64 `json:"cost_usd"`
}
ModelUsage is one model's contribution to a session in the API: how many llm calls ran on it and what they spent. Cost-weighted (priced at derive time) so a per-model share reflects spend, not call count.
type PayloadMode ¶ added in v0.16.0
type PayloadMode string
PayloadMode selects how much span payload a trace response carries. Full embeds the stored content verbatim; preview truncates long text so list-shaped reads stay O(structure), with the span drill-in endpoint serving the full payload on demand.
const ( PayloadFull PayloadMode = "full" PayloadPreview PayloadMode = "preview" )
type RawTurnHeaderItem ¶ added in v0.16.0
type RawTurnHeaderItem struct {
ID int64 `json:"id"`
Source string `json:"source"`
Provider string `json:"provider,omitempty"`
AgentName string `json:"agent_name,omitempty"`
RequestID string `json:"request_id,omitempty"`
ReceivedAt time.Time `json:"received_at"`
Meta json.RawMessage `json:"meta,omitempty" swaggertype:"object"`
RequestBytes int64 `json:"request_bytes"`
ResponseBytes int64 `json:"response_bytes"`
}
RawTurnHeaderItem is one wire-log row: what crossed the wire (or arrived as a transcript push), without the payload blobs. The `source` field is the wire-vs-transcript distinction.
type RawTurnListResponse ¶ added in v0.16.0
type RawTurnListResponse struct {
Items []RawTurnHeaderItem `json:"items"`
}
RawTurnListResponse is a session's wire log.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the API server for managing and querying the Tapes system
func NewServer ¶
NewServer creates a new API server. The storer is injected to allow sharing with other components (e.g., the proxy when not run as a singleton).
func (*Server) RunWithListener ¶
RunWithListener starts the API server using the provided listener.
type SessionDetailResponse ¶ added in v0.12.0
type SessionDetailResponse struct {
Session SessionItem `json:"session"`
}
SessionDetailResponse is the response for GET /v1/sessions/:id: the session record alone. The conversation content lives on the span model (GET /v1/sessions/:id/traces).
type SessionItem ¶ added in v0.12.0
type SessionItem struct {
// Identity — capture-side facts, ingest-written.
ID string `json:"id"`
HarnessID string `json:"harness_id"`
HarnessSessionID string `json:"harness_session_id"`
Cwd string `json:"cwd,omitempty"`
HarnessVersion string `json:"harness_version,omitempty"`
ParentSessionID string `json:"parent_session_id,omitempty"`
StartedAt time.Time `json:"started_at"`
LastSeenAt time.Time `json:"last_seen_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
HarnessMetadata map[string]any `json:"harness_metadata,omitempty"`
// AuthSubject is the gateway-stamped JWT subject (WorkOS user id)
// captured at ingest; empty for rows captured before the edge began
// stamping it.
AuthSubject string `json:"auth_subject,omitempty"`
// Name is the session's display label: the value on the identity row —
// a user rename or the harness-supplied session name — when set,
// otherwise the folded title (rollup.title) as a fallback. Empty only
// when the session has neither. It therefore equals rollup.title
// exactly when no identity-row name exists.
Name string `json:"name,omitempty"`
// Live is a runtime presence signal, not a projection fact: true when
// the session was seen within the liveness window AND the deriver has
// not marked it terminal. Computed at response time from last_seen_at,
// so the console renders it directly instead of inferring "running"
// from recency itself (keeps the console dumb; RFD 00007 §C).
Live bool `json:"live"`
// Rollup is the deriver-owned projection over the session's spans.
Rollup SessionRollup `json:"rollup"`
}
SessionItem is the per-session shape: capture identity at the top level, the deriver-owned projection nested under `rollup`. The split mirrors the storage rows — identity is ingest-written, rollup is deriver-written — so the wire can't blur which layer owns a field.
type SessionListResponse ¶ added in v0.4.0
type SessionListResponse struct {
Items []SessionItem `json:"items"`
NextCursor string `json:"next_cursor,omitempty"`
}
SessionListResponse is the response envelope for GET /v1/sessions.
type SessionRollup ¶ added in v0.25.0
type SessionRollup struct {
Status string `json:"status"`
// Title is the deriver's folded session title (derived_title),
// generated from the conversation. Empty until title generation
// produces one. It never falls back to the identity-row name, so it is
// the stable descriptive title clients prefer for display; the
// identity-row label (harness name or rename) is SessionItem.Name.
Title string `json:"title,omitempty"`
Preview string `json:"preview,omitempty"`
TurnCount int `json:"turn_count"`
// Model is the dominant conversation-spine model; ModelUsage is the
// per-model spend breakdown across every thread (subagent models
// included), cost-ordered so the UI can show "dominant model + share"
// without a cheap-subagent fan-out skewing it.
Model string `json:"model,omitempty"`
ModelUsage []ModelUsage `json:"model_usage,omitempty"`
// KindCounts (spans per call_kind) and Tasks (TaskCreate/TaskUpdate
// folds) are pinned so the rollup shape is uniform across sessions.
KindCounts map[string]int `json:"kind_counts"`
Tasks []TreeTask `json:"tasks"`
Usage SessionUsage `json:"usage"`
}
SessionRollup is the deriver-owned session projection — status, title, counts, and spend, all folded from the span layer at derive time. Every field is 'unknown'/zero/empty until the session first derives.
type SessionTracesResponse ¶ added in v0.16.0
type SessionTracesResponse struct {
Schema string `json:"schema"`
Session SessionItem `json:"session"`
Traces []TraceDetail `json:"traces"`
Links []SpanLinkItem `json:"links"`
}
SessionTracesResponse is the composite session view on the span model. `schema` stamps the projection generation the rows were derived against, so the presentational shape can version independently.
func BuildSessionTraces ¶ added in v0.16.0
func BuildSessionTraces( session SessionItem, turns []storage.SpanTurnRecord, spans []storage.SpanRecord, links []storage.SpanLinkRecord, mode PayloadMode, ) *SessionTracesResponse
BuildSessionTraces assembles the composite response. Pure rendering: every edge and kind here was computed by the deriver. Exported so `tapes dev trace-fixtures` emits byte-identical JSON to the handler.
type SessionUsage ¶ added in v0.25.0
type SessionUsage struct {
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CostUSD float64 `json:"cost_usd"`
}
SessionUsage is the session's total token/cost spend, folded from the span layer. Pinned (no omitempty) for a uniform object shape.
type SpanItem ¶ added in v0.16.0
type SpanItem struct {
TraceID string `json:"trace_id"`
SpanID string `json:"span_id"`
ParentSpanID string `json:"parent_span_id,omitempty"`
// Seq is the span's presentation ordinal within its trace; spans
// arrive sorted by it (started_at ties inside one llm call — parallel
// tool batches share an instant).
Seq int64 `json:"seq"`
Kind string `json:"kind"`
Name string `json:"name"`
Status string `json:"status"`
StartedAt time.Time `json:"started_at"`
DurationNS int64 `json:"duration_ns"`
// Deriver-written taxonomy, promoted from the old metadata grab-bag.
CallKind string `json:"call_kind"`
Model string `json:"model"`
StopReason string `json:"stop_reason"`
ThreadID string `json:"thread_id"`
RawTurnID int64 `json:"raw_turn_id,omitempty"`
// Verdict is the typed security-monitor disposition (null off
// permission-check spans), deriver-written. It is a Verdict object or
// null on the wire; typed as `object` (not the json.RawMessage byte
// array swag would otherwise emit) via swaggertype.
Verdict json.RawMessage `json:"verdict" swaggertype:"object" extensions:"x-nullable"`
// Input/Output are content-block arrays (llm.ContentBlock), uniform for
// every kind (tool spans included — no unwrapping). Pinned to [] when
// empty. swaggertype keeps the wire an array-of-object rather than the
// json.RawMessage byte array swag infers.
Input json.RawMessage `json:"input" swaggertype:"array,object"`
Output json.RawMessage `json:"output" swaggertype:"array,object"`
// Usage (was `metrics`) is an llm.Usage object on the wire — {}-pinned
// for usage-less spans.
Usage json.RawMessage `json:"usage" swaggertype:"object"`
// Payload marks a preview-truncated span so the console drills in for
// the full payload; absent in full mode.
Payload string `json:"payload,omitempty"`
}
SpanItem is one observed unit of work. Every field is a deriver output, formatting-only: the harness-taxonomy fields (call_kind, model, stop_reason, thread_id, verdict) are typed rather than bagged in a metadata map, and input/output are uniform content-block arrays for ALL kinds — the console owns per-kind rendering.
type SpanLinkItem ¶ added in v0.16.0
type SpanLinkItem struct {
Kind string `json:"kind"`
FromTraceID string `json:"from_trace_id"`
FromSpanID string `json:"from_span_id"`
FromIO string `json:"from_io,omitempty"`
ToTraceID string `json:"to_trace_id"`
ToSpanID string `json:"to_span_id"`
ToIO string `json:"to_io,omitempty"`
}
SpanLinkItem is a dataflow edge. kind is a typed top-level field (rejoin / verdict / compaction-seam / emits / feeds); from/to trace ids differ on cross-trace causality.
type SpanSearchOutput ¶ added in v0.16.0
type SpanSearchOutput struct {
Query string `json:"query"`
Results []SpanSearchResult `json:"results"`
Count int `json:"count"`
}
SpanSearchOutput is the span search response.
type SpanSearchResult ¶ added in v0.16.0
type SpanSearchResult struct {
TraceID string `json:"trace_id"`
SpanID string `json:"span_id"`
SessionID string `json:"session_id,omitempty"`
Score float32 `json:"score"`
// UserPrompt is the prompt of the turn (trace) the span belongs to.
// Served explicitly (not omitempty) so a synthetic turn's empty prompt
// reaches consumers as "" rather than a dropped key — see TraceItem.
UserPrompt string `json:"user_prompt"`
// Snippet previews the matched span's delta-only text.
Snippet string `json:"snippet,omitempty"`
Model string `json:"model,omitempty"`
StartedAt time.Time `json:"started_at"`
}
SpanSearchResult is one span hit with its trace/turn context.
type SpanSearcher ¶ added in v0.16.0
type SpanSearcher interface {
Search(ctx context.Context, orgID string, embedding []float32, topK int) ([]spanembed.Hit, error)
}
SpanSearcher performs vector-similarity search over span embeddings. *spanembed.Store implements it; tests substitute fakes.
type StatsResponse ¶ added in v0.4.0
type StatsResponse struct {
SessionCount int `json:"session_count"`
TurnCount int `json:"turn_count"`
CompletedCount int `json:"completed_count"`
TotalCost float64 `json:"total_cost"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
TotalDurationMs int64 `json:"total_duration_ms"`
ToolCalls int `json:"tool_calls"`
}
StatsResponse is the response for GET /v1/stats.
The numbers come from the span-projection trace-grain rollups, so they agree with the session detail and trace views:
- InputTokens / OutputTokens / TotalCost are SUMs of span_turns rollups — delta-only per-call usage, never the re-sent history (each main call re-bills the whole conversation on the wire).
- TotalDurationMs is the SUM of trace durations — agent time. Idle time between turns does not count. Served in milliseconds, not the nanoseconds we store: the summed ns over a wide window overflows a JSON consumer's 2^53 safe-integer range (~104 cumulative days), and sub-ms precision is meaningless for an aggregate agent-time figure.
- TurnCount counts traces (user-visible turns).
- CompletedCount counts distinct sessions whose denormalized derived_status is 'completed' (chain-aware, PCC-515).
type TraceDetail ¶ added in v0.16.0
type TraceDetail struct {
Schema string `json:"schema,omitempty"`
Trace TraceItem `json:"trace"`
Spans []SpanItem `json:"spans"`
Links []SpanLinkItem `json:"links,omitempty"`
}
TraceDetail is one trace with its spans. In the composite session response links are session-scoped (top level); the single-trace endpoint sets Links to the edges touching that trace. `schema` stamps the projection generation on the STANDALONE /v1/traces/{id} response (omitempty — the composite embeds TraceDetail and already carries one stamp at the top level, so the embedded copies stay unstamped).
func BuildTraceDetail ¶ added in v0.16.0
func BuildTraceDetail(turn storage.SpanTurnRecord, spans []storage.SpanRecord, links []storage.SpanLinkRecord, mode PayloadMode) TraceDetail
BuildTraceDetail renders one turn with its spans and links. Exported so `tapes dev trace-fixtures` emits byte-identical JSON to the handler.
type TraceItem ¶ added in v0.16.0
type TraceItem struct {
TraceID string `json:"trace_id"`
// UserPrompt is served explicitly (not omitempty): a synthetic opener
// has an empty prompt, and dropping the key turns the empty string
// into `undefined` on the wire, which breaks consumers that expect a
// string (e.g. the console's stripHarnessTags). Empty means synthetic.
UserPrompt string `json:"user_prompt"`
// ResponsePreview is the derive-time fold of the closing
// conversation-spine llm call's text output — the answer line for
// collapsed turn cards, so summary consumers never need spans.
ResponsePreview string `json:"response_preview,omitempty"`
Status string `json:"status"`
// Source is the capture origin of the turn's rows ("wire" |
// "transcript"), promoted from raw_turns.source. Per-trace, so a
// session can mix live wire capture and transcript backfill. Today
// every trace is "wire": transcripts only reconcile fork/parent edges
// during derivation, they never form a trace on their own. "transcript"
// becomes real when a session is reconstructed purely from a transcript
// file with no proxy capture (an OSS backfill path). See RFD 00007 §C.
Source string `json:"source"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
DurationNS int64 `json:"duration_ns"`
SpanCount int `json:"span_count"`
// Usage is the trace's total token/cost spend over ALL llm spans,
// shadow calls included; MainUsage is the task slice — the main agent
// and its subagents (every call_kind=main span, across threads). The
// difference (Usage − MainUsage) is the harness's shadow spend
// (permission checks, title-gen, web summaries) on the turn.
Usage TraceUsage `json:"usage"`
MainUsage MainUsage `json:"main_usage"`
// Synthetic is a typed deriver signal ("post-compaction" for a
// compaction continuation, "shadow-opener" for a shadow-only opener),
// promoted out of the old metadata grab-bag. Absent for genuine
// prompt-opened turns.
Synthetic string `json:"synthetic,omitempty"`
}
TraceItem is one user-visible turn's header. session_id / harness ids are not duplicated here — they belong to the session. A trace's post-compaction status is the typed Synthetic field below (promoted out of the old metadata grab-bag); the same seam is also recoverable from the session's compaction-seam links.
type TraceListResponse ¶ added in v0.16.0
TraceListResponse is the summaries list for one session. `schema` stamps the projection generation the rows were derived against — the same stamp the composite carries — so every trace-grain response is self-describing, not just the composite.
func BuildTraceList ¶ added in v0.16.0
func BuildTraceList(rows []storage.TraceSummaryRecord) TraceListResponse
BuildTraceList renders the turn-summary rows for one session. Exported so `tapes dev trace-fixtures` emits byte-identical JSON to the handler.
type TraceUsage ¶ added in v0.25.0
type TraceUsage struct {
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheCreationTokens int64 `json:"cache_creation_tokens"`
CostUSD float64 `json:"cost_usd"`
}
TraceUsage is a trace's total token/cost rollup. Fields are pinned (no omitempty) so the object shape is uniform across traces.