session

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package session implements the SQLite-backed conversation store.

Schema and design rationale live in docs/design.md §9. Key choices:

  • pure-Go sqlite (modernc.org/sqlite) so cross-compile stays CGO-free
  • one global DB at ~/.deepseek/sessions.db (not per-project)
  • branching by reference: child sessions store parent_id + branch_point and replay the parent's messages up to that index instead of copying them

Migrations are applied idempotently at Open time. The current schema is v1; future bumps live in migrations.go (not yet written).

Package session implements the SQLite-backed conversation store.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fingerprint

func Fingerprint(projectPath string) (string, error)

Fingerprint returns a stable FNV-1a hash of the canonical project path. Symlinks in the path are resolved so two symlinked paths produce the same fingerprint.

Types

type Message

type Message struct {
	Idx              int
	Role             string
	Blocks           []llm.ContentBlock
	Content          string
	ReasoningContent string
	ToolCalls        []llm.ToolCall
	ToolCallID       string
	Model            string
	Usage            llm.Usage
	CostYuan         float64
	Timestamp        time.Time
}

Message is a row in the messages table.

Blocks is the v2 canonical content; when non-nil the Content/ReasoningContent/ToolCalls fields are not read. Until T-019 removes them, both representations may coexist on a single in-memory row so legacy paths keep working during migration.

type Persister

type Persister struct {
	// contains filtered or unexported fields
}

Persister is the concrete agent.Persister wrapping a Store + a snapshots.Manager. Live state for one session.

Construct with NewPersister. Reuse for the lifetime of the session; it's safe to call from the agent's goroutine but not from multiple goroutines concurrently (database/sql handles its own pooling, but the ID/step counters live in this struct).

func NewPersister

func NewPersister(store *Store, snaps *snapshots.Manager, sessionID string) *Persister

NewPersister returns a Persister wrapping store + snaps for sessionID. snaps may be nil to disable snapshotting.

func (*Persister) AppendAssistant

func (p *Persister) AppendAssistant(ctx context.Context, blocks []llm.ContentBlock, model string, usage llm.Usage) (int, error)

AppendAssistant records an assistant turn from a typed block slice (Thinking → Text → ToolUse in emission order).

func (*Persister) AppendReceipt

func (p *Persister) AppendReceipt(ctx context.Context, kind ReceiptKind, payload json.RawMessage) (int64, error)

AppendReceipt adds a transcript receipt for the persister's session.

func (*Persister) AppendToolResult

func (p *Persister) AppendToolResult(ctx context.Context, toolUseID string, content string, isError bool) (int, error)

AppendToolResult records the result of one tool_use as a single ToolResultBlock-bearing tool message.

func (*Persister) AppendUserMessage

func (p *Persister) AppendUserMessage(ctx context.Context, blocks []llm.ContentBlock) (int, error)

AppendUserMessage records a user turn from a typed block slice.

func (*Persister) PersistedMessageCount

func (p *Persister) PersistedMessageCount(ctx context.Context) (int, error)

PersistedMessageCount returns the persisted body-message count, letting /undo verify in-memory↔disk index alignment before truncating disk (T3.5).

func (*Persister) ReplaceWithCompaction

func (p *Persister) ReplaceWithCompaction(ctx context.Context, fromIdx, toIdx int, summary string) (int, error)

ReplaceWithCompaction forwards to Store.ReplaceWithCompaction scoped to this persister's session.

func (*Persister) SessionID

func (p *Persister) SessionID() string

SessionID returns the session this persister is bound to.

func (*Persister) SetActiveModel

func (p *Persister) SetActiveModel(ctx context.Context, model string) error

SetActiveModel persists a /models switch.

func (*Persister) TakeSnapshot

func (p *Persister) TakeSnapshot(stepIdx int, paths []string) (int, error)

TakeSnapshot snapshots the given paths before a mutating tool runs.

func (*Persister) TruncateMessages

func (p *Persister) TruncateMessages(ctx context.Context, keepCount int) (int, error)

TruncateMessages drops persisted messages with idx >= keepCount, satisfying agent.MessageTruncator so /undo can make disk match the rewound in-memory transcript (T3.5).

func (*Persister) Undo

func (p *Persister) Undo(n int) (int, error)

