Framework-native adapters (LangGraph, OpenAI Agents, BAML, Claude Agent SDK) are not yet available for Go. See Frameworks overview for current coverage. Instrument Go code manually via
client.Span or client.Start.Installation
Quick Start
Coding Agent Prompt (Cursor, Claude Code)
Coding Agent Prompt (Cursor, Claude Code)
Copy this prompt into your coding agent (tested with Cursor and Claude Code using Sonnet 4.5):
Basic Configuration
Missing API key doesn’t crash. If the API key is missing, empty, or whitespace-only, the SDK automatically disables tracing and logs a warning. All instrumented functions still execute normally - no spans are sent, no errors are thrown. You don’t need any conditional logic around the API key.
WithAPIKey), the SDK reads BITFAB_API_KEY from the environment. Unlike the JS and Python SDKs, Go resolves the key eagerly in NewClient: clients are constructed explicitly (normally in main, after env has loaded), so there is no import-time construction-before-env trap to defer around. For standalone programs where a run that emits no traces should be a hard failure, use WithStrict:
Tracing
Give a step its own span when any of these is true:- It calls a model. Always. This is the span you iterate on, compare across experiments, and attach graders to.
- It reads external mutable state (DB query, HTTP
GET, object storage, vector search, cache). These are the spans you will want to mock on replay. - It writes external state (DB write, queue publish, email, charge, file write). Mark these to mock on replay so a replayed trace does not repeat the side effect.
- It transforms the model output (parsing, validation, ranking, formatting), so a quality regression points at the model or at your post-processing.
- It retries or loops, one span per attempt or iteration, so a trace shows how many attempts it really took.
Go replay defaults to
MockMarked: closure-style child spans tagged with WithMockOnReplay(true) reuse their recorded output. Untagged child code still runs, so mark or override unsafe side effects before replaying a workflow. Manual Start/End spans cannot skip caller-owned code; use closure-style Client.Span for mockable boundaries.Replay Historical Traces
Client.Replay fetches historical root inputs, decodes them into your current Go function’s parameter types, runs the items concurrently, and creates an experiment in Bitfab. Pass the same top-level function production uses. Replay accepts typed functions with an optional leading context.Context and an optional final error return.
Start/End, those spans nest beneath the replay root and retain the same key. The replay context is passed as the function’s leading context.Context, so continue passing the returned context through nested work.
ReplayOptions supports:
Limit- recent traces to replay (default5, maximum5000). Omitted from the request whenTraceIDsis set.TraceIDs- explicit historical trace IDs (maximum100). Passed alongsideDatasetIDorDatasetIDsthey pin which members of that selection replay, and the server rejects any ID none of those datasets contains.Name,ExperimentGroupID, andDatasetID/DatasetIDs- experiment organization and attribution.DatasetIDsruns against several datasets at once, replaying the union of their traces.GraderIDs- graders attached directly to this experiment.MaxConcurrency- bounded worker count (default10).CodeChangeDescriptionandCodeChangeFiles- explicit code-change context. When files are omitted, Go checksBITFAB_CODE_CHANGE_PATHand then captures the rename-aware working-tree diff against trunk.DisableCodeChangeCaptureopts one replay out.MockandMockOverrides- recorded-output strategy and selective substitutions for closure-style child spans.DBBranch- non-nil enables a trace-time database branch;MinCU,MaxCU, andWarmupSQLtune it.AdaptInputs- reshape historical positional inputs before type decoding.OnItemStartandOnItemFinish- lifecycle callbacks with running totals. The finish callback normally includes the new server trace ID after a per-item flush; the final result is the fallback when that delivery cannot be confirmed. Callback panics are isolated from the replay.
Mock child spans
Mark an expensive or unsafe closure-style child span once in production code:MockMarked strategy reuses that occurrence’s historical output during replay. Repeated same-name calls are matched in call order; the SDK uses strictly increasing microsecond timestamps so rapid sibling calls remain distinguishable. WithMockOutputType[T]() decodes recorded JSON into the concrete Go type the caller expects; it is unnecessary for primitive or deliberately dynamic any/map outputs. MockNone runs real child code unless an override matches it; MockAll substitutes every recorded child occurrence. A selected occurrence that is missing fails the item closed.
Overrides run before the base strategy, with per-call overrides before client-registered overrides:
GetOriginalOutput fetches lazily and is memoized per item. Mocked uploads record whether the output came from recorded history or an override.
Replay against trace-time database state
Every Go root trace now carries a no-I/O wall-clock snapshot reference. To request a historical branch, pass a non-nilDBBranch and read it inside the replayed function:
MaxConcurrency also bounds live branches. A requested branch that cannot be resolved fails that item instead of silently using the live database. The SDK reports whether DatabaseURL() was obtained, includes provisioning timings on the trace and replay item, and releases every branch after the item; the connection string is excluded from branch JSON and formatting.
Bound replay keys
Plain Go function values carry no tracing metadata, so explicit-key replay remains valid. When code wants a declared-key guard, bind the callable:Client.Replay under a different key fails before Bitfab selects any historical traces.
An item failure does not stop the batch. ReplayItem.TraceError retains an error or panic from the replayed function, ReplayItem.ReplayError retains setup/adaptation failures, and ReplayItem.Error is the compatible message. A persistence or finalization failure returns *bitfab.ReplayError, whose Items, TestRunID, TestRunURL, and Cause preserve the partial run.
Before finalizing, replay flushes the normal OpenTelemetry pipeline. Every carrier keeps a private delivery reference that never goes on the wire; successful ingestion acknowledges those references and returns each server TraceID. Replay finishes immediately when all carriers are acknowledged, and polls final trace status plus expected persisted span counts only when delivery is ambiguous. The final items include both the original trace lineage (OriginalTraceID, OriginalSpanID) and the new server TraceID, plus original duration/token/model measurements, replay duration, and replay token usage.
When BITFAB_REPLAY_RESULT_PATH is set, the SDK writes the same structured result JSON there automatically. This is how the Bitfab plugin captures results without parsing stdout.
Custom (Recommended)
Using Start/End to Instrument Existing Functions
The recommended way to add tracing to existing functions without restructuring them:
processOrder records one trace with two spans:
processOrder would record the same work as a single node. The nesting comes from passing the ctx that Start returns into the next step: hand a step the original ctx and its span becomes a separate root.
Startreturns an updatedcontext.Context(for nested span propagation) and anActiveSpandefer span.End()ensures the span is always completed and sentSetInput/SetOutput/SetErrorrecord data on the spanEndis idempotent - calling it multiple times is safe
Multi-File Projects
For projects with instrumented functions spread across multiple files, create a dedicated package that initializes the client and exposes a function handle. Import it wherever you need to instrument.ctx between instrumented functions.
Using client.Span (Closure Style)
Wrap inline code in a closure. Output is captured automatically from the return value. Use WithInput to record inputs:
Using GetFunction for a Static Trace Key
Bind a trace function key once, then create multiple spans without repeating it:
Automatic Nesting
Spans nest automatically when you passctx from the outer span callback:
bitfab.WithCaptureWhen(bitfab.CaptureWhenNested):
ctx, helper runs normally without creating a trace. Pass the child context from Span or Start to capture it as a nested span.
Span Options
Parameters:traceFunctionKey(required): Groups spans under a function key in BitfabWithName(name)(optional): Display name. Defaults to the trace function keyWithType(spanType)(optional): Span type. Defaults to"custom". A label only, used to organize and filter spans in the dashboard; it does not change how the span is traced, replayed, or evaluatedWithFunctionName(name)(optional): Override the function name in span dataWithInput(args...)(optional, closure style only): Record input data. A single arg is stored directly; multiple args as a sliceWithCaptureWhen(CaptureWhenNested)(optional): Capture only with an active parent span; otherwise run untraced. Defaults toCaptureWhenAlways; unknown values warn once and use that default
Span Context
Usespan.AddContext() on an ActiveSpan (Start/End style) to attach contextual key-value pairs at runtime - useful when context depends on computed values:
AddContext call pushes the entire map as one entry. Multiple calls accumulate entries:
Span Prompt
Usespan.SetPrompt() on an ActiveSpan (Start/End style) to set the prompt string on the current span. This is stored in span_data.prompt and is useful for capturing the exact prompt text sent to an LLM:
SetPrompt call wins — it overwrites any previously set prompt on the span. Calling SetPrompt outside a span context is a no-op (it never crashes).
Trace Context
Usebitfab.GetCurrentTrace(ctx) to set context that applies to the entire trace (all spans within a single execution). This is useful for grouping traces by session or attaching trace-level metadata:
SetSessionID(id)- Groups traces by user session. Stored as a database column for efficient filtering.SetName(name)- The trace’s title in Bitfab, and a field you can search and filter on. Use it for the case, ticket, or record the run is about. Unset, the trace is titled by its trace function key.SetMetadata(map)- Arbitrary key-value metadata on the trace. Merges with existing metadata.AddContext(map)- Key-value context entries. Accumulates across multiple calls.TraceID()- Returns the canonical Bitfab trace ID used for persisted lookups.
Read One Persisted Span
Fetch one span without loading the full trace. Repeated name matches default to the last span.bitfab.SpanOccurrenceAt(index) for a zero-based occurrence. A missing trace or span returns nil, nil.
Dropping a Trace
Usebitfab.GetCurrentTrace(ctx).Drop() to discard the in-flight trace. Once flagged, spans that complete afterward are not uploaded at all, and the flag rides out on the completion payload, so when the trace completes the server scrubs any payloads that already raced out (the trace, its external trace, and sibling spans), deletes the archived S3 objects, and marks it dropped instead of completed, keeping only a skeleton audit row. Use it to discard runs you never want stored (health checks, test traffic) or a run you know carries sensitive data.
- Safe to call outside a span (
GetCurrentTracereturnsnil, andDrop()is a no-op on anilreceiver), and never panics into your application.
Error Handling
Errors are captured in the span and returned to the caller:error_source: "code". SDK-internal errors are recorded with source: "sdk". Both appear in the span’s errors array in the Bitfab dashboard.
Flushing Traces
FlushTraces returns false when an export failed or the deadline expired.
Go does not have an automatic atexit hook. You must call FlushTraces before your program exits to ensure all pending spans are sent.
Closing a Client
Close flushes and permanently shuts down the client’s background delivery worker. It is idempotent, and returns false when an export failed or the deadline expired. A closed client no longer records spans, so prefer it over FlushTraces for the final flush in main.
OpenTelemetry Transport
Span delivery runs on OpenTelemetry. This is an internal transport detail:Span, Start/End, and GetFunction still produce Bitfab spans with Bitfab trace IDs, and no OTel type appears in the Bitfab API. The SDK owns a private tracer provider per client and never touches your application’s global OTel provider.
Each client lazily starts one bounded batching worker on its first span, so a client that never traces starts no worker.
Replay carriers use this same worker. A private carrier reference follows each
queued OTel span through batching and trimming but is excluded from the request
body. A successful ingestion response returns the server trace IDs and
acknowledges the references in that request. Replay polls the status endpoint
only as a fallback when the flush leaves delivery unconfirmed.
A single span may use up to 7,800,000 carrier bytes when its dedicated request
gzips below the 3,000,000-byte wire target and remains below the 8,000,000-byte
decompressed ingress limit. The carrier is the payload re-escaped into the OTLP
attribute. If it does not compress enough, compression is unavailable, or it
exceeds the raw ceiling, the SDK replaces its largest fields with
<unserializable: too_large_N_bytes> placeholders until it fits the
2,800,000-byte fallback budget. The trim is recorded on the span’s errors so
the trace is flagged as incomplete.
Adopting OpenTelemetry raises the SDK’s minimum Go version to 1.25. The SDK links only the OpenTelemetry trace SDK, so protobuf and gRPC stay out of your build.
See OpenTelemetry Transport Architecture for the full design.
Datasets
client.Datasets creates, reads, and modifies datasets programmatically, with the same operations your coding agent reaches through the Bitfab MCP tools. A dataset is a named bucket of traces under one trace function. Experiments replay against it and its graders score its members.
Save is an upsert on the dataset name within its trace function, so re-running a program does not accumulate duplicates. Membership and grader calls accept up to 100 ids and report ids they skipped rather than failing the whole call. RemoveTraces only drops membership. Traces are never deleted. RerunGraders waits for the run by default (90 seconds, configurable through Timeout) and returns whatever state it last saw. Set NoWait to return immediately and poll with GetGraderRerun. See the reference for every method and result type.