session

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const SnapshotMarker = "-pre-summary-"

SnapshotMarker tags the pre-compaction history dumps written alongside sessions as "<id>-pre-summary-<stamp>.json". Those files live in the sessions directory but are NOT sessions: their payload is keyed session_id/captured/messages, so decoding one into a Session yields an empty ID and a zero Created while still populating Messages.

Exported so the writer (internal/tui's writePreSummarySnapshot) and the directory scans here name the pattern once. When they disagreed, every snapshot showed up in `sessions list` and the /sessions picker as a blank-id row — and selecting one called Load(""), which fell through to a name lookup that matched the first unnamed session on disk and silently resumed the wrong conversation.

View Source
const SummaryPreamble = "Summarize our conversation so far so we can continue with bounded context."

SummaryPreamble is the synthetic user turn that a compacted history opens with, standing in for the real conversation the summary replaced. Defined here (and referenced by the compaction code that writes it) so Summary can recognize and skip it: it's identical in every compacted session, so using it as a session's description would label them all the same.

Variables

View Source
var ErrNoSessionInCwd = errors.New("no saved session in this directory")

ErrNoSessionInCwd is returned by LatestInCwd when no saved session has a Cwd field matching the requested directory. Sentinel so callers can present a friendly "no prior session in this directory" error without string matching.

Functions

func ExportMarkdown

func ExportMarkdown(s *Session) string

ExportMarkdown serializes a Session into a human-readable markdown document — useful for sharing a working session in a PR description, an issue, or just archiving. We deliberately omit the system message (boilerplate plus injected memory, not interesting to a reader) and fence tool output for clarity.

Living in the session package (rather than internal/tui) lets the non-interactive cobra subcommands call it without dragging the bubbletea/lipgloss dependency tree into the CLI binary's non-interactive paths.

func IsSnapshotID added in v0.4.0

func IsSnapshotID(id string) bool

IsSnapshotID reports whether a reference names an archived snapshot rather than a live session. Exported so the TUI can label what it loaded.

Types

type CompactionRecord added in v0.4.0

type CompactionRecord struct {
	At     time.Time `json:"at"`
	Before int       `json:"before"`
	After  int       `json:"after"`
	Auto   bool      `json:"auto,omitempty"`
}

CompactionRecord is one firing of this session's turn-boundary auto-summarize (or manual /summarize). Before/After are the estimated token counts immediately before and after the rewrite — the same figures already surfaced in the scrollback receipt, just persisted here too.

type ListOptions added in v0.4.0

type ListOptions struct {
	IncludeArchived bool
	// Offset skips this many newest rows before returning results. It lets TUI
	// pickers page through large session stores without decoding every session
	// file before the first screen can render.
	Offset int
	// Limit caps returned rows. A non-positive limit preserves the legacy full list.
	Limit int
}

ListOptions tunes what List returns.

IncludeArchived adds one row per snapshotted session for its richest pre-compaction archive. It is OPT-IN because an archived row is not an ordinary session: its id resolves through loadSnapshot, so Load returns a freshly-minted session rather than the row you asked for. Callers that treat List output as "the set of live sessions" — recall.Backfill keying its index and prune by id, `yottacode sessions list` feeding scripts — break subtly when handed archives. Only the /sessions picker, which knows to render and resume them differently, asks for them.

type Session

type Session struct {
	ID       string            `json:"id"`
	Name     string            `json:"name,omitempty"`
	Model    string            `json:"model"`
	Created  time.Time         `json:"created"`
	Cwd      string            `json:"cwd"`
	Messages []adapter.Message `json:"messages"`
	// Todos is the working plan written by the todo_write tool. Omitted
	// from JSON when empty so older session files load unchanged.
	Todos []agent.Todo `json:"todos,omitempty"`
	// Worktree is the yottacode-managed worktree name this session was
	// launched in (via `yottacode --worktree <name>`). Empty for sessions
	// running against the main checkout. Stored so `sessions resume`
	// lands back in the correct worktree dir even if the user moved or
	// renamed the repo. Omitted from JSON when empty so existing session
	// files load unchanged.
	Worktree string `json:"worktree,omitempty"`
	// TotalUsage is the cumulative token tally across every assistant
	// turn in this session. Written by AddUsage on each EventDone.
	// Omitted from JSON when zero so existing session files load
	// byte-identical until the first usage is recorded.
	TotalUsage adapter.Usage `json:"total_usage,omitzero"`
	// ModelUsage is per-model breakdown — users mix models within a
	// session (Anthropic for code review, Gemini for grep, etc.) and
	// the cost calculator needs to know which model produced each
	// turn. Keyed by model ID exactly as the adapter reported it.
	ModelUsage map[string]adapter.Usage `json:"model_usage,omitempty"`
	// SubagentTasks is the persisted index of this session's subagent runs.
	// The live registry (internal/subagents) is in-memory and rebuilt empty
	// each launch; persisting a summary lets get_subagent_result and
	// /subagents resolve task-ids the model wrote into the conversation in a
	// prior session, and lets a startup sweep reclaim a crashed session's
	// empty dispatch worktrees. Omitted when empty so existing session files
	// load unchanged.
	SubagentTasks []subagents.TaskRecord `json:"subagent_tasks,omitempty"`
	// ToolStats is a per-tool-name breakdown of main-thread tool calls —
	// count, approximate output tokens, and error count. Keyed by tool
	// name. Deliberately main-thread only (mirrors TotalUsage/ModelUsage):
	// subagent tool calls stay a single count on subagents.Task.ToolCalls,
	// not broken out by name. Omitted from JSON when empty so existing
	// session files load unchanged.
	ToolStats map[string]ToolStat `json:"tool_stats,omitempty"`
	// CompactionEvents records each time this session's history was
	// compacted, from either mechanism: the TUI's turn-boundary
	// auto-summarize (or manual /summarize), and the main loop's own
	// in-loop agent.ContextCompacted mid-turn self-compaction (wired
	// whenever agentruntime.Build resolves a compaction window — not
	// subagent-only, contrary to an earlier assumption here). Both record
	// into the same slice so /usage can explain otherwise-invisible token
	// spend from re-summarization regardless of which path fired. Omitted
	// from JSON when empty.
	CompactionEvents []CompactionRecord `json:"compaction_events,omitempty"`
	// RestoredFrom records the archived snapshot this session was seeded
	// from, when it was restored rather than started fresh. Provenance only
	// — the restored session is a full independent session from here on.
	RestoredFrom string `json:"restored_from,omitempty"`
	// contains filtered or unexported fields
}

Session is one resumable conversation persisted as JSON in ~/.yottacode/sessions/<id>.json.

Name is an optional human-readable label set via the /sessions picker's Rename action. It's a soft alias: Load will fall back to a Name match if the requested id doesn't resolve to a file.

func LatestInCwd added in v0.2.0

func LatestInCwd(cwd string) (*Session, error)

LatestInCwd returns the most recent saved session whose Cwd matches the given directory. Used by `yottacode --continue` (mirroring Claude Code's --continue) to skip the picker and resume the directory's last session directly. Returns an error wrapping errNoSessionInCwd when no saved session matches.

"Most recent" is determined by sorting all matches descending by Session.Created, falling back to the timestamp-prefixed ID when two sessions share an identical Created (test fixtures). The Cwd comparison is exact-string match — symlinked or differently-resolved paths won't unify; users hit by that should pass the matching path explicitly.

func Load

func Load(id string) (*Session, error)

Load reads a stored session by id (filename match) or name (Name field match). The legacy "last" keyword shortcut was retired alongside the /resume slash command — the /sessions picker (and the `yottacode sessions resume <id|name>` cobra subcommand) is the canonical path for "load the most recent" now, with the picker defaulting the cursor to the newest entry.

func New

func New(model, cwd string) (*Session, error)

New starts a fresh session and reserves its file path.

func (*Session) AddToolStat added in v0.4.0

func (s *Session) AddToolStat(name string, outputChars int, errored bool)

AddToolStat records one main-thread tool call into the session's per-tool breakdown. outputChars is the raw length of the tool result string; converted to an approximate token count here so renderers don't repeat the conversion. Safe to call with a nil receiver or empty name.

func (*Session) AddUsage added in v0.3.0

func (s *Session) AddUsage(model string, u *adapter.Usage)

AddUsage records the per-turn usage that just landed on an assistant message into the session's running totals. Safe to call with a nil receiver or nil usage — both branches no-op. Caller is responsible for Save() if the new totals should be persisted now; most callers persist on the same cadence as Messages.

func (*Session) HasExchange added in v0.4.0

func (s *Session) HasExchange() bool

HasExchange reports whether the session holds at least one user or assistant message — i.e. whether there is any conversation to come back to. System-only sessions don't count: launching yottacode composes a system prompt immediately, so a session that was opened and closed without a single turn is a ~48KB shell whose entire content is the system prompt.

This is the "is this worth persisting / worth offering as resumable" predicate, and it gates three places: the at-exit save (don't create shells), LatestInCwd (`--continue` must not land on one), and List (the /sessions picker must not offer one). Resuming a shell looks like data loss to the user — the picker says "1 msgs" and the history is simply gone — so the fix is to never create or offer them.

func (*Session) RecordCompaction added in v0.4.0

func (s *Session) RecordCompaction(before, after int, auto bool)

RecordCompaction appends one compaction event to the session's history. Safe to call with a nil receiver.

func (*Session) Save

func (s *Session) Save() error

Save atomically writes the session to disk.

The temp file gets a unique name (os.CreateTemp) rather than a fixed "<path>.tmp" suffix: two yottacode processes editing the same session (e.g. two terminals, or one `--continue` alongside a `--resume`) would otherwise write to the same temp path and clobber each other's in-flight write, so one process's history would be silently lost on rename. A per-write unique temp name makes concurrent saves last-writer-wins on the final file instead of corrupting it.

func (*Session) SubagentUsage added in v0.4.0

func (s *Session) SubagentUsage() SubagentUsageRollup

SubagentUsage returns this session's subagent token rollup, attributing inherited-model runs to the session's headline model. Nil-safe.

func (*Session) Summary added in v0.4.0

func (s *Session) Summary() string

Summary returns a one-line gist of what the session is about, taken from the first real user prompt. Empty when there's nothing usable.

Derived rather than stored, so it works on every session already on disk with no migration, and it costs nothing extra: List already decodes the full message log.

Skips the synthetic compaction preamble, and collapses all whitespace so a multi-line prompt renders as a single scannable line.

type SessionInfo

type SessionInfo struct {
	ID       string
	Name     string
	Model    string
	Created  time.Time
	Messages int
	// Worktree is the yottacode worktree name this session ran in, or
	// empty for the main checkout. Surfaced in `yottacode sessions list`
	// output so users can tell which sessions belong to which worktree.
	Worktree string
	// Summary is the one-line gist from the session's first real user
	// prompt — the "what was this one about?" that an id and a timestamp
	// can't answer. See Session.Summary. Empty when nothing usable.
	Summary string
	// Archived marks a pre-compaction snapshot rather than a live session.
	// Loading one restores its history into a NEW session; the archive
	// itself is never written to. ArchivedOf names the session it was
	// captured from (which may no longer exist).
	Archived   bool
	ArchivedOf string
}

SessionInfo is a metadata-only view returned by List.

func List

func List() ([]SessionInfo, error)

List returns every saved live session's metadata, newest first. Doesn't load the full message log to keep this cheap for the /sessions slash command. Archived snapshots are excluded — see ListWith.

func ListPage added in v0.4.0

func ListPage(opts ListOptions, offset, limit int) ([]SessionInfo, error)

ListPage returns one newest-first page of session metadata. It is a named wrapper around ListWith so UI code reads as pagination instead of a full-list scan followed by slicing.

func ListWith added in v0.4.0

func ListWith(opts ListOptions) ([]SessionInfo, error)

ListWith is List with explicit options.

type SessionUsageSummary added in v0.3.0

type SessionUsageSummary struct {
	ID            string
	Name          string
	Model         string
	Created       time.Time
	TotalUsage    adapter.Usage
	ModelUsage    map[string]adapter.Usage
	SubagentTasks []subagents.TaskRecord
}

SessionUsageSummary is a stripped per-session view used by the daily-rollup scan. We avoid decoding Messages (the heavy field) so /usage can scan dozens of session files cheaply.

func UsageSince added in v0.3.0

func UsageSince(t time.Time) ([]SessionUsageSummary, error)

UsageSince scans every saved session newer than t and returns a per-session usage summary. Decodes only the lightweight metadata + usage fields — Messages stay on disk, keeping the scan cheap. Sessions older than t are filtered out by Created; sessions with no usage data still appear so the daily rollup can show "N sessions, no token data yet."

func (SessionUsageSummary) SubagentUsage added in v0.4.0

func (s SessionUsageSummary) SubagentUsage() SubagentUsageRollup

SubagentUsage returns the subagent token rollup for this summary — the daily rollup adds it to TotalUsage so cross-session tallies include subagent spend, not just the main thread.

type SubagentUsageRollup added in v0.4.0

type SubagentUsageRollup struct {
	Total      adapter.Usage
	ByModel    map[string]adapter.Usage
	AgentCount int // number of subagents that reported non-zero usage
}

SubagentUsageRollup aggregates a session's subagent token spend from its persisted SubagentTasks index. It is kept DISTINCT from TotalUsage / ModelUsage — those track only the main assistant thread (session.AddUsage is called solely from the main loop) — so /usage can attribute spend to subagents separately and still present a combined total. Subagent turns that inherited the parent's model (empty Model on the record) are attributed to parentModel so the per-model breakdown stays meaningful.

type ToolStat added in v0.4.0

type ToolStat struct {
	Count        int   `json:"count"`
	OutputTokens int64 `json:"output_tokens,omitempty"`
	Errors       int   `json:"errors,omitempty"`
}

ToolStat is one tool's accumulated main-thread call stats: how many times it ran, its approximate combined output size in tokens (4-chars/token heuristic, matching the estimate agent/compaction.go and cmd_summarize.go already use for budgeting), and how many calls errored.

Jump to

Keyboard shortcuts

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