Skip to main content
The module is github.com/Project-White-Rabbit/bitfab-go. It requires Go 1.25 or later. Runtime dependencies are github.com/google/uuid, go.opentelemetry.io/otel, go.opentelemetry.io/otel/trace, and go.opentelemetry.io/otel/sdk.

Framework Integrations

No framework-native adapters are shipped for Go yet. Instrument Go code manually with client.Span or client.Start. See the Go SDK guide for the walkthrough. The Frameworks overview page tracks framework coverage across every SDK.

Package Constants

Version is the value attached to SDK requests. The two byte limits are transport safeguards. The SDK enforces them automatically. They’re exported so a caller can validate capture sizes before tracing.

Constructor

NewClient

Option

WithServiceURL

WithEnabled

When enabled is false, Span still executes the callback. Start returns a no-op *ActiveSpan. No data is sent to the API.

WithAPIKey

It’s equivalent to the apiKey argument of NewClient. Use it to construct a client purely from options. Whichever one is set last wins.

WithStrict

Makes an unresolvable API key a fatal misconfiguration. NewClient panics instead of disabling tracing. It’s off by default. That way, a missing telemetry key never crashes the host app. Turn it on in standalone programs where an untraced run is a failure you want surfaced immediately.

Transport environment variables

Commit ref environment variables

CommitRef

The commit the traced code was running at, sent as commit_ref on every root trace completion. sha is the commit. branch is the checked-out branch, or nil when detached or unknown. dirty is true when the working tree had uncommitted or untracked changes, false when it was clean, and nil when the SDK could not tell, which is always the case when the ref came from environment variables rather than git. remote is the origin URL reduced to host/owner/repo with any credentials removed, so a CI checkout token never reaches the trace. root_sha is the repository’s first commit, so two checkouts of the same repository match even without a remote. Resolution order is BITFAB_COMMIT_SHA, then the deploy platform’s build variables, then git in the process’s working directory (see Commit ref environment variables). Environment resolution is synchronous and free. The git path runs once per process in a goroutine, with a two second timeout per command, so it never sits on the code path that ran the traced function. A trace that completes before it lands ships without a commit_ref, and a process with neither variables nor a repository never sends one. The result, including a negative one, is memoized for the life of the process. Set BITFAB_DISABLE_COMMIT_REF to opt the process out entirely. Nil pointer fields serialize as JSON null.

type Client

Opaque struct. Construct via NewClient.

Span lookup types

(c *Client) Span

Executes fn inside a traced span. fn’s return value is captured as the span output. Returns: fn’s (any, error) return values. Errors: Only fn’s own error. It’s captured on the span. It’s also returned to the caller. Tracing never fails the call. An unknown WithType value degrades to custom with a one-time warning, instead of returning an error. An internal instrumentation failure runs fn untraced instead of crashing the host.

(c *Client) Start

Start/End-style span for instrumenting existing functions. Always call defer span.End(). Returns a child context that carries the span. It also returns an *ActiveSpan for recording data. When enabled is false, it returns the original context instead. The returned *ActiveSpan is then a zero value whose methods are all no-ops. Spans exist only where you create them. Nesting between spans is automatic, but only among spans that already exist. Wrapping just the outermost function, for example, records a trace with a single node. See Instrumentation for more.

(c *Client) FlushTraces

Drains this client’s pending span deliveries, up to timeout. Returns false when an export failed or the deadline expired. Use it for a mid-run flush, such as before reading a span back with GetTraceSpan.

(c *Client) Close

Flushes pending spans. Then permanently shuts down this client’s OpenTelemetry worker. It’s idempotent. It returns false when an export failed or the deadline expired. A closed client no longer records spans. Go has no atexit. Always defer client.Close(...) in main().

(c *Client) GetFunction

(c *Client) GetTraceSpan

Fetches one persisted span without loading its trace. traceID is the canonical Bitfab trace ID. Set exactly one of the span’s Bitfab SpanLookup.ID or SpanLookup.Name. Name lookup defaults to the last match. Use FirstSpanOccurrence or SpanOccurrenceAt(index) to override it. A miss returns nil, nil.

Datasets

