> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfab.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Go SDK

> Generate evaluations and iterate on your AI applications

The Bitfab Go SDK captures your AI function calls to automatically generate evaluations. Re-run your prompts with different models, parameters, and inputs to iterate faster.

<Note>
  Framework-native adapters (LangGraph, OpenAI Agents, BAML, Claude Agent SDK) are not yet available for Go. See [Frameworks overview](/frameworks/overview) for current coverage. Instrument Go code manually via `client.Span` or `client.Start`.
</Note>

## Installation

```bash theme={null}
go get github.com/Project-White-Rabbit/bitfab-go
```

## Quick Start

```go theme={null}
package main

import (
	"context"
	"os"
	"time"

	bitfab "github.com/Project-White-Rabbit/bitfab-go"
)

func main() {
	client := bitfab.NewClient(os.Getenv("BITFAB_API_KEY"))
	ctx := context.Background()

	client.Span(ctx, "my-service", func(ctx context.Context) (any, error) {
		return map[string]any{"result": "..."}, nil
	})

	client.FlushTraces(5 * time.Second)
}
```

<Tip>
  Need an API key? Get one from the [Bitfab dashboard](https://bitfab.ai/setup) or see the [API Keys guide](/api-keys) for detailed setup instructions.
</Tip>

<Accordion title="Coding Agent Prompt (Cursor, Claude Code)">
  Copy this prompt into your coding agent (tested with Cursor and Claude Code using Sonnet 4.5):

  ```text theme={null}
  Modify existing Go code to add Bitfab tracing.
  Do NOT browse or web search. Use ONLY the API described below.

  Bitfab Go SDK (authoritative excerpt):
  - Install: `go get github.com/Project-White-Rabbit/bitfab-go`
  - Init:
    import bitfab "github.com/Project-White-Rabbit/bitfab-go"
    client := bitfab.NewClient(os.Getenv("BITFAB_API_KEY"))
  - Start/End style (PREFERRED for existing functions):
    func myFunc(ctx context.Context, arg1 string) (Result, error) {
        ctx, span := client.Start(ctx, "<trace_function_key>", "<SpanName>", bitfab.WithType("function"))
        defer span.End()
        span.SetInput(arg1)
        result, err := doWork(ctx, arg1)
        if err != nil {
            span.SetError(err)
            return Result{}, err
        }
        span.SetOutput(result)
        return result, nil
    }
  - Closure style (for inline code):
    result, err := client.Span(ctx, "<trace_function_key>", func(ctx context.Context) (any, error) {
        return doWork(ctx), nil
    }, bitfab.WithName("<SpanName>"), bitfab.WithType("function"), bitfab.WithInput(args...))
  - Fluent API:
    fn := client.GetFunction("<trace_function_key>")
    ctx, span := fn.Start(ctx, "<SpanName>", bitfab.WithType("function"))
    defer span.End()
  - Span types: "llm", "agent", "function", "guardrail", "handoff", "custom"
  - Always pass ctx from Start or Span callback into nested calls for parent-child linking.
  - Always call client.FlushTraces(5 * time.Second) before program exit.

  Task:
  1) Ensure the bitfab-go module is added (`go get github.com/Project-White-Rabbit/bitfab-go`).
  2) Ensure a client is initialized with the API key.
  3) Read the codebase and identify ALL AI workflows (LLM calls, agent runs, AI-driven decisions).
  4) Present me with a numbered list of workflows you found. For each, describe:
     - What it does
     - Why it's worth instrumenting - what visibility tracing gives you into each step
  5) After I choose which workflow(s) to instrument:
     - Add Start/End at the top of each function (preferred) or wrap in Span closure
     - Use SetInput/SetOutput/SetError to capture data
     - Instrument intermediate steps (not just the final output) so each trace has enough context to diagnose issues
     - Pass `ctx` through for nested span support
  6) Do not change function signature, behavior, or return value. Minimal diff.

  Output:
  - First: your numbered list of workflows with why each is worth instrumenting
  - After my selection: minimal diffs for go.mod and the instrumented functions
  ```
</Accordion>

## Basic Configuration

```go theme={null}
// Default (production URL)
client := bitfab.NewClient(apiKey)

// Omit the key: NewClient reads BITFAB_API_KEY from the environment
client := bitfab.NewClient("")

// Set the key via an option instead of the positional argument
client := bitfab.NewClient("", bitfab.WithAPIKey(apiKey))

// Custom service URL
client := bitfab.NewClient(apiKey, bitfab.WithServiceURL("http://localhost:4000"))

// Disable tracing (functions still execute, but no spans are sent)
client := bitfab.NewClient(apiKey, bitfab.WithEnabled(false))
```

<Info>
  **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.
</Info>

When no key is passed (empty argument and no `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`:

```go theme={null}
// Panics in NewClient if no key resolves, instead of disabling quietly
client := bitfab.NewClient(apiKey, bitfab.WithStrict(true))
```

## Tracing

### Custom (Recommended)

#### Using `Start`/`End` to Instrument Existing Functions

The recommended way to add tracing to existing functions without restructuring them:

```go theme={null}
func processOrder(ctx context.Context, orderID string, amount float64) (Order, error) {
	ctx, span := client.Start(ctx, "order-processing", "ProcessOrder",
		bitfab.WithType("function"))
	defer span.End()
	span.SetInput(orderID, amount)

	order, err := doWork(ctx, orderID, amount)
	if err != nil {
		span.SetError(err)
		return Order{}, err
	}
	span.SetOutput(order)
	return order, nil
}
```

* `Start` returns an updated `context.Context` (for nested span propagation) and an `ActiveSpan`
* `defer span.End()` ensures the span is always completed and sent
* `SetInput` / `SetOutput` / `SetError` record data on the span
* `End` is 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.

```go theme={null}
// pkg/tracing/tracing.go - single source of truth
package tracing

import (
	"os"
	bitfab "github.com/Project-White-Rabbit/bitfab-go"
)

var Client = bitfab.NewClient(os.Getenv("BITFAB_API_KEY"))
var OrderService = Client.GetFunction("order-processing")
```

```go theme={null}
// services/process_order.go
package services

import (
	"context"
	"myapp/pkg/tracing"
	bitfab "github.com/Project-White-Rabbit/bitfab-go"
)

func ProcessOrder(ctx context.Context, orderID string) (Order, error) {
	ctx, span := tracing.OrderService.Start(ctx, "ProcessOrder", bitfab.WithType("function"))
	defer span.End()
	span.SetInput(orderID)

	order, err := doWork(ctx, orderID)
	if err != nil {
		span.SetError(err)
		return Order{}, err
	}
	span.SetOutput(order)
	return order, nil
}
```

```go theme={null}
// main.go
package main

import (
	"context"
	"myapp/pkg/tracing"
	"myapp/services"
	"time"
)

func main() {
	defer tracing.Client.FlushTraces(5 * time.Second)
	ctx := context.Background()
	services.ProcessOrder(ctx, "order-123")
}
```

Spans from different files are automatically linked as parent-child when you pass `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:

```go theme={null}
result, err := client.Span(ctx, "order-processing", func(ctx context.Context) (any, error) {
	return map[string]any{"order_id": "123", "total": 100}, nil
}, bitfab.WithName("ProcessOrder"), bitfab.WithType("function"),
   bitfab.WithInput("order-123", 100))
```

#### Using `GetFunction` for a Static Trace Key

Bind a trace function key once, then create multiple spans without repeating it:

```go theme={null}
orderService := client.GetFunction("order-processing")

result, err := orderService.Span(ctx, func(ctx context.Context) (any, error) {
	return map[string]any{"order_id": "123"}, nil
}, bitfab.WithName("ProcessOrder"), bitfab.WithType("function"))

result, err = orderService.Span(ctx, func(ctx context.Context) (any, error) {
	return map[string]any{"valid": true}, nil
}, bitfab.WithName("ValidateOrder"), bitfab.WithType("guardrail"))
```

#### Automatic Nesting

Spans nest automatically when you pass `ctx` from the outer span callback:

```go theme={null}
client.Span(ctx, "pipeline", func(ctx context.Context) (any, error) {
	// This span becomes a child of the outer span
	client.Span(ctx, "pipeline", func(ctx context.Context) (any, error) {
		// This span becomes a grandchild
		return client.Span(ctx, "pipeline", func(ctx context.Context) (any, error) {
			return map[string]any{"safe": true}, nil
		}, bitfab.WithName("CheckFraud"), bitfab.WithType("guardrail"))
	}, bitfab.WithName("Validate"), bitfab.WithType("guardrail"))

	return map[string]any{"status": "done"}, nil
}, bitfab.WithName("Process"), bitfab.WithType("agent"))
```

#### Span Options

**Parameters:**

* `traceFunctionKey` (required): Groups spans under a function key in Bitfab
* `WithName(name)` (optional): Display name. Defaults to the trace function key
* `WithType(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 evaluated
* `WithFunctionName(name)` (optional): Override the function name in span data
* `WithInput(args...)` (optional, closure style only): Record input data. A single arg is stored directly; multiple args as a slice

**Span Types:**

```go theme={null}
// Valid span types
"llm"       // LLM calls
"agent"     // Agent workflows
"function"  // Function calls
"guardrail" // Safety checks
"handoff"   // Human handoffs
"custom"    // Default
```

**Examples:**

```go theme={null}
// LLM call
client.Span(ctx, "chat-service", func(ctx context.Context) (any, error) {
	return callOpenAI(prompt), nil
}, bitfab.WithName("ChatCompletion"), bitfab.WithType("llm"))

// Safety check
client.Span(ctx, "safety-service", func(ctx context.Context) (any, error) {
	return map[string]any{"safe": true}, nil
}, bitfab.WithName("ContentFilter"), bitfab.WithType("guardrail"))
```

#### Span Context

Use `span.AddContext()` on an `ActiveSpan` (Start/End style) to attach contextual key-value pairs at runtime - useful when context depends on computed values:

```go theme={null}
ctx, span := client.Start(ctx, "order-processing", "ProcessOrder",
	bitfab.WithType("function"))
defer span.End()

requestID := generateRequestID()
span.AddContext(map[string]any{"request_id": requestID, "user_id": "u-123"})
```

Each `AddContext` call pushes the entire map as one entry. Multiple calls accumulate entries:

```go theme={null}
span.AddContext(map[string]any{"user_id": "u-123"})
span.AddContext(map[string]any{"request_id": "req-789"})
// Result: contexts: [{"user_id": "u-123"}, {"request_id": "req-789"}]
```

#### Span Prompt

Use `span.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:

```go theme={null}
func classifyText(ctx context.Context, text string) (string, error) {
	ctx, span := client.Start(ctx, "classification", "ClassifyText",
		bitfab.WithType("llm"))
	defer span.End()
	span.SetInput(text)

	prompt := fmt.Sprintf("Classify the following text: %s", text)
	span.SetPrompt(prompt)
	result, err := llm.Complete(prompt)
	if err != nil {
		span.SetError(err)
		return "", err
	}

	span.SetOutput(result)
	return result, nil
}
```

The prompt is metadata only. It records the prompt text for display and reference in the dashboard; it does not send the prompt to any model or change what the span executes. The last `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

Use `bitfab.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:

```go theme={null}
result, err := client.Span(ctx, "order-processing", func(ctx context.Context) (any, error) {
	trace := bitfab.GetCurrentTrace(ctx)

	// Set session ID (stored as database column, filterable in dashboard)
	trace.SetSessionID("session-123")

	// Set trace metadata (stored in raw trace data)
	trace.SetMetadata(map[string]any{"region": "us-west-2", "environment": "production"})

	// Add context entries (stored as key-value pairs, accumulates across calls)
	trace.AddContext(map[string]any{"workflow": "checkout-flow", "batch_id": "batch-2024-01"})

	return map[string]any{"status": "completed"}, nil
}, bitfab.WithName("ProcessOrder"), bitfab.WithType("function"))
```

* `SetSessionID(id)` - Groups traces by user session. Stored as a database column for efficient filtering.
* `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.

```go theme={null}
span, err := client.GetTraceSpan(ctx, traceID, bitfab.SpanLookup{
	Name: "GenerateAnswer",
})
first, err := client.GetTraceSpan(ctx, traceID, bitfab.SpanLookup{
	Name: "GenerateAnswer",
	Occurrence: bitfab.FirstSpanOccurrence,
})
exact, err := client.GetTraceSpan(ctx, traceID, bitfab.SpanLookup{ID: spanID})
```

Both IDs are canonical Bitfab IDs; ingestion source IDs are not accepted. Use `bitfab.SpanOccurrenceAt(index)` for a zero-based occurrence. A missing trace or span returns `nil, nil`.

#### Dropping a Trace

Use `bitfab.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.

```go theme={null}
result, err := client.Span(ctx, "order-processing", func(ctx context.Context) (any, error) {
	if isHealthCheck(ctx) {
		bitfab.GetCurrentTrace(ctx).Drop()
	}

	return map[string]any{"status": "completed"}, nil
}, bitfab.WithName("ProcessOrder"), bitfab.WithType("function"))
```

* Safe to call outside a span (`GetCurrentTrace` returns `nil`, and `Drop()` is a no-op on a `nil` receiver), and never panics into your application.

#### Error Handling

Errors are captured in the span and returned to the caller:

```go theme={null}
result, err := client.Span(ctx, "risky-service", func(ctx context.Context) (any, error) {
	return nil, errors.New("something went wrong")
}, bitfab.WithName("RiskyOperation"), bitfab.WithType("function"))

// err contains "something went wrong"
// The span records the error message and timing
```

Each error is classified by source. Errors returned by your function are recorded with `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

```go theme={null}
client.FlushTraces(5 * time.Second) // Wait up to 5s for pending spans
```

Go does not have an automatic `atexit` hook. You must call `FlushTraces` before your program exits to ensure all pending spans are sent.
