telemetry

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 14 Imported by: 0

README

telemetry

Native Go port of Pi's callback telemetry runtime at eeac84ca92498ac18b6832754d01aef1d3c5f654. Production code uses only the Go standard library. There is no subprocess, Bun, Node, or TypeScript dependency.

Safe settled producer telemetry defines the separate typed bounded pull observer, provenance rules and frozen C4 schema/example. The private recorder/export contract below is preserved. Settled wall timestamps and monotonic durations are also available through InMemory.GetSpanTimings and private export Batch.SpanTimings, without changing Pi span JSON.

This module uses concrete Context, Span, and InMemory types. The zero Context is a no-op. NewInMemory() owns an isolated recorder; a span supplies the explicit context for its children. Callbacks run in the calling goroutine. StartSpan[T] preserves a typed return value and error; the StartSpan methods accept callbacks returning only an error. Panics retain their original value.

recorder := telemetry.NewInMemory()
answer, err := telemetry.StartSpan(recorder.Context,
    telemetry.SpanOptions{Name: "operation"},
    func(span *telemetry.Span) (string, error) {
        span.AddEvent("ready", telemetry.NewAttributes(telemetry.Property{Name: "cached", Value: false}))
        return "answer", nil
    })
spans := recorder.GetSpans()

GetSpanTimings() returns detached timing records for settled spans, keyed by SpanID, in start order. StartedAt is wall-clock time and Duration uses Go's monotonic clock. Active/no-op spans have no completed timing. Timing is separate from GetSpans() so Pi-compatible recorded JSON remains unchanged. telemetry/export.Batch.SpanTimings includes these per-span records; its Duration still measures the entire batch and must not be used as child latency. Native AI attempt events also expose bounded structural ai.failure_kind and boolean ai.output_committed, without parsing or publishing provider error text. These diagnostics do not change retries; committed output still fences replay. Native AI attempt events also expose numeric ai.attempt and ai.duration_ms attributes without requiring hosts to parse the private llm.trace body.

Attributes is an ordered object, constructed with NewAttributes(Property{...}, ...); use Get, Lookup, Set, Delete, Len and Entries instead of map indexing. Its zero value is empty. Copies share the input object, while recorder admission and snapshots copy the outer object and arrays. NewObject constructs shared nested objects with the same property operations. JSON decoding also creates these nested *Object values. Replacing a value keeps its original key position; deleting and re-adding a key moves it to the end. Numeric index keys enumerate first in numeric order. Entries returns a detached entry list whose nested values remain shared. All native host call sites use this ordered API.

NewArray(values...) supplies shared array identity. JSON decoding creates *Array values too. Get, Has, Set, Delete, Append, Pop, Len, SetLength and Keys operate on that same array even after growth or truncation. Missing elements differ from present Undefined values in Has/Keys; both read as Undefined and export as null. Values returns a detached dense slice with holes filled by Undefined. Recording and snapshotting spread-copy direct array attributes, including filling holes, while arrays nested inside another array or object remain shared. Sparse indexed storage avoids allocating all intervening slots during a distant Set or length growth. GetProperty, SetProperty, DeleteProperty and PropertyKeys expose indexed and named enumerable own data properties. Numeric indices enumerate first; other names retain insertion order. Named properties survive length changes and remain live on nested arrays and status fields. Direct attribute spread copies drop them; JSON excludes them, even when they contain cycles or functions. SetProperty defines an own property for __proto__, without invoking a prototype setter. Length remains exposed through Len/SetLength. Custom prototypes and accessors are not represented. An explicit JSONMethod stored under toJSON can replace the array during JSON export. Plain Go slices remain accepted and retain their Go types; use Array when shared growth/truncation must behave like JavaScript.

StartSpanFrom accepts an options reader on both Context and Span; the package-level generic helper also preserves a typed callback result. No-op contexts and settled parents skip reading. If a reader panics or supplies an unsupported Go payload, the callback still executes once with a no-op span and keeps its original result, error or panic. Failed admission consumes no span ID. Readers run outside the recorder lock; a parent that settles before reading finishes prevents child admission. Spans created by the reader retain their own IDs, parentage and settlement independently of that outer admission.

Recorded spans retain Pi's JSON field names, start order, explicit parent IDs, settlement order, attribute merging, ordered events, and explicit status precedence. Only the exact status "ok" normalizes to success and drops error metadata; all other readable names, including the Go zero value, normalize to "error" and preserve supplied error details. A status set before settlement suppresses automatic error inspection. Once inspection starts, its computed error status overwrites a status set reentrantly by the error reader, matching Pi's assignment order. Error inspection runs outside the recorder lock, so snapshots and mutations remain available. SetStatusFrom(func() SpanStatus) models a status read that can fail or reenter recording. No-op or settled spans do not invoke the reader. A reader panic leaves the outer status and explicit-status flag untouched; mutations made by the reader itself remain. Successful reads apply their result after those mutations. The reader runs outside the recorder lock. If another goroutine settles the span while a reader is blocked, the completed read cannot overwrite that settlement. Attribute objects and their outer arrays are copied. Mutations after settlement are inert; late child callbacks still execute with no-op telemetry. A child admitted before its parent settles can finish independently. The recorder supports concurrent Go callers. Callers must synchronize concurrent mutation of their shared input and snapshot maps and slices.

Attribute values distinguish null from absence: nil records JSON null, including when replacing an existing value. telemetry.Undefined omits an attribute and leaves an existing value unchanged during merges. Inside nested objects, undefined fields remain present in memory but disappear from JSON; undefined array elements export as null. This also preserves null attributes decoded from JSON. Callers previously using nil for omission must use telemetry.Undefined instead. Function values remain present in snapshots; JSON omits them from objects and exports them as null in arrays. Recording and export never invoke ordinary functions. An explicit JSONMethod stored as an own toJSON property is the exception at export: it receives the object/array and containing key, and its return value replaces that position. Recording, snapshotting and graph cloning never call it. Export propagates callback errors and panics, reads later property values after earlier hooks, and captures object keys/array length before visiting children. It does not call a returned object's hook again at the same position. StringifyValue preserves an undefined root as no bytes; Go's json.Marshaler adapter must instead return null. Go's outer encoding/json encoder may also apply HTML escaping to the returned bytes.