The request and response types are:
client.Datasets holds a *DatasetsClient for the authenticated organization. It exposes the same operations the Bitfab MCP tools expose to a coding agent.
  • Save is an upsert keyed on (TraceFunctionKey, Name). Created is true for a new dataset and false when an existing one was updated. An empty Description leaves an existing description untouched.
  • List scopes to params.TraceFunctionKey when set. The zero value lists every dataset in the organization.
  • Dataset carries ID, TraceFunctionKey, Name, Description (*string), TraceCount, Graders ([]DatasetGraderRef with ID and Name), CreatedAt, and UpdatedAt.
  • ListTraces returns DatasetTraceIDs{DatasetID, TraceIDs}. This is the same membership a replay selects with ReplayOptions.DatasetID.
  • AddTraces and AddGraders each accept 1 to 100 ids. They report partial acceptance instead of failing outright. An id outside the organization, or under another trace function, comes back in SkippedTraceIDs or SkippedGraderIDs. An id already present comes back in AlreadyPresentTraceIDs or AlreadyAssignedGraderIDs.
  • RemoveTraces never deletes a trace. It only removes its membership in the dataset. Ids that were not members come back in NotPresentTraceIDs. RemoveGraders reports NotAssignedGraderIDs the same way.
  • RerunGraders re-scores every trace in the dataset. RerunGradersOptions.GraderIDs defaults to every assigned grader. Passing an unassigned id fails the call. With the zero-value options, RerunGraders waits for the run to finish. It polls every PollInterval, which defaults to 1 second, up to Timeout, which defaults to 90 seconds. It returns the last Run state it saw. Set NoWait to return as soon as the run is queued instead. A request that matches an in-flight run joins it, reported as JoinedExisting. Cancelling ctx while waiting returns ctx.Err().
  • GraderRerun has Status (a GraderRerunStatus, one of GraderRerunPending, GraderRerunRunning, GraderRerunCompleted, GraderRerunErrored, with Terminal()), GraderIDs, Progress (*GraderRerunProgress while running), Result (*GraderRerunResult when completed), and Error.
  • A dataset id from another organization fails with a 404 status error.

Replay

(c *Client) Replay

Replay fetches historical traces for traceFunctionKey. It re-runs their recorded inputs through fn. It waits for every emitted trace to persist. Then it completes the resulting experiment. fn must be a non-nil function. It may take context.Context as its first parameter, followed by any number of JSON-decodable typed parameters. It may return zero or more values, plus an optional final error. A single non-error return becomes ReplayItem.Result. Multiple returns become []any instead. Replay catches a panic from fn. It stores the panic as that item’s TraceError. Every invocation is automatically wrapped in a root span under traceFunctionKey. A production function that uses Start and End receives the replay root’s context. As a result, its existing spans become children of that root.

type ReplayOptions

Limit defaults to 5. It accepts values from 1 through 5000. MaxConcurrency defaults to 10. TraceIDs accepts at most 100 IDs. When it’s present, it determines the item count on its own. Limit is then omitted from the start request. When CodeChangeFiles is nil, replay first looks for BITFAB_CODE_CHANGE_PATH. If that’s not set, it captures the rename-aware Git diff against trunk instead. An explicit slice always wins over the automatic capture. Set DisableCodeChangeCapture, or the BITFAB_DISABLE_CODE_CHANGE_CAPTURE environment variable, to opt out entirely. BITFAB_CODE_CHANGE_BASE overrides trunk detection. Mock defaults to MockMarked. MockNone and MockAll select the other two strategies. MockOverrides take precedence over overrides registered on the client. Both take precedence over the base strategy. Mock interception only applies to closure-style child Client.Span calls, because those calls own execution. Manual Start/End instrumentation cannot prevent caller-owned code from running. DatasetID and DatasetIDs are the same selector at two counts, so set one of them: one dataset on DatasetID, several on DatasetIDs. Several replays the union of their traces, deduped where they overlap, graded by the union of their graders, and attributes the experiment to every one of them, so it appears under each dataset’s experiments. A non-nil DBBranch enables a historical database branch. An empty DBBranchOptions uses the connected project’s defaults. Set MinCU, MaxCU, or WarmupSQL to tune provisioning instead. Resolution happens inside the bounded item workers.

Replay mock types

