Skip to content

SDK Architecture

Understanding the pack-first design and component relationships.

  1. Pack-First - Pack file is the single source of truth
  2. Minimal Boilerplate - 5 lines to hello world
  3. Type Safety - Leverage Go’s type system
  4. Thread Safe - All operations are concurrent-safe
  5. Extensible - Hooks and custom handlers
Diagram

Loads and validates pack files:

// Internal - used by sdk.Open()
loader := packloader.New()
pack, err := loader.Load("./app.pack.json")

Responsibilities:

  • Parse JSON pack files
  • Validate schema
  • Cache loaded packs
  • Resolve prompt references

The Conversation type is the main interaction point. Its internal fields are unexported; the public API includes Send(), Stream(), SetVar(), GetVar(), OnTool(), and more.

Responsibilities:

  • Manage variables
  • Register tool handlers
  • Execute messages
  • Maintain history
  • Emit events

Observability through events:

type Event struct {
Type EventType // e.g. events.EventProviderCallCompleted
Timestamp time.Time
RunID string
SessionID string
ConversationID string
Data EventData // Type-specific payload (e.g. *ProviderCallCompletedData)
}

Event Flow:

  1. conv.Send() emits EventPipelineStarted
  2. Provider call emits EventProviderCallStarted / EventProviderCallCompleted
  3. Tool calls emit EventToolCallStarted / EventToolCallCompleted
  4. Pipeline completion emits EventPipelineCompleted
  5. Failures emit EventProviderCallFailed / EventPipelineFailed

The EventBus supports pluggable persistence via EventStore and fan-out to multiple listeners. See Observability for the full event architecture.

Automated quality checks on LLM outputs, defined in pack files and executed via dispatchers:

  • EvalDispatcher routes eval requests — InProcDispatcher runs synchronously, EventDispatcher publishes to an event bus for async workers, NoOpDispatcher defers to EventBusEvalListener
  • EvalRunner executes eval handlers (deterministic checks like contains, regex, json_valid, tools_called, or LLM judge evaluations) with timeout and panic recovery
  • ResultWriters record outcomes — MetricResultWriter feeds a MetricCollector for Prometheus metrics, MetadataResultWriter attaches results to message metadata

Trigger patterns:

  • every_turn — after each assistant response
  • on_session_complete — when a session closes
  • sample_turns / sample_sessions — deterministic hash-based sampling

The SDK eval middleware hooks into Send() (turn evals, async) and Close() (session evals, sync). Arena uses PackEvalHook to run evals against live or recorded conversations.

Diagram Diagram

Variables are stored internally in the session layer with thread-safe access:

  • SetVar(name, value string) acquires write lock
  • GetVar(name string) (string, bool) acquires read lock
  • Concurrent access is safe

Messages are managed through the session and pipeline layers. Use the public API:

// Get all messages
messages := conv.Messages(ctx)
// Clear history
_ = conv.Clear()

All Conversation methods are thread-safe:

// Safe concurrent access
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
conv.SetVar(fmt.Sprintf("key_%d", n), fmt.Sprintf("%d", n))
}(i)
}
wg.Wait()