SetAttributesFrom and AddEventFrom accept attribute readers. Active spans read once; no-op and settled spans skip reading. A panic suppresses the outer operation while preserving events, status and attribute edits already made by the reader. Successful attribute merges use the attributes captured before the read, matching Pi's mergeAttributes evaluation order: a reentrant attribute update may be overwritten by the outer merge. Nested events remain before the outer event. Readers run without holding the recorder lock, and a span that settles before the reader returns ignores the pending operation.

Go adaptations: ordinary Go errors produce {name: "Error", message: err.Error()}; other panic values produce an error status without details. This includes panic(nil): Go's runtime wrapper is excluded from recorded error details while the original recovered panic is propagated to the caller. Unsupported Go attribute payloads (channels, arbitrary pointers/structs and maps with non-string keys) in JSON-visible positions are ignored atomically. Named array properties are not visited by array export or spread copying. These adaptations do not invoke user serialization methods. An explicit *ErrorDetails preserves a serialized failure's original name and message while retaining normal Go error identity; the recorder copies its fields.

Decoded status JSON follows Pi's property reads. A null status is ignored without setting explicit status; other primitive inputs are readable and normalize to error. Falsy error values are omitted, while truthy primitive/array values produce empty error details. Only exact "ok" skips error inspection entirely. ErrorDetails retains missing, null and non-string name/message fields from JSON without inventing empty strings or keeping unrelated properties. Native string edits override their decoded values; ordinary Go literals retain both string fields. NameValue and MessageValue expose the actual fields, including Undefined, null, nonfinite numbers and shared *Object/*Array values. SetNameValue and SetMessageValue replace a field on just that error details value, including explicit empty strings, null and Undefined. The field descriptors remain immutable across shallow status copies, but nested objects and arrays retain their references. Edits through those references affect other copies and settled recorder snapshots, matching Pi's shallow error-field copying. Callers must synchronize shared edits. JSON export retains the attribute value domain and only invokes explicitly registered JSONMethod hooks, never arbitrary field functions or custom Go serializers; cycles and unsupported Go field payloads in JSON-visible positions return an export error.

The shared internal/jsonjs normalizer preserves numeric index-key ordering, duplicate-key semantics, binary64 rounding/overflow and UTF-16 surrogate escapes for these decoded fields. AI partial parsing, declarations and proxy encoding use the same Go implementation through ai.StringifyJSON.

Attribute JSON follows Pi's number domain: Go integer/float widths share the numeric category, including within []any; byte slices serialize as number arrays. JSON export converts numbers to binary64, rounds integers beyond its exact range, emits nonfinite values as null, and emits negative zero as 0. The recorder and detached snapshots retain their original Go types and values. Export extracts primitives without invoking user-defined serialization methods.

Decoding an attribute object uses binary64 values too: valid numeric overflow becomes infinity in memory, and underflow retains signed zero. Nested objects and arrays use the same conversion; duplicate keys keep their final value. A successful decode replaces the object, while a failed decode leaves the previous object and its aliases untouched. Null decodes to the empty zero value. Non-object top-level inputs remain outside the typed Attributes boundary. The shared internal/jsonjs.DecodeJSON parser preserves lone UTF-16 surrogates as WTF-8 bytes inside Go strings, keeping distinct string values and map keys separate. Valid surrogate pairs use ordinary UTF-8. Attribute export restores the original surrogate escapes, including in nested objects and arrays. Preserve these strings as opaque values or serialize them through Attributes; ordinary Go rune iteration and encoding/json on a plain string do not preserve lone surrogates. Span options and recorded span/event names use the same UTF-16 decoding and export rules. Ten pinned-source cases verify names through admission, active and settled snapshots, and importing serialized records again. Ordered attributes and decoded nested objects preserve source key insertion order. Plain Go maps remain accepted as nested values, but their insertion order cannot be recovered; those values export deterministically. Use NewObject or decoded objects when order matters.

SchemaDefinition and its span/event/attribute definitions preserve the serializable metadata, including enum/example arrays, sensitivity, cardinality, parent vocabularies, and explicit empty events. DefineSchema returns the same pointer. CreateTypedSpanStarter binds a context; its StartSpan callback receives the span and a starter bound to that span for children. StartTypedSpan preserves a generic result. Schema values are never inspected at runtime: duplicate or unknown names do not introduce runtime validation absent from Pi. Go does not infer literal attribute types from a schema value. TypeScript's schema-derived overloads/exact attribute constraints have no direct Go equivalent; the Go API exposes metadata and runtime behavior, not those compile-time checks.

Other backends use NewContextWithCallbacks with ContextCallbacks and NewSpan with SpanCallbacks. These concrete callbacks provide the same explicit-parent extension point without an interface hierarchy. NewContext still binds an eager-only StartSpan callback; use NewContextWithCallbacks to additionally bind StartSpanFrom. Missing context operations run the callback with no-op telemetry and never evaluate a deferred reader. The backend owns callback admission, settlement, passive recording, and concurrency; the constructors do not hide backend failures or change results. Backends can forward SpanCallbacks.SetStatusFrom, SetAttributesFrom and AddEventFrom to preserve deferred read admission and passivity; opaque contexts leave absent callbacks inert. Supplied status callbacks receive explicit null status inputs and retain their own failure behavior; only the in-memory recorder ignores unreadable null status. Thirty-two pinned-source cases compare direct and typed backend delegation, nested/retained child starters, callback results and thrown-value identity. An opaque span can use an in-memory root context for its children without owning a recorded parent span. Its missing mutation callbacks remain inert, including deferred readers; creating children still records through that context. Six Go regressions cover this boundary for events, attributes and status. telemetry/testing.CreateAdapterConformance supplies ten runner-independent cases. Each case creates and closes its own AdapterFixture, checks normalized snapshots, and returns an error on failure. Run the suite for any new backend.

