Documentation
¶
Overview ¶
Package session gives a conversation a durable identity, metadata and scoped state.
Scoping in the SDK is ambient: memory derives an "orgID:conversationID" key from the context and hard-fails if either is missing. That is enough to isolate transcripts and no more. There is no object describing a conversation, nothing to list with titles and timestamps, nowhere to keep a fact that should outlive one turn, and no way to resume by ID with anything other than a bare transcript.
A Session supplies that. It deliberately does NOT own the transcript: pkg/memory already stores messages, keyed by the same conversation ID a Session is identified by. Duplicating messages here would create two sources of truth for the same data.
Session.ID == the conversation ID used by pkg/memory
State scoping ¶
State keys are scoped by prefix, following the model Google's ADK uses:
"user:" shared across every session belonging to one user "app:" shared across every session in the application "temp:" never persisted; dropped on save (none) private to this session
The prefixes are resolved at the storage layer, so a caller reads and writes one flat namespace.
Index ¶
- Constants
- Variables
- func DeriveTitle(message string) string
- func NewID() string
- type Dialect
- type Filter
- type InMemoryStore
- func (s *InMemoryStore) Create(_ context.Context, sess *Session) error
- func (s *InMemoryStore) Delete(_ context.Context, orgID, id string) error
- func (s *InMemoryStore) Get(_ context.Context, orgID, id string) (*Session, error)
- func (s *InMemoryStore) List(_ context.Context, filter Filter) ([]*Session, error)
- func (s *InMemoryStore) Update(_ context.Context, sess *Session) error
- type Manager
- func (m *Manager) Bind(ctx context.Context, s *Session) context.Context
- func (m *Manager) Create(ctx context.Context, orgID, userID string) (*Session, error)
- func (m *Manager) Delete(ctx context.Context, orgID, id string) error
- func (m *Manager) Fork(ctx context.Context, orgID, id string) (*Session, error)
- func (m *Manager) Get(ctx context.Context, orgID, id string) (*Session, error)
- func (m *Manager) List(ctx context.Context, filter Filter) ([]*Session, error)
- func (m *Manager) Resume(ctx context.Context, orgID, id string) (*Session, context.Context, error)
- func (m *Manager) Save(ctx context.Context, s *Session) error
- func (m *Manager) Touch(ctx context.Context, s *Session, messages, tokens int, firstMessage string) error
- type ManagerOption
- type SQLStore
- func (s *SQLStore) Create(ctx context.Context, sess *Session) error
- func (s *SQLStore) Delete(ctx context.Context, orgID, id string) error
- func (s *SQLStore) Get(ctx context.Context, orgID, id string) (*Session, error)
- func (s *SQLStore) List(ctx context.Context, filter Filter) ([]*Session, error)
- func (s *SQLStore) Migrate(ctx context.Context) error
- func (s *SQLStore) Update(ctx context.Context, sess *Session) error
- type Session
- type Store
Constants ¶
const ( // UserPrefix scopes a key to the user, across all their sessions. UserPrefix = "user:" // AppPrefix scopes a key to the application, across all users. AppPrefix = "app:" // TempPrefix marks a key as never persisted. TempPrefix = "temp:" )
State key prefixes.
Variables ¶
var ErrNotFound = errors.New("session not found")
ErrNotFound is returned when no session matches an ID.
Functions ¶
func DeriveTitle ¶
DeriveTitle produces a short label from a message.
Kept deliberately dumb -- truncation on a word boundary, no model call. A title is a listing convenience, and spending a model round trip (and its latency, cost and failure mode) on one is not a good trade.
Types ¶
type Filter ¶
type Filter struct {
OrgID string
UserID string
// Limit caps the number of sessions returned. Zero means no limit.
Limit int
}
Filter narrows a session listing.
type InMemoryStore ¶
type InMemoryStore struct {
// contains filtered or unexported fields
}
InMemoryStore keeps sessions in process. Suitable for tests, single-process deployments and local development.
func NewInMemoryStore ¶
func NewInMemoryStore() *InMemoryStore
NewInMemoryStore creates an in-process session store.
func (*InMemoryStore) Create ¶
func (s *InMemoryStore) Create(_ context.Context, sess *Session) error
Create implements Store.
func (*InMemoryStore) Delete ¶
func (s *InMemoryStore) Delete(_ context.Context, orgID, id string) error
Delete implements Store.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager creates and resolves sessions, and stamps the identity that memory scoping requires onto a context.
This is the piece that makes sessions useful rather than decorative. Memory derives its scope key from multitenancy.GetOrgID and memory.GetConversationID and hard-fails when either is missing -- which is why a scheduled or background run, having no inbound request to inherit from, could not write to memory at all. Resolve produces a context carrying both.
func NewManager ¶
func NewManager(store Store, options ...ManagerOption) *Manager
NewManager creates a session manager over a store.
func (*Manager) Bind ¶
Bind stamps a session's identity onto a context so memory can scope to it.
Call this before handing the context to an agent. Without it an agent with memory configured fails its first turn, because getConversationID requires both an org ID and a conversation ID.
func (*Manager) Fork ¶
Fork creates a new session seeded with another's state.
The transcript is not copied: sessions reference messages by conversation ID rather than owning them, so a fork starts a fresh conversation carrying the parent's accumulated state. The parent is recorded under "forked_from".
func (*Manager) Resume ¶
Resume returns an existing session and a context scoped to it, ready to hand to an agent.
func (*Manager) Touch ¶
func (m *Manager) Touch(ctx context.Context, s *Session, messages, tokens int, firstMessage string) error
Touch records activity on a session: it refreshes UpdatedAt, adds to the message and token counters, and derives a title from the first message if the session does not have one yet.
type ManagerOption ¶
type ManagerOption func(*Manager)
ManagerOption configures a Manager.
func WithAppName ¶
func WithAppName(name string) ManagerOption
WithAppName sets the application name used to scope "app:" state.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore persists sessions in any database/sql backend.
It targets Postgres and SQLite, which differ in placeholder syntax ($1 versus ?) and in upsert spelling. Those are the only dialect-specific details, so rather than a driver interface per backend the store carries a small Dialect -- the same tradeoff ADK's DatabaseSessionService makes with its dialect-conditional branches.
State is stored in three tables so the "user:" and "app:" prefixes are genuinely shared rather than copied into every session row. "temp:" keys are dropped before any write.
func NewSQLStore ¶
NewSQLStore creates a session store over an existing *sql.DB.
It takes a live handle rather than a DSN on purpose: a service that already has a pool -- pkg/datastore/postgres, for instance -- should not open a second one just for sessions.
type Session ¶
type Session struct {
// ID is the conversation ID. It is the same value pkg/memory scopes the
// transcript by, so a session and its messages are never out of step.
ID string
// OrgID is the owning organization, matching pkg/multitenancy.
OrgID string
// UserID identifies the end user, and scopes "user:" state.
UserID string
// AppName scopes "app:" state. Optional.
AppName string
// Title is a human-readable label, usually derived from the first message.
Title string
// State is the session's scoped key-value store. Read it through Get and
// write it through Set so prefixes are handled consistently.
State map[string]any
CreatedAt time.Time
UpdatedAt time.Time
// MessageCount and TokenTotal are denormalised counters, maintained by the
// caller. They exist so a session list can show useful numbers without
// loading every transcript.
MessageCount int
TokenTotal int
}
Session is one conversation.
func (*Session) Clone ¶
Clone returns a deep copy, so a caller cannot mutate a store's internals through a returned session.
type Store ¶
type Store interface {
// Create stores a new session. It returns an error if the ID already exists.
Create(ctx context.Context, s *Session) error
// Get returns a session by ID, with shared state merged in.
Get(ctx context.Context, orgID, id string) (*Session, error)
// Update replaces a session's mutable fields.
Update(ctx context.Context, s *Session) error
// Delete removes a session and its private state.
Delete(ctx context.Context, orgID, id string) error
// List returns sessions matching the filter, most recently updated first.
// The returned sessions carry metadata but not state.
List(ctx context.Context, filter Filter) ([]*Session, error)
}
Store persists sessions.
Implementations must treat state prefixes as described in the package documentation: "temp:" keys are dropped on save, and "user:"/"app:" keys are shared across the sessions in that scope rather than copied into each.