SDK Architecture
Understanding the pack-first design and component relationships.
Design Principles
Section titled “Design Principles”- Pack-First - Pack file is the single source of truth
- Minimal Boilerplate - 5 lines to hello world
- Type Safety - Leverage Go’s type system
- Thread Safe - All operations are concurrent-safe
- Extensible - Hooks and custom handlers
Architecture Overview
Section titled “Architecture Overview”Core Components
Section titled “Core Components”PackLoader
Section titled “PackLoader”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
Conversation
Section titled “Conversation”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
Hooks (EventBus)
Section titled “Hooks (EventBus)”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:
conv.Send()emitsEventPipelineStarted- Provider call emits
EventProviderCallStarted/EventProviderCallCompleted - Tool calls emit
EventToolCallStarted/EventToolCallCompleted - Pipeline completion emits
EventPipelineCompleted - 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 —
InProcDispatcherruns synchronously,EventDispatcherpublishes to an event bus for async workers,NoOpDispatcherdefers toEventBusEvalListener - 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 —
MetricResultWriterfeeds aMetricCollectorfor Prometheus metrics,MetadataResultWriterattaches results to message metadata
Trigger patterns:
every_turn— after each assistant responseon_session_complete— when a session closessample_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.
Request Flow
Section titled “Request Flow”Tool Execution
Section titled “Tool Execution”State Management
Section titled “State Management”Variable Storage
Section titled “Variable Storage”Variables are stored internally in the session layer with thread-safe access:
SetVar(name, value string)acquires write lockGetVar(name string) (string, bool)acquires read lock- Concurrent access is safe
Message History
Section titled “Message History”Messages are managed through the session and pipeline layers. Use the public API:
// Get all messagesmessages := conv.Messages(ctx)
// Clear history_ = conv.Clear()Thread Safety
Section titled “Thread Safety”All Conversation methods are thread-safe:
// Safe concurrent accessvar 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()