Verification and remaining work

The pi-tojson.json fixture contains 128 pinned-source placement/return cases and 13 source-runtime mutation cases. These verify export-only hook execution, receiver identity, containing keys, array spread behavior, root/property omission, replacement values, errors, cycles, later-field mutations, and captured array length. Native tests also cover map mutation during cycle detection, passive cloning, and Go error/panic identity. These tests run entirely in Go.

go test -race ./telemetry/... runs without a JavaScript runtime. Thirty-nine recorded fixtures were generated by the unchanged Pi implementation, covering nested parentage, active snapshots, settlement, errors, explicit status, attributes, events, late child callbacks, and reentrant/unreadable error inspection. Additional Go tests cover result/error/panic identity, detached snapshots, passive error inspection, and concurrent callers. Another 24 source-generated cases verify thrown null, booleans, numbers, strings, arrays, plain error-shaped objects and named errors, with automatic or explicit status. These compare settled snapshots and check that recording preserves the original failure, including map/slice/error identity in Go. Seven additional schema/starter oracle scenarios check metadata round trips, cross-schema children, root reuse, late children, no-op execution and passive schema inputs. The Go conformance suite runs all nine mapped source cases plus a supplemental normalization case against both the in-memory recorder and a backend composed entirely through public callbacks. The mapped names are checked against the original nine-case export. Throwing options, attribute, event and status readers model failed property access; readable unknown status names remain a separate case. Inertness checks also verify that settled spans do not invoke readers. Forty-eight additional original-source getter cases cover active/no-op/settled spans, ordinary/null throws, reentrant status mutations and snapshot reads, with callback success/failure. Each runs against native and callback-forwarded Go contexts. A gated Go concurrency check verifies that a blocked reader neither holds the recorder lock nor changes a span that settled meanwhile. This is an explicit Go callback boundary, not support for arbitrary JavaScript proxies. Sixty-four original-source attribute getter cases cover successful/failed reads, null/undefined values, nested attribute/event/status writes and snapshots across active/no-op/settled spans. Both native and callback-forwarded contexts run every case. Gated Go checks cover concurrent settlement; an additional Go-only case checks unsupported payload rejection after reentrant writes. Sixty-four more original-source option getter cases cover failures at name, attributes and attribute-value reads, changes made during reading, reader-created spans, root/child/no-op/late admission, and callback result/error identity. Native and callback-forwarded contexts each run every case. Go checks also cover callback panic identity, nil readers, concurrent parent settlement and opaque backends without deferred admission. The conformance suite now uses throwing readers for all unreadable-input cases. These are explicit callback boundaries, not a JavaScript proxy implementation. Go errors/goroutines adapt failures and promise scheduling. Failure-path tests verify adapter rejection and fixture cleanup. Twenty-four additional span fixtures compare empty, missing, unknown and case-sensitive status names, dropped/retained error details, success, callback failures and suppression of error inspection against the actual source. Another 69 source-generated attribute cases compare active/settled JSON, including byte arrays, mixed numeric widths, nonfinite values, signed zero, large integers, and merge/event behavior. Comparisons retain JSON number text so float decoding cannot conceal an integer-rounding difference. Go tests cover passive export, preserved snapshots, and atomic rejection of unsupported Go values. Sixty additional source-generated cases parse external attribute JSON before span admission, merging and event recording. They compare active/settled JSON and the exact binary64 bits of decoded and recorded numbers, covering overflow, signed underflow, subnormal rounding, nested values, duplicate keys and special property names. Go checks also cover reused receivers, atomic rejection, nested options/event decoding and importing exported nulls into recorded spans. Another 60 source cases exercise Unicode values and property names during span admission, merging and event recording. Separate memory and JSON projections keep surrogate escapes as text so the fixture decoder cannot hide replacement or key collisions. They cover lone/pair/reversed surrogates, distinct surrogate keys, duplicate escaped keys, replacement characters, nested arrays/objects and control characters. The shared decoder/string exporter also matches source-runtime hashes for all 65,536 single UTF-16 code units and 1,048,576 valid surrogate pairs. Another 64 source cases compare the exact complete span JSON before/after mutations and at settlement, through both decoding and native constructors. They cover overwrite position, delete/reinsert, numeric index keys, duplicate JSON keys, prototype-related keys, nested shared edits, detached snapshot edits, undefined merges and reentrant attribute readers. Go checks cover object aliases, cyclic ordered graphs and passive export after settlement. Another 112 source cases compare complete JSON, array lengths, own index keys, undefined/null/hole values and reference identity across direct, object-nested, array-nested and repeated references. Each runs through both native construction and decoded arrays. Input/snapshot edits cover append, distant assignment, deletion, length growth/truncation and pop. Go checks cover cyclic arrays, passive function export, unsupported payload rejection, detached dense views and sparse storage at JavaScript's maximum array index. Another 240 original-source cases compare named properties on direct, nested, shared and status arrays, including input/snapshot edits before and after settlement. They compare complete JSON, property order and values, reference identity, constructor/prototype-related names, undefined values and named cycles or functions. Seven source-runtime graph-clone cases verify that detached copies preserve sparse membership, named fields, shared children and cycles. A native provider-stream snapshot test verifies the same graph preservation and producer isolation at the AI observation boundary. Native checks also cover the maximum index versus an ordinary numeric-looking name and ignored opaque Go payloads. The recorder does not enforce homogeneous arrays or reject readable nested objects merely because they lie outside the TypeScript attribute declaration. Twenty-four additional value cases cover mixed arrays, nested arrays/objects, null elements, nested special keys, merges, and recursive number conversion. Four graph fixtures check shallow copying and shared nested identity through input/snapshot edits. Cyclic graphs remain recordable; JSON export returns an error until the cycle is removed. Its diagnostic uses Go wording. No export calls user-defined serialization methods. Nested object/array edits can affect later snapshots, including settled spans, matching Pi's shallow-copy behavior; this is distinct from late Span mutation methods, which remain inert. Attribute copying also matches Pi's plain-object behavior: __proto__ is not recorded as an own attribute; constructor, toString and prototype remain ordinary attribute names. Source fixtures cover admission, updates and events. Eleven of the attribute cases cover null versus undefined at admission, during merges and in nested objects/arrays. The original span-action fixtures now encode undefined attributes explicitly instead of using null as a test-only marker. Eight cases cover function values at admission, merging, events and nested positions; memory inspection verifies that JSON omission does not discard them. Additional Go checks verify own-property presence, JSON round trips, marker retention in snapshots and shared nested edits after settlement. Another 228 original-source cases verify status JSON with no prior status, explicit success/error, and callback success/failure. They compare active/settled spans, complete serialized status JSON and individual error property encodings. Cases cover integer rounding, overflow, negative zero, nested key order, name/message order and distinct UTF-16 surrogates. Go checks verify detached status copies, native edits, JSON round trips and atomic rejection of malformed JSON; malformed JSON diagnostic wording remains Go's. Another 48 source cases compare complete status JSON and field reference identity for explicit status and automatically inspected failures. Distinct/shared objects and arrays are edited through the input or retained snapshot, replaced at the outer error field, or mutated after settlement. They verify that field replacement stays detached while nested edits remain shared, and automatic failures retain the original error identity. Go checks cover explicit field absence/null/empty strings, live overflow and signed zero, cycles and passive function export.

