Sessions
Sessions manage conversation context across synapse calls.
Why Sessions?
LLMs are stateless. Sessions maintain history so synapses can reference previous interactions:
session := zyn.NewSession()
// First call
classifier.Fire(ctx, session, "I love this product!")
// Session: [{user: "I love..."}, {assistant: {positive...}}]
// Second call sees history
followup.Fire(ctx, session, "Was that positive or negative?")
// LLM can answer correctly from context
Basic Operations
session := zyn.NewSession()
// Check state
messages := session.Messages() // []Message
count := session.Len() // int
isEmpty := session.Len() == 0 // bool
// Clear all
session.Clear()
Message Access
// Get message at index
msg, err := session.At(0)
// Message structure
type Message struct {
Role Role // RoleUser, RoleAssistant, RoleSystem
Content string
}
Message Manipulation
// Remove at index
err := session.Remove(2)
// Replace at index
err := session.Replace(1, zyn.Message{
Role: zyn.RoleAssistant,
Content: "Updated response",
})
// Insert at index
err := session.Insert(0, zyn.Message{
Role: zyn.RoleSystem,
Content: "You are a helpful assistant",
})
// Append (used internally by synapses)
session.Append(zyn.RoleUser, "Hello")
Bulk Operations
Prune
Remove the last N messages:
session.Prune(4) // Remove last 4 messages
Useful for context window management when sessions grow large.
Truncate
Keep first N and last M messages:
session.Truncate(2, 2) // Keep first 2, last 2, remove middle
Useful for preserving initial context while keeping recent history.
Replace All
session.SetMessages([]zyn.Message{
{Role: zyn.RoleUser, Content: "Summarized context"},
{Role: zyn.RoleAssistant, Content: "Acknowledged"},
})
Token Tracking
Sessions track token usage from the last call:
synapse.Fire(ctx, session, "input")
if usage := session.LastUsage(); usage != nil {
fmt.Printf("Tokens: prompt=%d completion=%d total=%d\n",
usage.Prompt, usage.Completion, usage.Total)
}
Context Strategies
Sliding Window
Keep only the last N exchanges:
func slidingWindow(session *zyn.Session, maxMessages int) {
if session.Len() > maxMessages {
excess := session.Len() - maxMessages
session.Prune(excess)
}
}
// Usage
synapse.Fire(ctx, session, input)
slidingWindow(session, 20) // Keep last 20 messages
Summarization
Replace old messages with a summary:
func summarizeOldContext(ctx context.Context, session *zyn.Session, summarizer *zyn.TransformSynapse) {
if session.Len() < 20 {
return
}
// Get old messages
old := session.Messages()[:10]
oldText := formatMessages(old)
// Summarize
summary, _ := summarizer.Fire(ctx, zyn.NewSession(), oldText)
// Replace with summary
session.Truncate(0, 10) // Keep last 10
session.Insert(0, zyn.Message{
Role: zyn.RoleSystem,
Content: "Previous context summary: " + summary,
})
}
Selective Pruning
Remove low-value exchanges:
func pruneByRelevance(session *zyn.Session, topic string) {
messages := session.Messages()
relevant := []zyn.Message{}
for _, msg := range messages {
if isRelevant(msg.Content, topic) {
relevant = append(relevant, msg)
}
}
session.SetMessages(relevant)
}
Multi-Synapse Workflows
Sessions enable complex workflows where context flows between different synapses:
session := zyn.NewSession()
// Step 1: Extract data
extractor, _ := zyn.Extract[Customer]("customer info", provider)
customer, _ := extractor.Fire(ctx, session, rawEmail)
// Step 2: Classify (sees extraction context)
classifier, _ := zyn.Classification("urgency", []string{"low", "high"}, provider)
urgency, _ := classifier.Fire(ctx, session, customer.Issue)
// Step 3: Generate response (sees both previous steps)
responder, _ := zyn.Transform("write response", provider)
response, _ := responder.Fire(ctx, session, "Respond appropriately")
Transactional Updates
Sessions only update after successful calls:
session := zyn.NewSession()
initialLen := session.Len() // 0
_, err := synapse.Fire(ctx, session, "input")
if err != nil {
// Session unchanged
assert(session.Len() == initialLen)
}
This prevents retry attempts from corrupting session state.
Prompt Caching
Sessions leverage provider-side caching automatically:
- Full history sent with each call
- Providers cache repeated prefixes (5min-1hr TTL)
- Subsequent calls reuse cache, reducing costs 60-90%
- No configuration needed
Independent Sessions
Different conversations need separate sessions:
// Each user gets their own session
userSessions := make(map[string]*zyn.Session)
func handleRequest(userID string, input string) {
session, ok := userSessions[userID]
if !ok {
session = zyn.NewSession()
userSessions[userID] = session
}
synapse.Fire(ctx, session, input)
}
Next Steps
- Reliability Guide - Retry, timeout, circuit breaker
- Multi-Turn Cookbook - Advanced patterns