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

Installation

go get github.com/Project-White-Rabbit/simforge-go

Quick Start

package main

import (
	"context"
	"os"
	"time"

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

func main() {
	client := simforge.NewClient(os.Getenv("SIMFORGE_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)
}
Copy this prompt into your coding agent (tested with Cursor and Claude Code using Sonnet 4.5):
Modify existing Go code to add Simforge tracing.
Do NOT browse or web search. Use ONLY the API described below.

Simforge Go SDK (authoritative excerpt):
- Install: `go get github.com/Project-White-Rabbit/simforge-go`
- Init:
  import simforge "github.com/Project-White-Rabbit/simforge-go"
  client := simforge.NewClient(os.Getenv("SIMFORGE_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>", simforge.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
  }, simforge.WithName("<SpanName>"), simforge.WithType("function"), simforge.WithInput(args...))
- Fluent API:
  fn := client.GetFunction("<trace_function_key>")
  ctx, span := fn.Start(ctx, "<SpanName>", simforge.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.

STRICT RULE:
You MUST ask me which function to instrument before making ANY code changes.
Do NOT choose a function yourself.

Task:
1) Ensure the simforge-go module is added (`go get github.com/Project-White-Rabbit/simforge-go`).
2) Ensure a client is initialized with the API key.
3) Ask me which EXISTING function should be instrumented with Simforge.
4) After I confirm the function name:
   - Add Start/End at the top of the function (preferred) or wrap in Span closure
   - Use SetInput/SetOutput/SetError to capture data
   - Pass `ctx` through for nested span support
5) Do not change function signature, behavior, or return value. Minimal diff.

Output:
- First: your question asking which function to instrument
- After confirmation: minimal diffs for go.mod and the instrumented function

Basic Configuration

// Default (production URL)
client := simforge.NewClient(apiKey)

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

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

Tracing

Using Start/End to Instrument Existing Functions

The recommended way to add tracing to existing functions without restructuring them:
func processOrder(ctx context.Context, orderID string, amount float64) (Order, error) {
	ctx, span := client.Start(ctx, "order-processing", "ProcessOrder",
		simforge.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

Using client.Span (Closure Style)

Wrap inline code in a closure. Output is captured automatically from the return value. Use WithInput to record inputs:
result, err := client.Span(ctx, "order-processing", func(ctx context.Context) (any, error) {
	return map[string]any{"order_id": "123", "total": 100}, nil
}, simforge.WithName("ProcessOrder"), simforge.WithType("function"),
   simforge.WithInput("order-123", 100))

Using GetFunction for a Static Trace Key

Bind a trace function key once, then create multiple spans without repeating it:
orderService := client.GetFunction("order-processing")

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

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

Automatic Nesting

Spans nest automatically when you pass ctx from the outer span callback:
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
		}, simforge.WithName("CheckFraud"), simforge.WithType("guardrail"))
	}, simforge.WithName("Validate"), simforge.WithType("guardrail"))

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

Span Options

Parameters:
  • traceFunctionKey (required): Groups spans under a function key in Simforge
  • WithName(name) (optional): Display name. Defaults to the trace function key
  • WithType(spanType) (optional): Span type. Defaults to "custom"
  • 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:
// Valid span types
"llm"       // LLM calls
"agent"     // Agent workflows
"function"  // Function calls
"guardrail" // Safety checks
"handoff"   // Human handoffs
"custom"    // Default
Examples:
// LLM call
client.Span(ctx, "chat-service", func(ctx context.Context) (any, error) {
	return callOpenAI(prompt), nil
}, simforge.WithName("ChatCompletion"), simforge.WithType("llm"))

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

Error Handling

Errors are captured in the span and returned to the caller:
result, err := client.Span(ctx, "risky-service", func(ctx context.Context) (any, error) {
	return nil, errors.New("something went wrong")
}, simforge.WithName("RiskyOperation"), simforge.WithType("function"))

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

Flushing Traces

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.

Native Functions

Coming soon.

Sim Runners

Coming soon.