Run go test -race ./telemetry/... against the recorded source fixtures. The TypeScript reference and generators have been removed. Source provenance is retained in third_party/pi/UPSTREAM.json; the original MIT notice is in LICENSE.pi. The native host records request spans and settled trace packets through this package and is the server default.

The ordered value containers and JSON codec are shared with the native agent engine through internal/jsonjs. Telemetry exports concrete aliases; recorder-specific attribute admission, outer-array copying and status handling stay in this package. Serializing an input object preserves its own properties; the recorder applies its attribute filtering when accepting that input.

Documentation

Overview

Package telemetry ports Pi's explicit-parent, callback-scoped telemetry to Go. The zero Context is inert; NewInMemory records span snapshots. It has no dependency on a JavaScript runtime or on the application.

Index

Constants

View Source
const SafeAccountingSchema = "2ai.disjoint-tokens.v1"
View Source
const SafeDrainLimit = 256
View Source
const SafeIdentifierLimit = 128
View Source
const SafeQueueLimit = 2048
View Source
const SafeRecordLimit = 4096
View Source
const SafeSchemaVersion = "2ai.safe.v1"
View Source
const Undefined = jsonjs.Undefined

Undefined represents JavaScript's undefined in attribute payloads. Unlike nil (null), it does not overwrite an existing attribute during SetAttributes. Nested snapshots retain the marker; JSON omits object fields carrying it and emits null for array elements, matching JSON.stringify.

Variables

This section is empty.

Functions

func BindSafeOrigin added in v0.1.1

func BindSafeOrigin(ctx context.Context, origin SafeOrigin, category SafeCategory) context.Context

func DetachSafeExecution added in v0.1.1

func DetachSafeExecution(ctx context.Context, category SafeCategory) context.Context

func ReconcileSafe added in v0.1.1

func ReconcileSafe(ctx context.Context, accounting SafeAccounting)

ReconcileSafe reports aggregates without inventing physical attempt coverage.

func SafeFailureKind added in v0.1.1

func SafeFailureKind(s string) string

func StartSpan

func StartSpan[T any](c Context, options SpanOptions, callback func(*Span) (T, error)) (value T, err error)

StartSpan preserves a typed callback result as well as its error. The method on Context is the equivalent for callbacks that only return an error.

func StartSpanFrom

func StartSpanFrom[T any](c Context, read func() SpanOptions, callback func(*Span) (T, error)) (value T, err error)

StartSpanFrom preserves the typed callback value and error, as StartSpan does.

func StartTypedSpan

func StartTypedSpan[T any](starter SpanStarter, name string, attributes Attributes, callback func(*Span, SpanStarter) (T, error)) (value T, err error)

StartTypedSpan preserves a generic result without boxing it into an interface.

func StringifyValue

func StringifyValue(value any) ([]byte, error)

StringifyValue preserves JSON.stringify's undefined root as an empty result. Only explicit JSONMethod values stored as toJSON hooks can execute on export.

func WithContext

func WithContext(ctx context.Context, parent Context) context.Context

WithContext carries an explicit telemetry parent through Go host callbacks. It creates no span and has no global or goroutine-local state.

func WithSafeCategory added in v0.1.1

func WithSafeCategory(ctx context.Context, category SafeCategory) context.Context

func WithSafeObserver added in v0.1.1

func WithSafeObserver(ctx context.Context, observer *SafeObserver, execution SafeExecution) (context.Context, error)

WithSafeObserver accepts opaque host-approved correlation only. Runtime, tenant, config and agent identity remain the consuming host's responsibility.

func WithoutSafeOrigin added in v0.1.1

func WithoutSafeOrigin(ctx context.Context) context.Context

WithoutSafeOrigin retains the independent execution while acknowledging that coalesced durable work has no trustworthy single originating execution.