Resolve takes precedence over Value. A nil Value is a valid flat override. The first matching override wins. GetOriginalOutput fetches the matched historical output lazily. It memoizes that output per replay item.
Per-call overrides are evaluated before client-registered overrides. A mocked span upload carries mocked: true, mockTarget: "output", and a mockSource of recorded or override.

Bound replay functions

Go function values cannot carry decorator metadata. These APIs explicitly bind a callable to its declared key. That lets Client.Replay reject a mismatch before starting the experiment. Plain callables remain valid for handler-instrumented workflows with no declared Go root.

Historical database branch

Inside a replay item whose source trace has a resolvable snapshot, GetCurrentReplayBranch returns that item’s *ReplayBranch. Outside replay, or when the source trace has no resolvable snapshot, it returns nil instead. DatabaseURL() is the only accessor that exposes the connection string. Calling it also marks the trace’s db_snapshot_usage.accessed flag. JSON encoding and String() both omit the URL. The branch also exposes its Neon branch ID, environment key, expiration, snapshot timestamp, console URL, read-only flag, region, and source trace ID as direct fields. Any other field the server adds later comes through Extra. Resolution failures produce a *DBBranchReplayError on the item’s ReplayError. It preserves Code, OriginalTraceID, and Cause. ReplayItem.DBSnapshotRef and ReplayItem.DBBranchTimings expose the historical pin and the server-measured provisioning phases.

Replay input adaptation

The adapter runs before typed parameter decoding. Its returned slice is passed positionally to fn. That same slice is stored as ReplayItem.Input. An adapter error becomes the item’s ReplayError. It does not stop other items from running.

Replay results and errors

ReplayItem carries everything about one replayed trace, including:
  • Input (the adapted recorded input), Result, and OriginalOutput
  • A compatible Error message, plus the actual TraceError and ReplayError
  • The replay’s own DurationMS, plus the original trace’s duration, token, and model measurements
  • The replay’s own token usage
  • The original trace and span lineage
  • The new server TraceID
  • The two trace outlines
TraceOutline is the replayed trace’s span tree. OriginalTraceOutline is the original trace’s span tree. Neither carries inputs or outputs. Each span in an outline records its name, type, nesting, start order, duration, tokens, model, errors, and whether it was mocked. Both outlines are nil until the run completes, so they’re also nil in every OnItemFinish event. They’re nil against an older server that doesn’t build them, too. TraceOutline is nil for one more case, an item whose replay produced no trace at all. These outlines exist for grading. Comparing the two trees shows whether a replay reached its output by the same path as the original. Per-item function and setup errors stay on their items. A run-wide persistence or completion failure returns *ReplayError. Use errors.As and errors.Unwrap to inspect it without losing the partial items it collected.

Replay progress and serialization

OnItemStart fires when a worker begins an item. This happens before replay loads its inputs, prepares mocks, or resolves a database branch. Its Item field carries only the original trace and span lineage at that point, because the rest of ReplayItem doesn’t exist yet. OnItemFinish fires exactly once per item, as it finishes. Items finish in completion order, not input order. OnItemFinish carries the full ReplayItem. Both callbacks report running totals, Completed, Succeeded, Errored, and Total. Replay doesn’t know pass or fail at finish time, since verdicts are assigned later. The totals only split runs that completed without error from runs that errored. A panicking callback is recovered. It never crashes the run. ReportReplayProgress writes one @@bitfab:progress {json} line to stderr. Pass it from both callbacks:
OnItemFinish flushes the finished trace. When that trace’s delivery is acknowledged, OnItemFinish fills the event’s server TraceID directly from the successful ingestion response. If per-item delivery can’t be confirmed, TraceID stays nil in the event instead. The run-wide persistence barrier then falls back to the status endpoint before filling in the final result. Replay aggregates token usage only at completion. Because of that, token usage appears only in the final ReplayResult, not in progress events. A panic inside a callback is swallowed. If BITFAB_REPLAY_RESULT_PATH is set, a successful Replay writes the structured result JSON there with one trailing newline.

Payload serialization helpers

These helpers expose the SDK’s JSON round trip for applications that need to preflight a captured value. Normal tracing and replay do not require calling them. Standard interface methods such as Error, Unwrap, MarshalJSON, String, and Format are documented with their owning replay types, rather than as independent entry points. TraceState is exported. This lets current-span handles share state across package boundaries. It’s transport plumbing, though, not an application-facing API.

