api

package
v0.25.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: AGPL-3.0 Imports: 31 Imported by: 0

Documentation

Overview

Package api provides an HTTP API server over the derived sessions/traces/spans read model.

Index

Constants

View Source
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

func (m *Metrics) Handler() fiber.Handler

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

func (m *Metrics) Middleware() fiber.Handler

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

func NewServer(config Config, driver storage.Driver, log *slog.Logger) (*Server, error)

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) Run

func (s *Server) Run() error

Run starts the API server on the configured address.

func (*Server) RunWithListener

func (s *Server) RunWithListener(listener net.Listener) error

RunWithListener starts the API server using the provided listener.

func (*Server) Shutdown

func (s *Server) Shutdown() error

Shutdown gracefully shuts down the API server.

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

type TraceListResponse struct {
	Schema string      `json:"schema"`
	Items  []TraceItem `json:"items"`
}

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.

type TreeTask added in v0.16.0

type TreeTask struct {
	ID          string `json:"id"`
	Subject     string `json:"subject"`
	Description string `json:"description,omitempty"`
	Status      string `json:"status"`
	Updates     int    `json:"updates"`
}

TreeTask is one task folded from the session's TaskCreate/TaskUpdate calls.

Directories

Path Synopsis
Package mcp provides an MCP (Model Context Protocol) server for the Tapes system.
Package mcp provides an MCP (Model Context Protocol) server for the Tapes system.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL