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 main interaction point:

type Conversation struct {
pack *pack.Pack
prompt *pack.Prompt
state *ConversationState
handlers map[string]ToolHandler
eventBus *events.EventBus
// ...
}

Responsibilities:

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

Observability through events:

type Event struct {
Type string
Timestamp time.Time
Data map[string]any
}

Event Flow:

  1. conv.Send() emits EventSend
  2. Provider call executes
  3. Response emits EventResponse
  4. Tool calls emit EventToolCall
  5. Errors emit EventError
Diagram Diagram
type ConversationState struct {
mu sync.RWMutex
variables map[string]any
messages []types.Message
}

Thread-safe access:

  • SetVar() acquires write lock
  • GetVar() acquires read lock
  • Concurrent access safe
// Append message
state.AddMessage(types.Message{
Role: "user",
Content: "Hello",
})
// Get all messages
messages := state.GetMessages()

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), n)
}(i)
}
wg.Wait()