type SpanFunc

type SpanOption

WithName

Defaults to traceFunctionKey for Span, or the spanName arg for Start.

WithType

One of "llm", "agent", "function", "guardrail", "handoff", "custom". An unknown value warns once. It then degrades to "custom" without changing the user’s result. Defaults to "custom".

WithMockOnReplay

Marks a closure-style child Span for recorded-output substitution under MockMarked. It has no effect outside replay. A selected occurrence that is absent from the historical tree returns an error without executing the real callback.

WithMockOutputType

Decodes a recorded or overridden JSON output into T before a mocked closure-style Span returns it. Use it for structs, slices, and other concrete outputs that would otherwise arrive as JSON-shaped map[string]any or []any values. Primitive and deliberately dynamic outputs do not need it.

WithFunctionName

Recorded as span_data.function_name.

WithInput

One arg stored directly. Multiple args stored as a slice. Only relevant to Span. For Start, use ActiveSpan.SetInput.

WithCaptureWhen

CaptureWhenNested records the span only when ctx contains an active Bitfab parent span. Without a parent, Span runs the callback untraced. Start returns the original context with a no-op ActiveSpan instead. The default is CaptureWhenAlways. An unknown value warns once. It then falls back to that default.

type Function

Obtained via (*Client).GetFunction(key). Fluent wrapper that binds traceFunctionKey.

(f *Function) Span

(f *Function) Start

type ActiveSpan

Returned by Start. All methods are safe on nil, safe under recover(), and idempotent where noted.

(s *ActiveSpan) SetInput

One arg stored directly. Multiple args stored as a slice. No-op on nil receiver.

(s *ActiveSpan) SetOutput

(s *ActiveSpan) SetError

(s *ActiveSpan) AddContext

Appends the map as one entry on span_data.contexts. No-op when context == nil.

(s *ActiveSpan) SetPrompt

Overwrites span_data.prompt. No-op on empty string.

(s *ActiveSpan) End

Idempotent via sync.Once. Sends the span in a background goroutine. Any panic inside the send path is recovered. That keeps the host app from crashing.

Trace-Level API

type ContextEntry

type CurrentSpan

Returns the active span’s canonical Bitfab span and trace IDs. GetCurrentSpan returns nil outside a span. Both methods are safe on a nil receiver. They return an empty string in that case.

type CurrentTrace

Returned by GetCurrentTrace.

(ct *CurrentTrace) TraceID

Returns the canonical Bitfab trace ID. It’s safe on a nil receiver. It returns an empty string in that case.

(ct *CurrentTrace) SetSessionID

(ct *CurrentTrace) SetName

Sets the trace’s title in Bitfab. The title is a searchable and filterable field, stored in the trace’s name column. Unset, the trace is titled by its trace function key instead. Empty strings are ignored. It’s safe on a nil receiver.

(ct *CurrentTrace) SetMetadata

Shallow-merges with existing trace metadata. Later keys win.

(ct *CurrentTrace) AddContext

Appends. Accumulates across calls.

(ct *CurrentTrace) Drop

Flags the trace to be dropped. Once the flag is set, no spans that complete afterward are uploaded at all. The flag itself rides along on the trace’s completion payload. At completion, the server does the rest of the cleanup:
  • It scrubs any payloads that already raced out ahead of the flag, meaning the trace, its external trace, and any sibling spans
  • It deletes the archived S3 objects
  • It marks the trace dropped instead of completed, keeping only a skeleton audit row
Drop is safe on a nil receiver. That makes it a no-op, so calling GetCurrentTrace(ctx).Drop() outside a span does nothing. Drop never panics.

GetCurrentTrace

Returns nil when ctx carries no active span. Callers must nil-check.

Concurrency Model

  • Nested span context is carried on context.Context. It’s safe across goroutines that inherit that context
  • Goroutines that do not inherit the context will not see the parent span
  • Spans are queued on a private OpenTelemetry batching worker. Each client gets its own worker, started lazily on the first span it sends. Queueing itself never blocks. FlushTraces and Close are what drain the worker
  • Trace state is stored in a mutex-protected package-level map, keyed by traceID

Error Behavior Summary