Types

type Array

type Array = jsonjs.Array

func NewArray

func NewArray(values ...any) *Array

type AttributeDefinition

type AttributeDefinition struct {
	Type          string          `json:"type"`
	Description   string          `json:"description"`
	Sensitive     *bool           `json:"sensitive,omitempty"`
	Cardinality   string          `json:"cardinality,omitempty"`
	Values        json.RawMessage `json:"values,omitempty"`
	ElementValues json.RawMessage `json:"elementValues,omitempty"`
	Examples      json.RawMessage `json:"examples,omitempty"`
}

AttributeDefinition is the serializable schema metadata from Pi. Values, ElementValues and Examples retain the JSON shape of their selected Type: scalar examples are arrays, array examples are arrays of arrays. Metadata describes instrumentation; it never causes runtime validation or redaction.

type Attributes

type Attributes = jsonjs.ObjectValue

Attributes records Pi's scalar and array attributes. Like Pi's runtime, it also accepts JSON-shaped objects, mixed arrays and nested values without imposing the TypeScript declaration as runtime validation. Attribute objects and outer arrays are copied; nested objects/arrays retain shared identity. NewAttributes and its Get/Lookup/Set/Delete/Entries methods retain property insertion order, with numeric index keys enumerated first. The zero value is empty. NewArray retains shared length and indexed mutations. Decoded arrays use *Array; direct array attributes are spread-copied, while nested arrays stay shared. A nil value represents null. Undefined omits an attribute, including during a merge; nested object fields omit Undefined and array elements export it as null. Function values remain in memory; JSON omits object fields and uses null for array elements without invoking the function. Numeric Go types share one category; []any may mix numeric widths. JSON uses binary64 numbers (including integer rounding), nonfinite values become null, and byte slices remain numeric arrays. In-memory snapshots keep Go types. JSON decoding creates binary64 values, retaining overflow and signed zero; a successful decode replaces the object, and a failed decode leaves it untouched. Decoded lone UTF-16 surrogates use WTF-8 bytes in strings and map keys. Attribute JSON export preserves them; ordinary Go rune iteration does not. Callers must synchronize access to shared input/snapshot maps and slices.

func NewAttributes

func NewAttributes(properties ...Property) Attributes

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context identifies an explicit parent. Copies share the same recording scope; there is no global recorder or implicit goroutine-local parent.

func FromContext

func FromContext(ctx context.Context) Context

func NewContext

func NewContext(start func(SpanOptions, func(*Span) error) error) Context

NewContext binds a backend's synchronous callback-scoped span operation. The backend owns settlement, passivity, and explicit child parentage. A nil function gives the same no-op behavior as the zero Context.

func NewContextWithCallbacks

func NewContextWithCallbacks(callbacks ContextCallbacks) Context

NewContextWithCallbacks binds a backend that supports deferred option reads. NewContext is the equivalent constructor for an eager-only backend.

func SafeContext added in v0.1.1

func SafeContext(private Context, ctx context.Context) Context

SafeContext composes safe live settlement with the existing private backend. It also works when that backend is inert or refuses late children.

func (Context) IsZero

func (c Context) IsZero() bool

IsZero reports whether this context has no recording backend.

func (Context) StartSpan

func (c Context) StartSpan(options SpanOptions, callback func(*Span) error) (err error)

StartSpan invokes callback exactly once in the calling goroutine. Returning an error or panicking settles the span without changing the error/panic value. Child work that should belong to this span must start before callback returns.

func (Context) StartSpanFrom

func (c Context) StartSpanFrom(read func() SpanOptions, callback func(*Span) error) error

StartSpanFrom reads options only when this context can admit a span. A read panic falls back to no-op telemetry, without allocating an ID or changing the callback's result. The callback still runs exactly once. Readers run without the recorder lock and may create other spans; those are admitted first. Concurrent parent settlement wins over a reader that has not yet returned.

type ContextCallbacks

type ContextCallbacks struct {
	StartSpan     func(SpanOptions, func(*Span) error) error
	StartSpanFrom func(func() SpanOptions, func(*Span) error) error
}

ContextCallbacks supplies eager and deferred span admission for a backend. Backends own settlement, read passivity and child parentage. A missing operation invokes the user's callback with a no-op span; an absent deferred operation also leaves its options reader uncalled.

type ErrorDetails

type ErrorDetails struct {
	Name    string `json:"name"`
	Message string `json:"message"`
	// contains filtered or unexported fields
}

func (*ErrorDetails) Error

func (e *ErrorDetails) Error() string

Error preserves an explicitly named failure when adapting serialized Pi errors. Ordinary Go errors continue to use the default name "Error".

func (ErrorDetails) MarshalJSON

func (e ErrorDetails) MarshalJSON() ([]byte, error)

func (ErrorDetails) MessageValue

func (e ErrorDetails) MessageValue() any

func (ErrorDetails) NameValue

func (e ErrorDetails) NameValue() any

