zoobzio December 13, 2025 Edit this page

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