Documentation
¶
Overview ¶
Package obsdb is the observability database: what weft/otel's local sink writes and Weft Studio reads, in one module so neither owns the schema (ADR 0024, S3).
The model is OTLP-shaped — spans and log records, resource included — so a stock OpenTelemetry pipeline's data and weft's own land in the same tables. Weft identity (the run → session → public-id chain) is derived from attributes, never parsed out of ids, and is stored as indexed columns through the Weft struct.
Three moving parts:
- DB is the backend interface: Write (idempotent on (run, record kind, position) and (trace, span)), run/session queries, the event page with its gap detector, the transcript a replay reads, and the spans of a run or trace. Status is derived from the four-row table (DeriveStatus), never stored.
- The hub (Hub, Frame) is the live lane: every Write publishes its frames before returning, so an in-process subscriber — Studio's SSE handler in setup A — sees a record without a network hop.
- obsdbtest is the conformance table every backend runs (the storetest pattern); sqlite is the default backend, clickhouse the hosted one (its own module).
Deltas travel but are not stored (their own counter means their absence never looks like a lost event); heartbeats are never stored, they only move a run's last-seen. Both rules are Write's, shared by every backend.
Index ¶
- Constants
- Variables
- func ApplyView(transcript []core.Message, v Compaction) ([]core.Message, error)
- func CheckToolArgs(tool string, schema, args json.RawMessage) error
- func DedupTranscript(bodies []json.RawMessage) []json.RawMessage
- func ExplainMissing(ctx context.Context, db DB, runID, kind, hash string) error
- func HoleNote(h Hole) (reason, fix string)
- func HoleNoteFor(h Hole, cause HoleCause) (reason, fix string)
- func LimitOf(n int) int
- func MetaOf(attrs map[string]any) map[string]string
- func ParseSeverity(s string) (n int, ok bool)
- func SeverityText(n int) string
- func SortCompactions(cs []Compaction)
- func TranscriptBodies(batches []TranscriptBatch) []json.RawMessage
- type Assembler
- type Batch
- type Compaction
- type DB
- type EventPage
- type Experiment
- type ExperimentInput
- type ExperimentVariant
- type Frame
- type Hole
- type HoleCause
- type HoleError
- type Hub
- type HubOption
- type LogCandidates
- type LogPage
- type LogQuery
- type OtherLog
- type PosEvent
- type PromptRecord
- type Record
- type RequestBody
- type RequestMessagesRef
- type RequestModel
- type RequestParams
- type RequestQuery
- type RequestRecord
- type RequestThinking
- type RequestToolChoice
- type RequestTools
- type RunDetail
- type RunPage
- type RunQuery
- type RunRow
- type Selector
- type SessionDetail
- type SessionPage
- type SessionQuery
- type SessionRow
- type Span
- type SpanEvent
- type Status
- type StepMessages
- type StepMessagesError
- type StoredRecord
- type ToolEntry
- type ToolsRecord
- type TranscriptBatch
- type Weft
Examples ¶
Constants ¶
const ( CompactionRun = "run" CompactionSession = "session" ReasonCompacted = "compacted" // RecordCompaction is the weft.record of thread's session marker. RecordCompaction = "compaction" )
The two compaction scopes (weft.compaction.scope) and the one weft.messages.reason ADR 0028 §8 defines.
const ( FrameRecord = "record" FrameRun = "run" )
Frame kinds.
const InterruptedAfter = 30 * time.Second
InterruptedAfter is the "last seen" age that turns a non-terminal run interrupted: three missed heartbeats.
const LogSkew = time.Minute
LogSkew widens a run's time window for the app-log scan (the otel_logs read on ClickHouse, the same bound on SQLite): clock skew between the emitters of one run.
const MaxGaps = 1000
MaxGaps bounds EventPage.Gaps: positions are the sender's numbers, and one stray high position must not turn a page read into a list of every position below it.
const MaxLogCandidates = 10_000
MaxLogCandidates caps how many log records one DB.OtherLogs read loads: the first non-weft records of the run's traces in its time window, by time — before attribution, so a subagent's or a sibling run's lines in the same trace (and duplicates) count against it. A run whose traces hold more reads LogPage.Truncated.
const MaxTies = 500
MaxTies bounds how far a page may run past its Limit to finish a group of rows sharing the cursor's time (RunPage, SessionPage): one page never grows unbounded however many rows share one timestamp.
Variables ¶
var ErrClosed = errors.New("obsdb: closed")
ErrClosed is returned by a backend used after Close.
var ErrNotFound = errors.New("obsdb: not found")
ErrNotFound is returned by Run, Session, Events, Transcript, RunSpans, OtherLogs, Requests, Prompt, Tools, Catalogs, Experiment and ResolvePublicID for an id the database does not hold (Prompt and Tools also for a hash, possibly as a *HoleError). Trace answers an unknown trace with no spans instead: a trace is only ever the spans that arrived.
var ErrStepMessages = errors.New("obsdb: the records do not rebuild the step's messages")
ErrStepMessages is matched (errors.Is) by every *StepMessagesError: the stored records do not rebuild the messages a request names.
Functions ¶
func ApplyView ¶ added in v0.13.0
ApplyView rebuilds the messages a compacted request carried (ADR 0028 §8): transcript — the growth records below the view's index, concatenated — with the range [FromSeq, ToSeq) replaced by the view's messages. A range that does not fit the transcript, or a body that is not messages, is an error.
func CheckToolArgs ¶ added in v0.13.0
func CheckToolArgs(tool string, schema, args json.RawMessage) error
CheckToolArgs checks a tool call's arguments against the tool's input schema as a tools record stores it (ToolEntry.Schema): the check a playground `tool_args` transcript edit passes before it is accepted (ADR 0029 §8), on both of its surfaces — weft/runtime's authoritative copy and Studio's mirror — so the two refuse in one wording. A nil error means the arguments fit.
The failure is a *core.ToolError coded INVALID_INPUT whose cause is core.ErrInvalidToolInput, worded like the loop where the loop has a sentence for it — `INVALID_INPUT: tool "lookup_order": field "days": expected integer, got string` — naming the field by its JSON path (dotted; an array element by its index). The loop's decode does not check required fields or enums; their sentences ("missing required field …", "… is not one of the schema's values") are this check's own. A null field is a no-op, as encoding/json decodes it; an integer must be written as one (3.0 is refused, as the loop refuses it).
The arguments must be one JSON object (empty or null reads as {}, as the loop decodes them). The schema walk covers the vocabulary weft's reflector and a hand-written schema share: type (one or a list), properties, required, additionalProperties (false refuses an undeclared field; a schema checks each extra one), items and enum. Any other keyword is not checked. An empty or unreadable schema checks the object shape alone.
Example ¶
A playground tool_args edit is checked against the schema the run's tools record stored before the replay is accepted: a wrong type is refused in the loop's own words, naming the field.
package main
import (
"encoding/json"
"fmt"
"github.com/weftgo/weft/obsdb"
)
func main() {
schema := json.RawMessage(`{"type":"object","properties":{"order_id":{"type":"string"}},"required":["order_id"]}`)
fmt.Println(obsdb.CheckToolArgs("lookup_order", schema, json.RawMessage(`{"order_id":"7"}`)))
fmt.Println(obsdb.CheckToolArgs("lookup_order", schema, json.RawMessage(`{"order_id":7}`)))
}
Output: <nil> INVALID_INPUT: tool "lookup_order": field "order_id": expected string, got number
func DedupTranscript ¶
func DedupTranscript(bodies []json.RawMessage) []json.RawMessage
DedupTranscript makes a run's messages bodies concatenate to the transcript the run actually held. bodies are the messages records' bodies in index order, as a backend's Transcript reads them; every backend's Transcript returns its result through this.
One stored shape needs it: a run recorded before ADR 0028 §8's partial-resume shape. A resume whose input already holds a partial tool message (some calls of the step answered, the rest parked) was recorded with that input as fed — index 0 — and then the tool message it rebuilt over the partial one, at index 1. Concatenated naively the two versions of the one message sit side by side; the later record is the authoritative one. The current core never writes that shape (its input record stops at the last assistant message with tool calls and the rebuilt message is the next growth record, step 0); this stays for runs stored in the old one. So when the second body is a lone tool message and the first holds, directly after its last assistant message with tool calls, a tool message whose results the lone one repeats, the partial message is dropped from the first body. Every other message stays byte-for-byte, the number of bodies never changes (a body's place is still its record's index), and any other shape is returned untouched.
func ExplainMissing ¶ added in v0.10.0
ExplainMissing is the error DB.Prompt and DB.Tools return for a hash the run holds no record of (kind "prompt" or "tools"): a *HoleError when the reason is known (HoleError's table), a plain ErrNotFound otherwise, and the read's own error when the run or its requests cannot be read. Every backend answers through it, so both say the same thing for the same records.
func HoleNote ¶ added in v0.10.0
HoleNote is a badge's one-line reason and, where one exists, its one-line fix ("" when the hole has none: a redaction is by design, a derived value has nothing to repair) — ADR 0028 §11's table in words, the one source both Studio surfaces render (its golden, studio/testdata/holes.golden.json, is what Studio's TypeScript table is checked against). A reader that knows more (a step's own gap, a run built from spans) says so in its own reason and keeps this fix. An unknown hole has neither.
func HoleNoteFor ¶ added in v0.10.0
HoleNoteFor is HoleNote for a hole whose cause the reader knows: the cause's own reason and fix where the table words it, HoleNote(h) otherwise (CauseDefault included). The one table for every surface (run page, panel, export).
func LimitOf ¶
LimitOf normalizes a query limit: 0 means the default 50, values above 500 clamp to 500, negatives read as the default.
func MetaOf ¶
MetaOf returns the caller metadata a set of attributes carries: every string value under a key outside the record/span contract (what core.Metadata put there, enduser.id included). Keys under "weft." that are not part of the contract — thread's weft.session.parent, a future module's own — are metadata and are kept.
func ParseSeverity ¶ added in v0.10.0
ParseSeverity reads a severity filter: a level name (trace, debug, info, warn or warning, error, fatal — any case, the range's first number), a SeverityText spelling ("WARN2"), or a number 1–24. ok is false for anything else.
func SeverityText ¶ added in v0.10.0
SeverityText is an OTLP severity number's short name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR" or "FATAL" for the first number of each range, with the range's ordinal after it for the rest ("WARN2" is 14); "" for 0 (unspecified) and anything outside 1–24.
func SortCompactions ¶ added in v0.10.0
func SortCompactions(cs []Compaction)
SortCompactions orders DB.Compactions' answer: the views by index, then the session markers in the order given (a backend reads them in emission order). A marker is filed under the run that produced the compacted context, so it follows that run's own views.
func TranscriptBodies ¶ added in v0.10.0
func TranscriptBodies(batches []TranscriptBatch) []json.RawMessage
TranscriptBodies returns the batches' bodies in order — Transcript's answer, for a backend that reads both through one query.
Types ¶
type Assembler ¶ added in v0.13.0
type Assembler struct {
// contains filtered or unexported fields
}
Assembler is AssembleStep over one run's growth batches decoded once: each Step call answers exactly what AssembleStep answers for the same records and step, without re-reading every batch (a reader that walks every step of a run — the wefttest fixture export — would otherwise decode the transcript once per step). The messages Step returns share their parts with the Assembler's decoded batches: read them, or copy before patching. Not safe for concurrent use.
func NewAssembler ¶ added in v0.13.0
func NewAssembler(batches []TranscriptBatch) *Assembler
NewAssembler decodes batches once for Step.
func (*Assembler) Step ¶ added in v0.13.0
func (a *Assembler) Step(requests []RequestRecord, compactions []Compaction, step int) (StepMessages, error)
Step is AssembleStep for one step over the decoded batches.
type Batch ¶
Batch is one write: spans and records as ingested, in arrival order. Write is idempotent on (run, record kind, pos) and (trace, span), so a retried transport's duplicate is a no-op.
type Compaction ¶ added in v0.10.0
type Compaction struct {
Scope string
Hash string // weft.compaction.hash
Index int64
Step int
FromSeq int64
ToSeq int64
Messages json.RawMessage
Reason string
Replaced int
Entries int
TokensBefore int64
TokensAfter int64
}
Compaction is one compaction a run's records name (ADR 0028 §8), read by DB.Compactions. Two scopes:
- CompactionRun: a compaction view, the messages record the core emits when a PrepareStep sent a request whose messages are not the run's transcript. The request whose messages_ref names Index saw the growth records up to Index, concatenated, with the transcript range [FromSeq, ToSeq) replaced by Messages. It is never part of the plain transcript (Transcript and TranscriptBatches hold growth records only) and never applies to another request.
- CompactionSession: the informational marker weft/thread emits (a record of kind compaction) when the session compacts, filed under the last run that produced the compacted context (or, for a context no run produced, under the next run). The next run's input record holds the compacted context literally; the marker carries no messages and a reader never applies it. Index and Step are -1, FromSeq and ToSeq 0.
Replaced and Entries are the change in messages: for a view, ToSeq-FromSeq and the length of Messages; for a session marker, the counts thread reported over the session's context. TokensBefore and TokensAfter are thread's estimates (session scope only; 0 for a view, whose tokens the core does not estimate). Reason is thread's compaction reason (manual, threshold, overflow, from_hook, trim); "" for a view.
func CompactionOf ¶ added in v0.10.0
func CompactionOf(kind string, index int64, step int, attrs map[string]any, body []byte) (c Compaction, ok bool, err error)
CompactionOf reads one stored record as a Compaction, so every backend parses alike. kind is the record's weft.record, index its position, step its weft.step.index, attrs its attributes (numbers as numbers or numeric strings) and body its body. ok is false for a record that names no compaction — a growth messages record, any other kind. A messages record whose weft.messages.reason is not one this build knows is an error: ADR 0028 §8 has readers fail loudly rather than mistake a view they cannot apply for something else.
func ViewOf ¶ added in v0.13.0
func ViewOf(rec RequestRecord, compactions []Compaction) (Compaction, bool)
ViewOf returns the run-scope compaction view a request record names: the one whose index is the request's messages_ref.index. ok is false for a request that carried the plain transcript (its ref names a growth record) or names nothing.
type DB ¶
type DB interface {
// Write stores one batch, idempotent on (run, record kind, pos)
// and (trace, span): a retried transport's duplicate is a no-op.
// Deltas are counted, never stored; heartbeats are never stored —
// they only move the run's last-seen (DeriveStatus's clock).
Write(ctx context.Context, b Batch) error
Runs(ctx context.Context, q RunQuery) (RunPage, error)
Run(ctx context.Context, id string) (RunDetail, error) // ErrNotFound
// Events pages a run's durable events in position order. after is
// exclusive — pass -1 to read from the start (0 would skip position
// 0, the run_start) and EventPage.NextAfter to continue. limit: 0 =
// 100, max 1000.
Events(ctx context.Context, runID string, after int64, limit int) (EventPage, error)
// Transcript returns the messages bodies in index order, one per
// growth messages record (a compaction view, ADR 0028 §8, is not
// transcript: see Compactions), read through DedupTranscript — so
// they concatenate to the transcript the run held.
Transcript(ctx context.Context, runID string) ([]json.RawMessage, error)
// TranscriptBatches is Transcript with what each messages record
// stored beside its body: its index, the step it joined
// (weft.step.index, -1 when the record carried none) and whether
// it is the run's input (weft.messages.input). The bodies are
// Transcript's, through DedupTranscript, one per record.
TranscriptBatches(ctx context.Context, runID string) ([]TranscriptBatch, error)
// Compactions returns the compactions a run's records name (ADR
// 0028 §8): every run-scope view in index order, then the session
// markers thread filed under the run, in emission order — what a reader needs to draw the compaction and
// rebuild a request's messages. Transcript and TranscriptBatches
// never include a view: the plain transcript is growth records
// only. A messages record with an unknown weft.messages.reason is
// an error. ErrNotFound for an unknown run; empty, not an error,
// for a run that never compacted.
Compactions(ctx context.Context, runID string) ([]Compaction, error)
// The request record (ADR 0028). Requests pages a run's request
// records in index order, one per model-call attempt
// (RequestQuery). Prompt and Tools return the run's prompt or tools
// record of one hash: ErrNotFound when the run holds none, as a
// *HoleError when the reason is known (not_recorded, stripped,
// gap — ExplainMissing). Catalogs returns every tools record of the
// run, one per hash, in index order; a content-off run has none.
// All four answer ErrNotFound for an unknown run.
Requests(ctx context.Context, runID string, q RequestQuery) ([]RequestRecord, error)
Prompt(ctx context.Context, runID, hash string) (PromptRecord, error)
Tools(ctx context.Context, runID, hash string) (ToolsRecord, error)
Catalogs(ctx context.Context, runID string) ([]ToolsRecord, error)
RunSpans(ctx context.Context, runID string) ([]Span, error)
// OtherLogs pages a run's app log records — the non-weft records
// the writers store beside weft's (other_logs on SQLite, otel_logs
// on ClickHouse), attributed to the run through the spans they were
// emitted under — in time order (LogQuery, ReadOtherLogs), with
// what the read could not show (LogPage: partial while running,
// lines under a never-stored span, the candidate cap).
// ErrNotFound for an unknown run; empty Logs, not nil, for a run
// with no app logs; a *HoleError (Kind "logs", HoleNotRecorded) for
// a finished run with no span to attribute them through.
OtherLogs(ctx context.Context, runID string, q LogQuery) (LogPage, error)
Trace(ctx context.Context, traceID string) ([]Span, error) // empty, not an error, for an unknown trace
Sessions(ctx context.Context, q SessionQuery) (SessionPage, error)
Session(ctx context.Context, id string) (SessionDetail, error)
ResolvePublicID(ctx context.Context, publicID string) (sessionID string, err error)
// The playground's saved experiments (WEFT-PLAYGROUND §10.4,
// PQ4): the definition rows keyed by the weft.experiment.id the
// runs carry — one small table beside runs.
SaveExperiment(ctx context.Context, e Experiment) error // upsert by ID
Experiments(ctx context.Context) ([]Experiment, error) // newest update first
Experiment(ctx context.Context, id string) (Experiment, error)
Close() error
}
DB is the observability database every backend implements (sqlite by default, clickhouse hosted). Implementations must be safe for concurrent use: writers are rare (the local sink's synchronous processors), readers many (Studio).
type EventPage ¶
type EventPage struct {
Events []PosEvent
NextAfter *int64
Done bool // the run is terminal and every stored event was returned
Gaps []int64
}
EventPage is one page of a run's durable events. Gaps are durable positions missing below the high-water mark: a lost batch, never a delta — deltas are on their own counter, so their absence cannot open a hole here (D3). At most MaxGaps are listed, lowest first.
type Experiment ¶
type Experiment struct {
ID, Name, Agent string
Created, Updated time.Time
Variants []ExperimentVariant
Inputs []ExperimentInput
}
Experiment is one saved playground group: a name, the variants and inputs that define it, keyed by the id its runs carry as weft.experiment.id (§10.4). Variants and inputs are the wire shapes verbatim (overrides JSON included); the runs themselves stay in the runs table.
type ExperimentInput ¶
type ExperimentInput struct {
Key string `json:"key"`
SourceRunID string `json:"source_run_id,omitempty"`
Text string `json:"text,omitempty"`
}
ExperimentInput is one row of the matrix: a key, a source run to take the turn from, or a literal text.
type ExperimentVariant ¶
type ExperimentVariant struct {
Key string `json:"key"`
Overrides json.RawMessage `json:"overrides,omitempty"`
}
ExperimentVariant is one column of the matrix: a key and the overrides object it applies.
type Frame ¶
type Frame struct {
Kind string // "record" | "run"
Record *Record // Kind == record; Weft derived
Weft Weft
Run *RunRow // Kind == run
Seq uint64 // hub-wide, monotonic: the SSE id and the resume cursor
}
Frame is one live-lane message: a record as ingested (deltas included), or a run row that changed.
func RecordFrame ¶
RecordFrame builds the frame a Write publishes for one record: the record as ingested with its derived identity, deltas included.
type Hole ¶ added in v0.10.0
type Hole string
Hole is one badge of ADR 0028 §11's closed table: the one word a reader shows wherever a record is missing, cut or derived. The vocabulary is closed — a new badge is an amendment to that ADR — and Holes lists it. The zero value means no hole.
const ( HoleTruncated Hole = "truncated" // a destination's cap cut the content (weft.content.truncated_bytes) HoleStripped Hole = "stripped" // the destination's chain is content-off (weft.content = stripped) HoleRedacted Hole = "redacted" // the destination's Redact changed the content (reserved: weft's pipeline does not mark a redaction) HoleMaxTokens Hole = "max_tokens" // the step finished on the output token limit HoleInterrupted Hole = "interrupted" // the run stopped reporting (DeriveStatus) HoleGap Hole = "gap" // a hole in a contiguous counter, or a record a destination dropped HoleNotRecorded Hole = "not_recorded" // the record kind did not exist in the version that wrote the run HoleDerived Hole = "derived" // the value was computed by the reader, not recorded HoleHidden Hole = "hidden" // the reader's scope may not see it HoleCompacted Hole = "compacted" // the model saw a compacted view )
The closed table, in ADR 0028 §11's order.
type HoleCause ¶ added in v0.10.0
type HoleCause string
HoleCause narrows a hole to the cause a reader established, when the table words that cause apart: CauseDefault is the hole's own note.
const ( CauseDefault HoleCause = "" // CauseNoSpans: the run's records arrived but no span did — it ran // without a tracer (not_recorded: attempts, the answering model). CauseNoSpans HoleCause = "no_spans" // CauseResultCap: the core's own tool-result cap cut the content // before any destination saw it (truncated: a tool result the model // itself read as a prefix plus the marker, weft.MaxResultBytes). CauseResultCap HoleCause = "result_cap" // CauseLogCap: the app-log reader scanned its candidate cap // (MaxLogCandidates) before attributing lines to the run, so later // lines may be missing (truncated: no destination setting changes // it). CauseLogCap HoleCause = "log_cap" // CauseNoPublicID: the session's turns carry no weft.public_id — // a thread session created without thread.PublicID, or an app that // never stamped one (not_recorded: Studio's // GET /api/sessions/{id}/public_id). CauseNoPublicID HoleCause = "no_public_id" // CauseDevTokenOnly: the route answers the dev (server) token and // setup A's open API only; a panel token, scoped to one public id, // may not learn another (hidden: the session-to-public-id lookup). CauseDevTokenOnly HoleCause = "dev_token_only" // CauseNotServed: the Studio asked serves no request record (its // requests capability is off, or it predates the record), so the // run's request, prompt and tools records were not read — the run // may hold them (not_recorded: the panel's Request tab). CauseNotServed HoleCause = "not_served" // CauseResponseCap: the route bounds how much one response reads // (Studio's GET /api/diff compares a bounded number of steps), and // the run holds more than it read (truncated: nothing was lost; the // rest is a read away). CauseResponseCap HoleCause = "response_cap" )
func HoleCauses ¶ added in v0.10.0
func HoleCauses() []HoleCause
HoleCauses lists the causes the table words, in order.
type HoleError ¶ added in v0.10.0
HoleError is the ErrNotFound DB.Prompt and DB.Tools return when the reader knows why the record is missing: errors.Is(err, ErrNotFound) holds, and errors.As yields the badge. Hole is one of:
- HoleNotRecorded: the run was written before ADR 0028 (its InstructionsHash is ""), so no such record ever existed;
- HoleStripped: a request naming the hash came through a content-off chain, which drops prompt and tools records;
- HoleGap: a request naming the hash was stored as emitted, yet the record it names is absent — a destination dropped it.
A hash no request of the run names is a plain ErrNotFound.
DB.OtherLogs returns one with Kind "logs", no Hash and HoleNotRecorded for a run that has no span to attribute app logs through (ReadOtherLogs).
type Hub ¶
type Hub interface {
Publish(ctx context.Context, f Frame)
Subscribe(ctx context.Context, sel Selector, after uint64) (<-chan Frame, error)
}
Hub is the live lane. Publish never blocks the caller beyond a bounded per-subscriber queue; a slow subscriber is dropped with Overflow.
type HubOption ¶
type HubOption interface {
// contains filtered or unexported methods
}
HubOption configures NewHub.
type LogCandidates ¶ added in v0.10.0
type LogCandidates func(ctx context.Context, traceIDs []string, from, to time.Time, limit int) ([]OtherLog, error)
LogCandidates is a backend's half of DB.OtherLogs: the non-weft log records (no weft.run.id) of the given traces whose time is within [from, to]: the first limit of them, returned in time order (ties in a stable order of the backend's). ReadOtherLogs does the rest.
type LogPage ¶ added in v0.10.0
LogPage is one DB.OtherLogs answer: the page and what the reader knows about what it could not show.
- Partial: the run is running. Spans are exported when they end, so a line under an in-flight span (invoke_agent, an open execute_tool) is not attributable yet: it appears once its span arrives, and it may then take an index below lines already served — indexes are stable only once the run has ended.
- Gap: lines in the run's traces (deduplicated, in its window) that name a span no stored span has — dropped (a lost batch, a process that exited before exporting), or still open (an app span that encloses the run and has not ended). They may belong to this run or to another in the same trace, and are not in Logs. Always 0 while Partial (the span may still be on its way).
- Truncated: the run's traces hold more than MaxLogCandidates log lines in its window — anyone's, the cap applies before attribution — so only lines among the first MaxLogCandidates by time were read: later lines of this run may be missing, and Logs may hold far fewer than MaxLogCandidates.
func ReadOtherLogs ¶ added in v0.10.0
func ReadOtherLogs(ctx context.Context, db DB, runID string, q LogQuery, candidates LogCandidates) (LogPage, error)
ReadOtherLogs is DB.OtherLogs for every backend, so both attribute, order, index and page alike. An app log names no run — it carries no weft.run.id, or the writer would have taken it for a weft record — so it is attributed through the span it was emitted under: a record belongs to the run when its (trace, span) is one of the run's spans (invoke_agent, chat, execute_tool: a tool handler's log line), or a non-weft span below one of them (the app's own span inside a tool), never one below another run's span (a subagent's lines are the child's). A record emitted with no span context cannot be attributed and is in no run's logs.
The scan reads the run's traces, bounded by its time window (Started − LogSkew to LastSeen + LogSkew). Duplicates — a retried transport's batch, which neither backend's log table collapses — are dropped (same time, trace, span, severity, event name, body and attributes). The order is time, then span id, severity and body; the filter applies after indexing (LogQuery). An unknown run is ErrNotFound; a run with spans and no app logs has an empty Logs, never nil. A run with no span at all has nothing to attribute logs through: a running one reads empty and Partial (its spans may not have arrived yet), any other is a *HoleError{Kind: "logs", Hole: HoleNotRecorded} — it was recorded without a tracer.
While the run is running the page is Partial and its indexes may shift (LogPage): a client paging with From may re-read lines and miss as many. TODO(phase 2): a stable cursor — (time, span, seq) rather than an index — so a live walk neither repeats nor skips.
At most MaxLogCandidates records are read (Truncated past it), chosen before attribution. TODO(phase 2): filter by the attributed span set in the backends' SQL, so the cap counts this run's lines only. Lines naming a span never stored are counted, not shown (Gap).
type LogQuery ¶ added in v0.10.0
LogQuery selects a run's app log records for DB.OtherLogs. The zero value reads every record from index 0, 100 at a time. From is the first index read (inclusive): pass the last returned Index + 1 to continue; a page shorter than PageLimit is the last. MinSeverity, when above 0, keeps the records whose OTLP severity number is at least that (ParseSeverity reads a level name); Index is the record's place among all of the run's app logs, so a filtered page's indexes skip and From continues a filtered walk alike. Limit: 0 = 100, max 1000.
type OtherLog ¶ added in v0.10.0
type OtherLog struct {
Index int64
Time time.Time
Severity int
EventName string
Body string
Attrs map[string]any
TraceID string
SpanID string
Service string
}
OtherLog is one app log record of a run: a non-weft record (no weft.run.id — the app's own slog or OTel Logs API lines, which the writers keep in other_logs on SQLite and in otel_logs on ClickHouse) attributed to the run through the span it was emitted under (DB.OtherLogs). Index is its place in the run's app logs, in time order; Severity the OTLP severity number (0 unspecified, 1–24; SeverityText names it). Attrs are the record's attributes: typed on SQLite, strings on ClickHouse, whose otel_logs keeps the collector's string map only.
type PosEvent ¶
type PosEvent struct {
Pos int64
Time time.Time
Event json.RawMessage // the body, verbatim
Content string
TruncatedBytes int64
}
PosEvent is one positioned event of a run: the body verbatim, at its durable position, with the two content attributes the destination's chain stamped on it (ADR 0028 §11): Content is weft.content ("stripped" from a content-off chain, "" as emitted) and TruncatedBytes weft.content.truncated_bytes (what a content-on chain's cap cut, 0 for none). A ClickHouse row written before migration 0004 carries neither.
type PromptRecord ¶ added in v0.10.0
type PromptRecord struct {
Index int64
Time time.Time
Hash string
Text string
Content Hole
TruncatedBytes int64
}
PromptRecord is one stored prompt record (ADR 0028 §4): the composed system text of one distinct system hash. Index is weft.prompt.index. Content is HoleTruncated when a destination's cap cut Text (TruncatedBytes > 0), HoleDerived when the body did not parse (a malformed producer; Hash is then the weft.system.hash attribute). Hash is over the text before the cut.
func PromptRecordOf ¶ added in v0.10.0
func PromptRecordOf(r StoredRecord) PromptRecord
PromptRecordOf builds a PromptRecord from a stored prompt record. Content is HoleTruncated when a cap cut the text; a body that does not parse (a malformed producer) reads HoleDerived, with no text and the hash of the record's weft.system.hash attribute.
type Record ¶
type Record struct {
Time, Observed time.Time
TraceID, SpanID string
Severity int
EventName string
Body string
Service string
Attrs map[string]any
Resource map[string]any
}
Record is one log record, OTLP-shaped.
func FromOTLPLogs ¶
func FromOTLPLogs(req *collogspb.ExportLogsServiceRequest) []Record
FromOTLPLogs decodes an OTLP/HTTP log export request into the model. weft records (weft.run.id present) and non-weft records (slog lines, other services) both pass through; the kind they carry decides where Write puts them.
type RequestBody ¶ added in v0.10.0
type RequestBody struct {
Step int `json:"step"`
Attempt int64 `json:"attempt"`
SystemHash string `json:"system_hash"`
MessagesRef RequestMessagesRef `json:"messages_ref"`
Tools RequestTools `json:"tools"`
ToolChoice *RequestToolChoice `json:"tool_choice,omitempty"`
Thinking *RequestThinking `json:"thinking,omitempty"`
SequentialTools bool `json:"sequential_tools"`
Params RequestParams `json:"params"`
Model RequestModel `json:"model"`
Stream bool `json:"stream"`
}
RequestBody is the request record's body (ADR 0028 §3), keys as the core writes them; an absent field is unset.
type RequestMessagesRef ¶ added in v0.10.0
RequestMessagesRef says which messages the request carried: the run's view as of messages record Index, Count messages long. Index is nil where no messages record exists (capture off).
type RequestModel ¶ added in v0.10.0
type RequestModel struct {
Provider string `json:"provider,omitempty"`
Name string `json:"name,omitempty"`
}
RequestModel is the model the attempt requested.
type RequestParams ¶ added in v0.10.0
type RequestParams struct {
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
Stop []string `json:"stop,omitempty"`
Seed *int64 `json:"seed,omitempty"`
}
RequestParams are the sampling parameters; nil is the adapter's default.
type RequestQuery ¶ added in v0.10.0
RequestQuery selects a run's request records for DB.Requests, in request-index order. The zero value reads every step from index 0, 100 at a time. Step, when set, keeps step *Step's attempts only. From is the first index read (inclusive): pass the last returned Index + 1 to continue; a page shorter than PageLimit is the last. Limit: 0 = 100, max 1000.
func (RequestQuery) PageLimit ¶ added in v0.10.0
func (q RequestQuery) PageLimit() int
PageLimit is the query's normalized limit: 100 for 0 (or negative), 1000 at most.
type RequestRecord ¶ added in v0.10.0
type RequestRecord struct {
Index int64
Step int
Attempt int64
Time time.Time
SystemHash string
CatalogHash string
Content Hole
TruncatedBytes int64 // informational only; a request record is never capped by weft
Body RequestBody
Raw json.RawMessage
}
RequestRecord is one stored request record (ADR 0028 §3): one model call attempt. Index is weft.request.index, Step the stored weft.step.index (-1 when absent), Attempt the body's attempt. The record's weft.system.hash and weft.catalog.hash attributes win over the body's system_hash and tools.catalog_hash when set and they disagree; SystemHash and CatalogHash fall back to the body's where the attribute is absent. Raw is the body verbatim.
Content is the record's hole, from ADR 0028 §11's table:
- "": stored as emitted;
- HoleStripped: it came through a content-off chain (weft.content = stripped: its params.stop and messages_ref.index were removed, and that chain dropped the prompt and tools records it names) — whether or not its body parses;
- HoleDerived: a malformed producer — an unstripped record whose body did not parse, so Body is zero, Attempt is 0, and the two hashes come from the record's attributes.
A request record is never capped (only prompt and tools records are), and a destination's Redact over params.stop is not marked on the record: HoleRedacted is reserved for the readers that can tell (ADR 0028 §11, A3).
func RequestRecordOf ¶ added in v0.10.0
func RequestRecordOf(r StoredRecord) RequestRecord
RequestRecordOf builds a RequestRecord from a stored request record: the hashes are the attributes' where set, the body's otherwise; a stripped mark is HoleStripped, and only an unstripped record whose body does not parse is HoleDerived (RequestRecord).
type RequestThinking ¶ added in v0.10.0
RequestThinking is the thinking configuration in force.
type RequestToolChoice ¶ added in v0.10.0
RequestToolChoice is the tool choice in force.
type RequestTools ¶ added in v0.10.0
RequestTools is the catalog a request offered: its hash ("" for no tools) and the names in the order offered.
type RunDetail ¶
RunDetail is one run with its subagent children (runs whose ParentRunID is this run, by Started).
type RunPage ¶
RunPage is one Runs result, newest Started first (ties by ID, descending). NextBefore and NextBeforeID are the next page's RunQuery.Before and BeforeID, nil and "" on the last page. A page does not end inside a group of runs sharing one Started: the rest of the group rides along, so a caller paging on Before alone skips nothing — up to MaxTies rows past Limit. A longer tie (a bulk import at one truncated timestamp) is cut there, and only the (Before, BeforeID) pair resumes inside it. Total counts the whole match, cursor aside.
type RunQuery ¶
type RunQuery struct {
Agent, SessionID, PublicID, ParentRunID string // ParentRunID: "" top-level only, "*" all
Status Status // "" any
Playground *bool // nil any
ExperimentID string // the runs of one experiment
Meta map[string]string // subset match on metadata
Before time.Time // cursor on Started; zero = newest
// BeforeID, with Before, makes the cursor the pair (Started, ID):
// the runs after that one in the list order — exact inside a group
// sharing one Started. Pass RunPage.NextBeforeID.
BeforeID string
Limit int // 0 = 50, max 500
}
RunQuery selects runs for Runs. The zero value lists top-level runs, newest first, 50 at a time.
type RunRow ¶
type RunRow struct {
ID, ParentRunID, ParentCallID, TraceID string
Agent, Provider, Model, ManifestHash, WeftVersion, Service string
SessionID, PublicID string
Turn int
Playground bool
ExperimentID, ForkedFrom string
Meta map[string]string
Started time.Time
Finished *time.Time
LastSeen time.Time
Status Status
Err string
Steps, Pending int
StopReason string
Usage core.Usage
EventCount, MessageCount int64
DeltaCount int64 // deltas are counted, never stored (Q4)
// ADR 0028 §10: run_start's weft.instructions.hash ("" on a run
// written before the request record — RequestsHole reads the
// table), request index 0's weft.catalog.hash ("" when it offered
// no tools) and the request records' high-water mark (max
// weft.request.index + 1). The API names them instructions_hash,
// catalog_hash and request_count.
InstructionsHash, CatalogHash string
RequestCount int64
}
RunRow is one run in a list or detail: the identity chain, timing, derived status and the denormalised usage and counts the list view reads without a second query.
func (RunRow) RequestsHole ¶ added in v0.10.0
RequestsHole is ADR 0028 §10's reading table over a run row: HoleNotRecorded when InstructionsHash is "" and no request record exists (written before the request record existed: no request, prompt or tools records), "" otherwise — with RequestCount 0 the run made no model call, above 0 its requests are recorded. A row with request records but no instructions hash lost the batch that carried run_start (or has not received it yet): its requests are recorded, and a prompt or catalog they name but the store lacks is a gap (ExplainMissing), never not_recorded — "upgrade weft" would be the wrong fix for a run this weft wrote.
type Selector ¶
type Selector struct{ RunID, SessionID, PublicID, Agent string }
Selector scopes a subscription: exactly one field set. A run-scoped subscription follows one run; a session- or public-id-scoped one spans the many runs of one conversation, which is why the cursor is hub-wide rather than per run.
type SessionDetail ¶
type SessionDetail struct {
SessionRow
Runs []RunRow
}
SessionDetail is one session with its turns — all of them: top-level runs by Turn, then Started; experiments excluded (they hang off runs via ForkedFrom).
type SessionPage ¶
type SessionPage struct {
Sessions []SessionRow
Total int
NextBefore *time.Time
NextBeforeID string
}
SessionPage is one Sessions result, paged as RunPage is: newest LastSeen first (ties by ID, descending), NextBefore/NextBeforeID are the next SessionQuery.Before/BeforeID, sessions sharing the last row's LastSeen ride along up to MaxTies, and Total counts the whole match.
type SessionQuery ¶
type SessionQuery struct {
Agent, PublicID string
Before time.Time // cursor on LastSeen; zero = newest
BeforeID string // with Before: the (LastSeen, ID) pair, as RunQuery.BeforeID
Limit int // 0 = 50, max 500
}
SessionQuery selects sessions. The zero value lists them newest activity first, 50 at a time.
type SessionRow ¶
type SessionRow struct {
ID, PublicID, Agent string
Turns int
FirstSeen, LastSeen time.Time
Status Status // the newest turn's
Usage core.Usage
}
SessionRow is one session (a GROUP BY over its top-level, non-playground runs).
type Span ¶
type Span struct {
TraceID, SpanID, ParentSpanID string // lowercase hex
Name string
Kind int // OTLP SpanKind
Start, End time.Time
StatusCode int // 0 unset, 1 ok, 2 error
StatusMessage string
Service string // resource service.name
Attrs map[string]any // string, bool, int64, float64, []any
Resource map[string]any
Events []SpanEvent // exceptions etc.
}
Span is one finished span, OTLP-shaped.
func FromOTLPTraces ¶
func FromOTLPTraces(req *coltracepb.ExportTraceServiceRequest) []Span
FromOTLPTraces decodes an OTLP/HTTP trace export request into the model. The receiver (studio/ingest) and a stock collector's dump produce the same rows the local sink does; non-weft spans pass through unchanged — a polyglot trace is a first-class citizen.
type Status ¶
type Status string
Status is a run's derived state. running, succeeded and failed are what the data says; interrupted is what a reader concludes from a stale last-seen (DeriveStatus) — never stored, so a crash leaves evidence and a live run in another process is not mistaken for a corpse (the store's rule, moved to last-seen).
func DeriveStatus ¶
DeriveStatus applies the four-row table every backend reads through:
the invoke_agent span ended with status error → failed a run_finish record exists (and no error span) → succeeded neither, and now - LastSeen ≤ InterruptedAfter → running neither, and older → interrupted
spanFailed is the invoke_agent span's error status; runFinished is the run_finish record's presence. LastSeen is the newest record or span time for the run, heartbeats included. failed outranks succeeded: a run whose span errored failed, whatever records raced behind it. Pending > 0 on a succeeded run means parked at an approval, not stuck.
type StepMessages ¶ added in v0.13.0
type StepMessages struct {
Step int
Messages []core.Message
View *Compaction
Derived bool
}
StepMessages is the messages one step's model call carried — what the model saw at that step (ADR 0028 §8, ADR 0029): the replay prefix for from_step Step, the "compacted messages" a fixture keys on. Step is the step asked for. View is the run-scope compaction view the step's request named (its messages_ref.index), nil when the request carried the plain transcript; Messages is then the growth records up to the view's index with its range replaced, else the growth records up to the request's own index. Derived is true when no request record placed the messages — a run written before ADR 0028, or a request whose messages_ref a content-off chain stripped — and the from_step cut rule did instead: the transcript before step Step's assistant message (no view can apply there: nothing names one).
func AssembleStep ¶ added in v0.13.0
func AssembleStep(batches []TranscriptBatch, requests []RequestRecord, compactions []Compaction, step int) (StepMessages, error)
AssembleStep is MessagesAsOf over records already read: the run's growth batches (TranscriptBatches), its request records (any steps; the answering — last — attempt of step is used) and its compactions. A reader assembling several steps of one run decodes the batches once with NewAssembler instead.
func MessagesAsOf ¶ added in v0.13.0
MessagesAsOf answers the messages step step of run runID's model call carried (StepMessages), read through TranscriptBatches, Requests and Compactions — one assembly (AssembleStep) for every backend and every reader: Studio's transcript route with ?step=, weft/runtime's replay prefix, the wefttest fixtures. ErrNotFound for an unknown run and for a step the run never reached (no request record and no assistant message of that step); an error wrapping ErrStepMessages when the records do not rebuild what the request names.
A session-scope compaction marker is never applied: the run's input record already holds the compacted context literally. A run-scope view applies only to the request that names it, never to a later step's.
Example ¶
ADR 0028 §8's worked example, read back as the model saw it: growth records 0 (the input, u1), 1 (a1) and 2 (t1), then step 1's PrepareStep sent [u1, s] — the core recorded view 3 replacing seqs [1, 3) with s, and step 1's request names it. MessagesAsOf answers each step's messages: step 0 the plain transcript, step 1 the view applied (the replay prefix for from_step 1, ADR 0029).
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/weftgo/weft/obsdb"
"github.com/weftgo/weft/obsdb/sqlite"
)
func main() {
db, err := sqlite.Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer func() { _ = db.Close() }()
ctx := context.Background()
at := time.Unix(0, 1790845923120000000).UTC()
text := func(role, t string) string {
return `{"role":"` + role + `","content":[{"type":"text","text":"` + t + `"}]}`
}
msgs := func(index, step int64, body string, extra map[string]any) obsdb.Record {
attrs := map[string]any{"weft.record": "messages", "weft.run.id": "run_1",
"weft.messages.index": index, "weft.step.index": step}
for k, v := range extra {
attrs[k] = v
}
return obsdb.Record{Time: at, EventName: "weft.messages", Body: body, Attrs: attrs}
}
request := func(index, step int64, ref int64, count int) obsdb.Record {
return obsdb.Record{Time: at, EventName: "weft.request",
Body: fmt.Sprintf(`{"step":%d,"attempt":1,"messages_ref":{"index":%d,"count":%d},"tools":{"catalog_hash":"","names":[]},"params":{},"model":{"name":"m"}}`, step, ref, count),
Attrs: map[string]any{"weft.record": "request", "weft.run.id": "run_1",
"weft.request.index": index, "weft.step.index": step, "weft.attempt.index": int64(1)}}
}
err = db.Write(ctx, obsdb.Batch{Records: []obsdb.Record{
{Time: at, EventName: "weft.event", Body: `{"type":"run_start","id":"run_1"}`,
Attrs: map[string]any{"weft.record": "event", "weft.run.id": "run_1", "weft.event.type": "run_start", "weft.event.pos": int64(0)}},
msgs(0, 0, "["+text("user", "u1")+"]", map[string]any{"weft.messages.input": true}),
request(0, 0, 0, 1),
msgs(1, 0, `[{"role":"assistant","content":[{"type":"tool_call","id":"c1","name":"lookup","args":{}}]}]`, nil),
msgs(2, 0, `[{"role":"tool","content":[{"type":"tool_result","call_id":"c1","name":"lookup","content":"t1"}]}]`, nil),
msgs(3, 1, "["+text("user", "s")+"]", map[string]any{"weft.messages.reason": "compacted",
"weft.messages.from_seq": int64(1), "weft.messages.to_seq": int64(3),
"weft.compaction.scope": "run", "weft.compaction.hash": "h1"}),
request(1, 1, 3, 2),
}})
if err != nil {
log.Fatal(err)
}
for _, step := range []int{0, 1} {
sm, err := obsdb.MessagesAsOf(ctx, db, "run_1", step)
if err != nil {
log.Fatal(err)
}
var roles []string
for _, m := range sm.Messages {
roles = append(roles, string(m.Role))
}
fmt.Println(step, roles, sm.View != nil)
}
_, err = obsdb.MessagesAsOf(ctx, db, "run_1", 5)
fmt.Println(errors.Is(err, obsdb.ErrNotFound))
}
Output: 0 [user] false 1 [user user] true true
type StepMessagesError ¶ added in v0.13.0
StepMessagesError is MessagesAsOf's and AssembleStep's refusal, with the hole that explains it (ADR 0028 §11's table): HoleGap when a record the step's request names is missing or does not fit — a growth record below its index, the growth record its plain ref names, a view that does not fit or was never written (a rewritten request with no view index), a body that is not messages — and HoleStripped when the run stored no messages at all (content capture off, or a content-off chain). errors.Is(err, ErrStepMessages) holds; a reader badges Hole and never guesses.
func (*StepMessagesError) Error ¶ added in v0.13.0
func (e *StepMessagesError) Error() string
func (*StepMessagesError) Is ¶ added in v0.13.0
func (e *StepMessagesError) Is(target error) bool
Is makes a StepMessagesError an ErrStepMessages.
type StoredRecord ¶ added in v0.10.0
type StoredRecord struct {
Index int64
Step int
Time time.Time
Body []byte
Content string
TruncatedBytes int64
SystemHash string
CatalogHash string
}
StoredRecord is one request, prompt or tools record as a backend stored it: its index, stored step (-1 when absent), time and body, and the four attributes the readers need — weft.content (the mark), weft.content.truncated_bytes, weft.system.hash and weft.catalog.hash. A backend fills it from its columns (or its attribute column) and builds the read type through RequestRecordOf, PromptRecordOf or ToolsRecordOf, so every backend parses alike.
type ToolEntry ¶ added in v0.10.0
type ToolEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Schema json.RawMessage `json:"schema"`
TimeoutMS int64 `json:"timeout_ms"`
Approval bool `json:"approval"`
Replay string `json:"replay"`
MaxResultBytes int `json:"max_result_bytes"`
Sequential bool `json:"sequential"`
Source string `json:"source"`
}
ToolEntry is one tool of a catalog: the model-visible triple and the policy chips (ADR 0028 §5).
type ToolsRecord ¶ added in v0.10.0
type ToolsRecord struct {
Index int64
Time time.Time
Hash string
Tools []ToolEntry
Content Hole
TruncatedBytes int64
}
ToolsRecord is one stored tools record (ADR 0028 §5): the catalog of one distinct catalog hash, tools in name order. Content is HoleTruncated when a destination's cap dropped entries from the end (TruncatedBytes > 0), HoleDerived when the body did not parse (a malformed producer; Hash is then the weft.catalog.hash attribute). Hash is over the whole catalog.
func ToolsRecordOf ¶ added in v0.10.0
func ToolsRecordOf(r StoredRecord) ToolsRecord
ToolsRecordOf builds a ToolsRecord from a stored tools record. Content is HoleTruncated when a cap dropped entries; a body that does not parse (a malformed producer) reads HoleDerived, with no tools and the hash of the record's weft.catalog.hash attribute.
type TranscriptBatch ¶ added in v0.10.0
type TranscriptBatch struct {
Index int64
Step int
Input bool
InputDerived bool // Input was inferred by the backend, not read from the record
Messages json.RawMessage
}
TranscriptBatch is one messages record as stored (ADR 0028 §8): readers place a batch by its Step, never by its neighbours. Step is -1 when the record carried no weft.step.index — a ClickHouse row written before migration 0004, or a producer that never stamped it; a reader that places such a batch anyway infers the step and says so (the derived badge, ADR 0028 §11). Input is the record's weft.messages.input: the input record — on a partial resume a prefix of what the run was fed, ending at the last assistant message with tool calls; the tail (the rebuilt tool message and what follows it) is the next batch, at step 0, not flagged. A backend that did not store the flag for a row (ClickHouse rows written before weft_records.Input existed) infers Input on index 0 — true unless the body is a lone assistant message, which is step 0 of a run fed no messages (the core writes the input record first, and none for an empty input) — and sets InputDerived on that batch so readers badge it HoleDerived.
func DedupBatches ¶ added in v0.10.0
func DedupBatches(batches []TranscriptBatch) []TranscriptBatch
DedupBatches runs DedupTranscript over the batches' bodies; the number of batches and their stored fields never change.
type Weft ¶
type Weft struct {
RunID, ParentRunID, ParentCallID string
SessionID, PublicID string
Turn int
Agent string
Record string // event | delta | messages | heartbeat | "" (non-weft)
EventType string
Pos int64 // event pos, messages index, delta pos, or request/prompt/tools index
Step int // -1 when absent
ToolSeq int64 // -1 when absent
Playground bool
ExperimentID, ForkedFrom string
// Reason is a messages record's weft.messages.reason: "" for
// transcript growth, "compacted" for a compaction view (ADR 0028
// §8), which is never counted or concatenated as transcript.
Reason string
}
Weft is what obsdb derives from a span or record's attributes, stored as indexed columns. Zero values mean absent.
func DeriveRecord ¶
DeriveRecord returns the weft identity a log record carries. Pos is the event position for event records, the messages index for transcript records, the delta position for delta records, and the request, prompt or tools index for those kinds (ADR 0028) — the value the (run, kind, pos) key and the live lane's dedup need. A record with no weft.run.id is non-weft and lands in other_logs.
func DeriveSpan ¶
DeriveSpan returns the weft identity a span carries, from its attributes. A span with no weft.run.id is non-weft (a stock OTel application's span): every field reads absent.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package clickhouse — see doc.go for the package documentation.
|
Package clickhouse — see doc.go for the package documentation. |
|
internal
|
|
|
reqread
Package reqread holds what obsdb's two backends share to answer DB.Prompt, DB.Tools and DB.Catalogs over the records they read: the lookups by hash and the one-per-hash catalog list.
|
Package reqread holds what obsdb's two backends share to answer DB.Prompt, DB.Tools and DB.Catalogs over the records they read: the lookups by hash and the one-per-hash catalog list. |
|
Package obsdbtest is the shared conformance table for obsdb.DB backends — the executable form of S3.5's promises, the storetest pattern.
|
Package obsdbtest is the shared conformance table for obsdb.DB backends — the executable form of S3.5's promises, the storetest pattern. |
|
Package sqlite is obsdb's default backend: the observability schema in a single SQLite file on the CGO-free modernc.org/sqlite driver (thread/sqlite's choice).
|
Package sqlite is obsdb's default backend: the observability schema in a single SQLite file on the CGO-free modernc.org/sqlite driver (thread/sqlite's choice). |