SDK v2 Architecture
Understanding the pack-first design and component relationships.
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
┌─────────────────────────────────────────────────────────┐
│ Application │
│ │
│ conv, _ := sdk.Open("pack.json", "chat") │
│ resp, _ := conv.Send(ctx, "Hello") │
│ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ SDK Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PackLoader │ │ Conversation │ │ Hooks │ │
│ │ │ │ │ │ │ │
│ │ - Load pack │ │ - Variables │ │ - Events │ │
│ │ - Validate │ │ - Tools │ │ - Subscribe │ │
│ │ - Cache │ │ - Messages │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ Runtime Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Providers │ │ Pipeline │ │ Tools │ │
│ │ │ │ │ │ │ │
│ │ - OpenAI │ │ - Execute │ │ - Registry │ │
│ │ - Anthropic │ │ - Stream │ │ - Handlers │ │
│ │ - Google │ │ - Context │ │ - HTTP │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
Core Components
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
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
Hooks (EventBus)
Observability through events:
type Event struct {
Type string
Timestamp time.Time
Data map[string]any
}
Event Flow:
conv.Send()emitsEventSend- Provider call executes
- Response emits
EventResponse - Tool calls emit
EventToolCall - Errors emit
EventError
Request Flow
conv.Send(ctx, "Hello")
│
▼
┌───────────────────┐
│ Emit EventSend │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Build Context │
│ - System prompt │
│ - Variables │
│ - History │
│ - Tool defs │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Provider Call │
│ (OpenAI, etc.) │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Process Response │
│ - Tool calls? │
│ - Update history │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Emit EventResponse│
└─────────┬─────────┘
│
▼
Return Response
Tool Execution
LLM Response with tool_call
│
▼
┌───────────────────┐
│ Lookup Handler │
│ conv.handlers[name]│
└─────────┬─────────┘
│
┌─────┴─────┐
│ │
▼ ▼
OnTool OnToolAsync
│ │
│ ┌─────┴─────┐
│ │ │
│ ▼ ▼
│ Auto-OK Pending
│ │ │
▼ ▼ ▼
Execute Handler Wait for
│ Resolve/Reject
▼ │
Emit EventToolResult │
│ │
▼─────────────────┘
Continue conversation
State Management
Variable Storage
type ConversationState struct {
mu sync.RWMutex
variables map[string]any
messages []types.Message
}
Thread-safe access:
SetVar()acquires write lockGetVar()acquires read lock- Concurrent access safe
Message History
// Append message
state.AddMessage(types.Message{
Role: "user",
Content: "Hello",
})
// Get all messages
messages := state.GetMessages()
Thread Safety
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()
See Also
Was this page helpful?