Undo restores the last n step snapshots and returns the number of files restored. Returns 0 if no snapshots manager is configured.

func (*Persister) UsageSummary added in v0.3.4

func (p *Persister) UsageSummary(ctx context.Context) (UsageSummary, error)

UsageSummary is a convenience wrapper that calls the store method scoped to this persister's session.

type ReceiptKind

type ReceiptKind string

ReceiptKind identifies the type of transcript receipt.

const (
	ReceiptModelFinal ReceiptKind = "model_final" // model usage and prefix hash
	ReceiptRepair     ReceiptKind = "repair"      // repair report from tool-call pipeline
	ReceiptPermission ReceiptKind = "permission"  // permission decision
	ReceiptCompaction ReceiptKind = "compaction"  // compaction event
	ReceiptEpoch      ReceiptKind = "epoch"       // epoch lifecycle event
)

type Session

type Session struct {
	ID                string
	ParentID          string // empty if root
	BranchPoint       int    // valid only when ParentID != ""
	ProjectPath       string
	WorkspaceFP       string // FNV-1a fingerprint of the canonical project path
	Model             string
	DuetEnabled       bool
	CreatedAt         time.Time
	LastUsedAt        time.Time
	Summary           string
	CompactionCount   int
	CompactionSummary string
}

Session is a row in the sessions table.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the SQLite-backed session store. Safe for concurrent use (database/sql handles pooling). Open once per process.

func Open

func Open(path string) (*Store, error)

Open opens (and migrates) the store at path. The parent directory is created if missing. If path is empty, ~/.deepseek/sessions.db is used.

func (*Store) AppendMessage

func (s *Store) AppendMessage(ctx context.Context, sessionID string, m Message) (int, error)

AppendMessage inserts a message at the next available index. The caller is expected to construct a Message in send-order; idx is auto-assigned (length of current messages).

func (*Store) AppendReceipt

func (s *Store) AppendReceipt(ctx context.Context, sessionID string, kind ReceiptKind, payload json.RawMessage) (int64, error)

AppendReceipt adds a transcript receipt to the session. The seq is auto-assigned as max(seq)+1 for the session. The payload must be valid JSON.

func (*Store) Close

func (s *Store) Close() error

Close releases the DB handle.

func (*Store) CountMessages

func (s *Store) CountMessages(ctx context.Context, sessionID string) (int, error)

ReplaceWithCompaction atomically collapses messages in [fromIdx, toIdx) into a single synthetic assistant message containing the summary, then renumbers later messages so idx stays contiguous. The role is assistant (not system) so a session compacted under T4.3+ replays the same body-message shape the live agent uses — one system message at the head, the summary in the body. (Sessions compacted under older code persisted a role='system' summary row; LoadMessages replays that verbatim, so a legacy summary still re-enters as a mid-body system message. That is tolerated by DeepSeek; it is simply the pre-T4.3 shape, not the new guarantee.)

Snapshots in this project are filesystem-backed (no SQL table to UPDATE), so step_idx shifting for snapshots lives in snapshots.Manager rather than here — see T-217.

Returns the idx of the inserted summary row (== fromIdx). A no-op call (fromIdx == toIdx) returns fromIdx with nil error. toIdx larger than the current message count is clamped to count+1. CountMessages returns the number of persisted messages for the session. /undo reconcile (T3.5) uses it to verify the in-memory transcript is index-aligned with disk before truncating disk.

func (*Store) GetSession

func (s *Store) GetSession(ctx context.Context, id string) (Session, error)

GetSession reads one session by ID.

func (*Store) LatestInProject

func (s *Store) LatestInProject(ctx context.Context, projectPath string) (Session, error)

LatestInProject returns the most recently used session whose workspace fingerprint matches projectPath. Unlike MostRecentInProject, this uses the canonical fingerprint so symlinked or renamed paths match.

func (*Store) ListAll

func (s *Store) ListAll(ctx context.Context) ([]Session, error)

ListAll returns all sessions globally, newest first. Used by `dsc -r`.

func (*Store) ListByProject

func (s *Store) ListByProject(ctx context.Context, projectPath string) ([]Session, error)

ListByProject returns all sessions for a project, newest first.

func (*Store) ListReceipts