NameValue and MessageValue expose the actual JS-shaped fields, including Undefined for absent fields and shared *Object/*Array values. Name/Message remain convenient string projections for ordinary Go errors.

func (*ErrorDetails) SetMessageValue

func (e *ErrorDetails) SetMessageValue(value any)

func (*ErrorDetails) SetNameValue

func (e *ErrorDetails) SetNameValue(value any)

SetNameValue and SetMessageValue replace only this error's field. They also distinguish explicit empty strings, null and Undefined from an unchanged Go zero value. Nested edits through the returned object/array stay shared.

func (*ErrorDetails) UnmarshalJSON

func (e *ErrorDetails) UnmarshalJSON(data []byte) error

type EventAttributeDefinition

type EventAttributeDefinition = StartAttributeDefinition

type EventDefinition

type EventDefinition struct {
	Description string                              `json:"description"`
	Attributes  map[string]EventAttributeDefinition `json:"attributes"`
}

type InMemory

type InMemory struct {
	Context
}

InMemory owns one isolated recording scope. Spans are retained for its lifetime, just as in Pi's reference adapter.

func NewInMemory

func NewInMemory() *InMemory

func (*InMemory) GetSpanTimings

func (m *InMemory) GetSpanTimings() []SpanTiming

GetSpanTimings returns settled span timings in start order. Active and no-op spans have no completed timing. Durations use Go's monotonic clock and each snapshot is independent of future recording.

func (*InMemory) GetSpans

func (m *InMemory) GetSpans() []RecordedSpan

GetSpans returns snapshots in start order, including active spans. Attribute maps and outer arrays are detached; nested objects/arrays remain shared, as with Pi's shallow copy. Callers must synchronize edits to shared payloads.

type JSONMethod

type JSONMethod = jsonjs.JSONMethod

type Object

type Object = jsonjs.Object

func NewObject

func NewObject(properties ...Property) *Object

type ParentDefinition

type ParentDefinition struct {
	Kind  string   `json:"kind"`
	Spans []string `json:"spans,omitempty"`
}

func (ParentDefinition) MarshalJSON

func (p ParentDefinition) MarshalJSON() ([]byte, error)

An explicit empty spans list is distinct from an absent list in the union.

type Property

type Property = jsonjs.Property

Property, Object and Array share the native JSON value implementation used by agent arguments. No JavaScript runtime or interface hierarchy is involved.

type RecordedEvent

type RecordedEvent struct {
	Name       string     `json:"name"`
	Attributes Attributes `json:"attributes"`
}

func (RecordedEvent) MarshalJSON

func (e RecordedEvent) MarshalJSON() ([]byte, error)

func (*RecordedEvent) UnmarshalJSON

func (e *RecordedEvent) UnmarshalJSON(raw []byte) error

type RecordedSpan

type RecordedSpan struct {
	ID          int             `json:"id"`
	ParentID    *int            `json:"parentId"`
	Name        string          `json:"name"`
	Attributes  Attributes      `json:"attributes"`
	Events      []RecordedEvent `json:"events"`
	Status      SpanStatus      `json:"status"`
	Settled     bool            `json:"settled"`
	EndSequence *int            `json:"endSequence,omitempty"`
}

func (RecordedSpan) MarshalJSON

func (s RecordedSpan) MarshalJSON() ([]byte, error)

func (*RecordedSpan) UnmarshalJSON

func (s *RecordedSpan) UnmarshalJSON(raw []byte) error

type SafeAccounting added in v0.1.1

type SafeAccounting struct {
	UsageObserved   bool        `json:"usage_observed"`
	PricingObserved bool        `json:"pricing_observed"`
	UsageSource     string      `json:"usage_source"`
	PricingSource   string      `json:"pricing_source"`
	PriceState      string      `json:"price_state"`
	Complete        bool        `json:"complete"`
	AttemptCoverage string      `json:"attempt_coverage"`
	Tokens          *SafeTokens `json:"tokens"`
	KnownCostUSD    *float64    `json:"known_cost_usd"`
}

SafeAccounting is evidence, not a billing claim. Unknown values are null. Only attempt_settled records are additive. Reconciliation is comparison-only.

func NormalizeSafeAccounting added in v0.1.1

func NormalizeSafeAccounting(a SafeAccounting) SafeAccounting

type SafeAggregate added in v0.1.1

type SafeAggregate struct {
	ReportedTokens  SafeTokens `json:"reported_tokens"`
	ReportedCostUSD float64    `json:"reported_cost_usd"`
	Additive        bool       `json:"additive"`
}

SafeAggregate preserves existing reported totals for comparison only. The producer cannot infer wire usage or known pricing from these host totals.

type SafeAttempt added in v0.1.1

type SafeAttempt struct {
	// contains filtered or unexported fields
}

func (*SafeAttempt) Finish added in v0.1.1

func (a *SafeAttempt) Finish(status, failureKind string, accounting SafeAccounting)

func (*SafeAttempt) FinishAdmission added in v0.1.1

func (a *SafeAttempt) FinishAdmission(status, failureKind string, accounting SafeAccounting, provider, model string, admitted bool)

FinishAdmission distinguishes an attempted open from an admitted producer. An admission failure remains an attempt with unavailable provider usage.

func (*SafeAttempt) FinishModel added in v0.1.1

func (a *SafeAttempt) FinishModel(status, failureKind string, accounting SafeAccounting, provider, model string)

FinishModel checks response selection against the immutable approved binding. An unapproved internal fallback is unavailable; response text is never copied into a label. Empty response selection retains the host candidate binding.

type SafeCall added in v0.1.1

type SafeCall struct {
	// contains filtered or unexported fields
}

NewSafeCall allocates one logical call identity, shared by every retry/rung.

func NewSafeCall added in v0.1.1

func NewSafeCall(ctx context.Context) SafeCall

func (SafeCall) StartAttempt added in v0.1.1

func (c SafeCall) StartAttempt(ordinal int, labels SafeLabels) *SafeAttempt

type SafeCategory added in v0.1.1

type SafeCategory string
const (
	CategoryMain       SafeCategory = "main"
	CategoryCompaction SafeCategory = "compaction"
	CategoryAdvisor    SafeCategory = "advisor"
	CategoryMemory     SafeCategory = "memory"
	CategoryChild      SafeCategory = "child"
)

type SafeExecution added in v0.1.1

type SafeExecution struct {
	ExecutionID, OriginExecutionID string
	Category                       SafeCategory
}

type SafeHealth added in v0.1.1

type SafeHealth struct {
	Capacity        int    `json:"capacity"`
	Queued          int    `json:"queued"`
	Admitted        uint64 `json:"admitted"`
	Overflow        uint64 `json:"overflow"`
	Oversize        uint64 `json:"oversize"`
	ClosedAdmission uint64 `json:"closed_admission"`
	Contended       uint64 `json:"contended"`
	Closed          bool   `json:"closed"`
	Complete        bool   `json:"complete"`
}

type SafeLabels added in v0.1.1

type SafeLabels struct {
	// contains filtered or unexported fields
}

SafeLabels can only be constructed by an explicit host approval. Approval must use configured public IDs, never provider response text or raw metadata. The zero value represents unavailable labels. Values are immutable.

func ApproveModelLabels added in v0.1.1

func ApproveModelLabels(provider, model string) (SafeLabels, error)

func ApproveToolLabel added in v0.1.1

func ApproveToolLabel(tool string) (SafeLabels, error)

type SafeObserver added in v0.1.1

type SafeObserver struct {
	// contains filtered or unexported fields
}

SafeObserver is a finite passive pull queue. It owns no goroutine and invokes no sink. The host drains, persists, retries and deduplicates detached records. Intake lifetime belongs to the host, independently of any root settlement.

func NewSafeObserver added in v0.1.1

func NewSafeObserver(options SafeObserverOptions) (*SafeObserver, error)

func (*SafeObserver) Close added in v0.1.1

func (o *SafeObserver) Close()

func (*SafeObserver) Drain added in v0.1.1

func (o *SafeObserver) Drain(limit int) []SafeRecord

Drain consumes at most min(limit, 256) records. A nonpositive limit consumes none. Host delivery retries retain the returned event IDs; there is no replay.

func (*SafeObserver) Health added in v0.1.1

func (o *SafeObserver) Health() SafeHealth

type SafeObserverOptions added in v0.1.1

type SafeObserverOptions struct{ Capacity, RecordBytes int }

type SafeOrigin added in v0.1.1

type SafeOrigin struct {
	// contains filtered or unexported fields
}

SafeOrigin carries only immutable safe observation metadata across host work queues. It carries no cancellation, private recorder or application context.

func CaptureSafeOrigin added in v0.1.1

func CaptureSafeOrigin(ctx context.Context) SafeOrigin

func (SafeOrigin) Merge added in v0.1.1

func (o SafeOrigin) Merge(other SafeOrigin) SafeOrigin

func (SafeOrigin) SameScope added in v0.1.1

func (o SafeOrigin) SameScope(other SafeOrigin) bool

SameScope compares immutable observation metadata, including its observer, execution, span and category. Equivalent context copies share a scope.

type SafeRecord added in v0.1.1

type SafeRecord struct {
	SchemaVersion     string         `json:"schema_version"`
	AccountingSchema  string         `json:"accounting_schema"`
	Kind              string         `json:"kind"`
	ProducerEventID   string         `json:"producer_event_id"`
	ExecutionID       string         `json:"execution_id"`
	OriginExecutionID *string        `json:"origin_execution_id"`
	SpanID            *string        `json:"span_id"`
	ParentSpanID      *string        `json:"parent_span_id"`
	ParentExecutionID *string        `json:"parent_execution_id"`
	CallID            *string        `json:"call_id"`
	AttemptID         *string        `json:"attempt_id"`
	AttemptOrdinal    int            `json:"attempt_ordinal"`
	ProducerAdmitted  bool           `json:"producer_admitted"`
	Category          SafeCategory   `json:"category"`
	SpanKind          string         `json:"span_kind"`
	ProviderID        *string        `json:"provider_id"`
	ModelID           *string        `json:"model_id"`
	ToolID            *string        `json:"tool_id"`
	LabelsState       string         `json:"labels_state"`
	StartedAt         time.Time      `json:"started_at"`
	SettledAt         time.Time      `json:"settled_at"`
	Duration          time.Duration  `json:"duration_ns"`
	Status            string         `json:"status"`
	FailureKind       string         `json:"failure_kind"`
	Accounting        SafeAccounting `json:"accounting"`
	Reconciliation    *SafeAggregate `json:"reconciliation"`
}

SafeRecord has no private attributes, errors, messages or serializer hooks. Every optional ID is null when unavailable. All records are detached values.

type SafeTokens added in v0.1.1

type SafeTokens struct {
	Input      float64 `json:"input"`
	Output     float64 `json:"output"`
	CacheRead  float64 `json:"cache_read"`
	CacheWrite float64 `json:"cache_write"`
}

type SchemaDefinition

type SchemaDefinition struct {
	Version float64                   `json:"version"`
	Spans   map[string]SpanDefinition `json:"spans"`
}

func DefineSchema

func DefineSchema(schema *SchemaDefinition) *SchemaDefinition

DefineSchema is Pi's identity helper. No cloning, normalization, validation, or schema access occurs; the same pointer is returned.

type Span

type Span struct {
	// contains filtered or unexported fields
}

Span is valid as a recording parent until its callback returns. Retaining it is safe: later mutations are inert, and later child callbacks still execute.

func NewSpan

func NewSpan(children Context, callbacks SpanCallbacks) *Span

NewSpan constructs a backend span with its explicit child context. It does not add lifecycle behavior; that belongs to the backend's start function.

func (*Span) AddEvent

func (s *Span) AddEvent(name string, attributes Attributes)

func (*Span) AddEventFrom

func (s *Span) AddEventFrom(name string, read func() Attributes)

AddEventFrom reads attributes only while recording is active. A read panic suppresses the outer event; nested events already emitted by the reader stay in order. No recorder lock is held while invoking the reader. An event is ignored if its span settles before the reader returns.

func (*Span) Context

func (s *Span) Context() Context

Context returns this span's explicit child context.

func (*Span) SetAttributes

func (s *Span) SetAttributes(attributes Attributes)

func (*Span) SetAttributesFrom

func (s *Span) SetAttributesFrom(read func() Attributes)

SetAttributesFrom defers reading an incoming attribute set until recording is active. Like Pi's property reads, a panic discards the outer update while retaining any reentrant mutations the reader already made. A successful read merges into the attributes captured before reading, so reentrant attribute updates are overwritten by that outer merge. Events and status are unaffected. Readers run without the recorder lock; concurrent settlement wins.

func (*Span) SetSafeOutcome added in v0.1.1

func (s *Span) SetSafeOutcome(status, failure string)

SetSafeOutcome binds only bounded structural status metadata. It never reads a private status/error or changes the legacy recorder's status precedence. Cancellation may accompany a returned error; that error is still preserved.

func (*Span) SetStatus

func (s *Span) SetStatus(status SpanStatus)

func (*Span) SetStatusFrom

func (s *Span) SetStatusFrom(read func() SpanStatus)

SetStatusFrom reads a status only when recording is active. It is the Go equivalent of Pi reading a status object with user-defined getters. A reader panic is passive: no outer status/explicit-status change is applied, while the reader's own reentrant mutations remain. Readers run without the recorder lock, so they may record events, set a nested status or inspect snapshots. Concurrent settlement wins over a reader that has not yet returned. Callback backends implement admission through SpanCallbacks.SetStatusFrom.

func (*Span) StartSpan

func (s *Span) StartSpan(options SpanOptions, callback func(*Span) error) error

func (*Span) StartSpanFrom

func (s *Span) StartSpanFrom(read func() SpanOptions, callback func(*Span) error) error

StartSpanFrom creates a child using deferred options and explicit parentage.

type SpanCallbacks

type SpanCallbacks struct {
	AddEvent      func(string, Attributes)
	SetAttributes func(Attributes)
	SetStatus     func(SpanStatus)
	// Deferred reads own admission, including skipping reads after settlement
	// and suppressing read failures. Nil retains the supplied context's default
	// behavior (inert for an opaque backend).
	SetStatusFrom     func(func() SpanStatus)
	SetAttributesFrom func(func() Attributes)
	AddEventFrom      func(string, func() Attributes)
}

SpanCallbacks are the mutation operations supplied by a telemetry backend. Provided callbacks override native recorder operations; opaque contexts leave missing operations inert. Backends must ignore mutations after settlement and suppress recording failures without changing the user's callback result.

type SpanDefinition

type SpanDefinition struct {
	Description     string                              `json:"description"`
	Parents         ParentDefinition                    `json:"parents"`
	StartAttributes map[string]StartAttributeDefinition `json:"startAttributes"`
	EndAttributes   map[string]AttributeDefinition      `json:"endAttributes"`
	Events          map[string]EventDefinition          `json:"events,omitempty"`
	Status          StatusDefinition                    `json:"status"`
}

func (SpanDefinition) MarshalJSON

func (s SpanDefinition) MarshalJSON() ([]byte, error)

type SpanOptions

type SpanOptions struct {
	Name       string     `json:"name"`
	Attributes Attributes `json:"attributes,omitzero"`
	SafeLabels SafeLabels `json:"-"`
}

func (SpanOptions) MarshalJSON

func (o SpanOptions) MarshalJSON() ([]byte, error)

func (*SpanOptions) UnmarshalJSON

func (o *SpanOptions) UnmarshalJSON(raw []byte) error

type SpanStarter

type SpanStarter struct {
	// contains filtered or unexported fields
}

SpanStarter binds one explicit parent context. Schema values are deliberately unread, as in createTypedSpanStarter. Go does not infer literal attribute types from schema values; use application-owned typed wrappers when needed. It must not introduce runtime validation absent from Pi's implementation.

func CreateTypedSpanStarter

func CreateTypedSpanStarter(parent Context, _ ...*SchemaDefinition) SpanStarter

func (SpanStarter) StartSpan

func (s SpanStarter) StartSpan(name string, attributes Attributes, callback func(*Span, SpanStarter) error) error

StartSpan supplies a child starter bound to the newly admitted span. Saving that starter does not extend the span's lifetime; late children remain no-op.

type SpanStatus

type SpanStatus struct {
	Status string        `json:"status"`
	Error  *ErrorDetails `json:"error,omitempty"`
	// contains filtered or unexported fields
}

func (SpanStatus) MarshalJSON

func (s SpanStatus) MarshalJSON() ([]byte, error)

func (*SpanStatus) UnmarshalJSON

func (s *SpanStatus) UnmarshalJSON(data []byte) error

UnmarshalJSON projects serialized status inputs using Pi's property reads. Non-object primitives have no status/error properties; null is unreadable and SetStatus ignores it. Truthy errors copy only their name/message properties.

type SpanTiming

type SpanTiming struct {
	SpanID    int           `json:"span_id"`
	StartedAt time.Time     `json:"started_at"`
	Duration  time.Duration `json:"duration_ns"`
}

SpanTiming is a detached timing for a settled span. It is kept separately from RecordedSpan so Pi-compatible records retain their original JSON shape.

type StartAttributeDefinition

type StartAttributeDefinition struct {
	AttributeDefinition
	Required bool `json:"required"`
}

type StatusDefinition

type StatusDefinition struct {
	Default   string `json:"default"`
	ErrorWhen string `json:"errorWhen"`
}

Directories

Path Synopsis
Package export delivers completed telemetry scopes to host-owned sinks.
Package export delivers completed telemetry scopes to host-owned sinks.
Package llm defines the export format for model-call telemetry.
Package llm defines the export format for model-call telemetry.
Package telemetrytest provides runner-independent conformance cases for Go telemetry adapters, ported from Pi's telemetry/testing entry point.
Package telemetrytest provides runner-independent conformance cases for Go telemetry adapters, ported from Pi's telemetry/testing entry point.

Jump to

Keyboard shortcuts

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