A Session persists conversation history across runs, so multi-turn chat needs no manual item threading: prior items are prepended to the input before the run, and the new input plus everything the run generated is saved after it completes.
sess := agents.NewInMemorySession()
res1, _ := agents.Run(ctx, agent, "What city is the Golden Gate Bridge in?", agents.RunOptions{Session: sess, ModelProvider: p})
// "San Francisco"
res2, _ := agents.Run(ctx, agent, "What state is it in?", agents.RunOptions{Session: sess, ModelProvider: p})
// "California" — the agent saw the previous turn
type Session interface {
// GetItems returns stored items oldest-first. limit <= 0 returns all;
// a positive limit returns the most recent `limit` items.
GetItems(ctx context.Context, limit int) ([]TResponseInputItem, error)
// AddItems appends items to the history.
AddItems(ctx context.Context, items []TResponseInputItem) error
// PopItem removes and returns the most recent item, or nil if empty.
PopItem(ctx context.Context) (*TResponseInputItem, error)
// Clear removes all items.
Clear(ctx context.Context) error
}
Implement it against any store (Postgres, Redis, …). Use agents.MarshalInputItem / agents.UnmarshalInputItem for encoding — they handle two openai-go serialization quirks (assistant messages and "type"-less easy messages) that naive JSON round-trips get wrong.
The built-ins sit on a spectrum from “zero dependencies” to “full database”. They all satisfy the same interface, so you can switch later:
| Implementation | Storage | Dependencies | Module | Use when |
|---|---|---|---|---|
InMemorySession |
memory | none | core | tests, short-lived chats |
memory.FileSession |
JSONL file | none | core | single process, no database wanted |
sessions (SQLite) |
.db file |
bun + driver | sessions |
one host, but you want SQL (transactions, external querying) |
sessions (PostgreSQL) |
server | bun + driver | sessions |
concurrent processes, shared/production storage |
openai.ConversationsSession |
OpenAI server | core (models/openai) |
core | no local store; history lives in the OpenAI Conversations API |
openai.CompactionSession |
wraps another Session | core (models/openai) |
core | auto-summarize history via responses.compact once it grows large |
FileSession and the SQLite backend overlap — both persist to one local file — and the line between them is dependencies: FileSession is zero-dependency and lives in the core module, so anyone using the SDK has it without pulling a database driver. Reach for the sessions module’s SQLite when you specifically want SQL semantics (real transactions, querying the .db with other tools, an easy migration path to Postgres).
agents.NewInMemorySession() — goroutine-safe, process-lifetime history. Ideal for tests. Treat returned items as read-only (they share underlying pointers with the store).
memory.FileSession persists history as one JSON item per line, with zero extra dependencies. It fills the “simple local persistence” niche Python covers with SQLiteSession, but without a database driver (for actual SQLite/Postgres, see the sessions module below):
import "github.com/zzir/agents-go/memory"
sess, err := memory.NewFileSession("sessions", "user-123") // sessions/user-123.jsonl
// or pin an exact path:
sess, err = memory.OpenFileSession("/var/data/chats/user-123.jsonl")
Properties:
FileSession instances opened on the same path (they share a per-path lock). Cross-process access is not locked.write call; rewrites (PopItem) go through an fsynced temp file + atomic rename.The github.com/zzir/agents-go/sessions module backs a Session with a SQL database via uptrace/bun. It is a separate Go module so its database-driver dependencies never reach the core SDK — add it only if you use it:
import "github.com/zzir/agents-go/sessions"
// SQLite — pure-Go (modernc) driver, no CGO:
sess, db, err := sessions.NewSQLite("file:/var/data/agents.db", "user-123")
defer db.Close()
err = sessions.CreateSchema(ctx, db) // once
// PostgreSQL — bring your own *sql.DB (pgx, lib/pq, or bun's pgdriver):
sess, db := sessions.NewPostgres(sqldb, "user-123")
err = sessions.CreateSchema(ctx, db)
Both store one row per item in an agent_messages table, encoded with agents.MarshalInputItem. A single *bun.DB can serve many session IDs (sessions.New(db, id)); rows are isolated by session_id. One schema and CRUD path serves both backends — bun smooths over the dialect differences.
openai.ConversationsSession stores history server-side under an OpenAI conversation ID — there is no local store at all. It is the Go counterpart of Python’s OpenAIConversationsSession. The conversation is created lazily on first use unless you attach an existing one:
import "github.com/zzir/agents-go/models/openai"
sess := openai.NewConversationsSession() // reads OPENAI_API_KEY; or pass option.WithAPIKey(...)
// sess = sess.WithConversationID("conv_existing") // resume a known conversation
// id, _ := sess.ConversationID(ctx) // read/create the server-side ID
agents.Run(ctx, agent, "remember my name is Ada",
agents.RunOptions{Session: sess, ModelProvider: openai.NewProvider()})
GetItems/AddItems/PopItem/Clear proxy the OpenAI Conversations API. Item conversion reuses agents.UnmarshalInputItem, so messages and function calls/outputs round-trip; exotic server-only item types may not. Clear deletes the conversation, and the next use creates a fresh one. Lives in the models/openai package because it needs the OpenAI client.
openai.CompactionSession decorates any other Session, calling the OpenAI responses.compact API to summarize history once it grows past a threshold, then replacing the stored items with the compacted result. It is the Go counterpart of Python’s OpenAIResponsesCompactionSession.
import "github.com/zzir/agents-go/models/openai"
base := agents.NewInMemorySession() // or memory.FileSession, sessions.New, …
sess, err := openai.NewCompactionSession(base, openai.CompactionOptions{
Model: "gpt-4.1", // OpenAI model used for compaction (default gpt-4.1)
Threshold: 20, // compact when ≥20 candidate items accumulate (default 10)
// Mode / ShouldCompact override the defaults if needed.
}, /* option.WithAPIKey(...) */)
agents.Run(ctx, agent, "…", agents.RunOptions{Session: sess, ModelProvider: openai.NewProvider()})
The runner calls compaction after persisting a completed run (Python compacts per turn; Go persists once per run, so compaction is attempted once per run). “Candidate” items exclude user messages and existing compaction items, matching the Python heuristic. It cannot wrap a ConversationsSession (that manages its own server-side history) and requires an OpenAI compaction model.
Session to ResumeRun.PopItem to remove the last item (e.g. let a user edit their question):last, _ := sess.PopItem(ctx) // remove the assistant answer
last, _ = sess.PopItem(ctx) // remove the user question
res, _ := agents.Run(ctx, agent, correctedQuestion, agents.RunOptions{Session: sess, ModelProvider: p})
ForkSession clones an entire conversation; ForkSessionAt copies only the first n items, creating a branch point. Both operate on the Session interface, so any combination of source and destination backends works (e.g. fork a SQLite session into an in-memory one):
// Full clone — dst becomes an exact copy of src.
dst := agents.NewInMemorySession()
agents.ForkSession(ctx, src, dst)
// Branch at item 5 — dst gets items [0..4], the two sessions diverge from there.
branch := agents.NewInMemorySession()
agents.ForkSessionAt(ctx, src, branch, 5)
When the fork point is known by a server-assigned item ID rather than a positional index, use IndexOfItemID to resolve it:
items, _ := src.GetItems(ctx, 0)
idx, ok := agents.IndexOfItemID(items, "msg_abc123")
if ok {
agents.ForkSessionAt(ctx, src, branch, idx+1) // include the matched item
}
Only items the model produced carry IDs (output messages, function calls, reasoning items, etc.). User-created “easy” messages have no server-assigned ID and are never matched by IndexOfItemID — address those by position.
One session = one conversation. Key sessions by conversation ID:
func sessionFor(userID, threadID string) (agents.Session, error) {
return memory.NewFileSession("sessions", userID+"-"+threadID)
}