func (s *Store) ListReceipts(ctx context.Context, sessionID string) ([]TranscriptReceipt, error)

ListReceipts returns all receipts for a session in sequence order. Returns an empty slice (not nil) for sessions with no receipts.

func (*Store) LoadMessages

func (s *Store) LoadMessages(ctx context.Context, sessionID string) ([]Message, error)

LoadMessages returns the message list for a session in idx order. Does NOT replay parents — use Replay for that.

Blocks resolution: if the v2 blocks column is non-empty, UnmarshalBlocks restores the typed slice. Otherwise the row is pre-v2 and rebuildBlocksFromLegacy synthesizes blocks from content/reasoning/tool_calls so callers see a uniform Blocks view regardless of when the row was written.

func (*Store) MostRecentInProject

func (s *Store) MostRecentInProject(ctx context.Context, projectPath string) (Session, error)

MostRecentInProject returns the most recently used session for a given project path, or sql.ErrNoRows if none.

func (*Store) NewBranch

func (s *Store) NewBranch(ctx context.Context, parentID string, branchPoint int) (Session, error)

NewBranch creates a child session referencing (parentID, branchPoint). Messages are NOT copied — Replay() walks parents at read time.

func (*Store) NewSession

func (s *Store) NewSession(ctx context.Context, projectPath, model string, duetEnabled bool) (Session, error)

NewSession inserts a new (root) session and returns its row. Use NewBranch for child sessions.

func (*Store) Path

func (s *Store) Path() string

Path returns the on-disk DB path; useful for diagnostics.

func (*Store) PruneOlderThan

func (s *Store) PruneOlderThan(ctx context.Context, older time.Duration) (int64, error)

PruneOlderThan deletes sessions older than the given duration. The cascade also removes their messages.

func (*Store) ReplaceWithCompaction

func (s *Store) ReplaceWithCompaction(ctx context.Context, sessionID string, fromIdx, toIdx int, summary string) (int, error)

func (*Store) Replay

func (s *Store) Replay(ctx context.Context, sessionID string) ([]Message, error)

Replay returns the full effective message history for a session, walking parents and concatenating up to each branch_point.

Concretely, for a chain:

root          (messages 0..N)
 └── child A  (branch_point=5, own messages → effective: root[0..4] + A[0..M])
     └── child B  (branch_point=3, own → effective: root[0..4] + A[0..2] + B[...])

We never copy messages, so storage stays O(unique) even for deeply branched experiment trees.

func (*Store) TouchLastUsed

func (s *Store) TouchLastUsed(ctx context.Context, id string) error

TouchLastUsed updates last_used_at to now. Call on every meaningful interaction so the auto-resume heuristic stays accurate.

func (*Store) TruncateMessages

func (s *Store) TruncateMessages(ctx context.Context, sessionID string, keepCount int) (int, error)

TruncateMessages deletes all messages with idx >= keepCount for the session. The deleted tail is contiguous from keepCount, so no renumbering is needed. Used by /undo reconcile (T3.5) to make disk match the rewound in-memory transcript. keepCount <= 0 clears all messages; returns the rows removed.

func (*Store) UpdateModel

func (s *Store) UpdateModel(ctx context.Context, id, model string) error

UpdateModel persists a /models switch.

func (*Store) UpdateSummary

func (s *Store) UpdateSummary(ctx context.Context, id, summary string) error

UpdateSummary persists an autogenerated short title.

func (*Store) UsageSummary added in v0.3.4

func (s *Store) UsageSummary(ctx context.Context, sessionID string) (UsageSummary, error)

UsageSummary returns the aggregated usage from persisted assistant messages for the given session. Only rows with at least one usage token column > 0 are counted as turns.

type TranscriptReceipt

type TranscriptReceipt struct {
	SessionID string
	Seq       int64
	Kind      ReceiptKind
	Payload   json.RawMessage
}

TranscriptReceipt is an append-only entry in the transcript log.

type UsageSummary added in v0.3.4

type UsageSummary struct {
	Turns           int
	CacheHitTokens  int
	CacheMissTokens int
	OutputTokens    int
	CostYuan        float64
	SavedYuan       float64
}

UsageSummary aggregates persisted assistant-message usage for a session, enabling resumed sessions to restore cost/cache totals.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL