session

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package session records what an agent was doing, so a different agent can pick it up.

Memory answers "what do you know about X". That is only half of continuity — the other half is "where did we stop, and what had we already ruled out". An agent that has to rediscover the three approaches that failed yesterday is not resuming, it is starting over with a warmer greeting.

The split mirrors git, which is the shape brain is aiming at:

working notes  →  SQLite   →  the working tree. Cheap, frequent, disposable.
checkpoints    →  the vault →  commits. Durable, readable, diffable.

So a running agent scribbles freely with AddNote and pays nothing, and Commit is the deliberate act that turns that scribbling into a markdown note the vault owns. Working notes are lost if the index is rebuilt; that is not a bug, it is the definition of uncommitted. Checkpoints survive, because the file is the record — see checkpoint.go, which reads them back off disk rather than trusting a table.

Index

Constants

View Source
const AbandonAfter = 6 * time.Hour

AbandonAfter is how long an open session may sit with no new activity before it counts as abandoned rather than merely paused. Long enough that a lunch break, an overnight stop, or a slow afternoon is not flagged; short enough that a session that died mid-task is caught the same day rather than discovered a month later when nobody remembers what it was doing.

View Source
const CheckpointDir = "sessions"

CheckpointDir is where checkpoints live inside the vault. A visible folder, not a dotfile: these are notes you are meant to be able to open, read, and put under version control alongside everything else.

View Source
const NotesFile = "uncommitted.md"

NotesFile is the working-notes file inside a scope's session directory.

View Source
const PlanDir = "plans"

PlanDir nests under a project's own checkpoint directory — sessions/<project>/plans — rather than beside it, so a plan travels with the continuity of the project that produced it and inherits the same worktree scoping checkpoints already have (see safeScope). Nesting also keeps it out of History's and latestCheckpoint's checkpoint-only scans for free: History reads its directory non-recursively and skips subdirectories, and latestCheckpoint's recursive walk filters on IsCheckpointFile, which this directory's filenames are built to never match — see the naming note on planFilename.

View Source
const QuietAfter = 14 * 24 * time.Hour

QuietAfter is how long since the last checkpoint before a project reads as having gone quiet rather than simply between sessions. Two weeks: long enough to survive a normal gap between working stretches on a side project, short enough that a project going genuinely stale is caught within a month.

View Source
const Schema = `` /* 631-byte string literal not displayed */

Variables

This section is empty.

Functions

func Commit

func Commit(db *sql.DB, vaultDir string, c *Checkpoint) error

Commit writes the checkpoint to the vault and closes the project's open sessions. This is the commit in "working tree, then commit": until it is called, everything the agent recorded lives only in the rebuildable index and is not really saved.

The project's uncommitted working notes are folded in — including any left by an agent that died mid-task — so an agent that only ever called note_progress still produces a useful checkpoint instead of an empty one.

Project is a scope, so "kestrel/feature-x" writes into sessions/kestrel/feature-x/ and closes only that worktree's sessions. The caller decides the scope, exactly as it decides the project — see internal/mcpserver/scope.go.

func ImportNotes added in v0.1.3

func ImportNotes(db *sql.DB, vaultDir string) (int, error)

ImportNotes restores working notes from the vault into an empty index.

Called by `brain index`, which is the command whose whole promise is that the cache can be rebuilt from the vault. It adds only what is missing: a note already in the index is left alone, matched on its scope, text and timestamp, so running index twice does not double anything.

It never deletes. A note present in the index and absent from the file is not evidence of a deletion — it is what a note written by a running agent since the last flush looks like, and the safe reading of an ambiguity here is the one that keeps the work.

Returns how many notes it restored.

func Init

func Init(db *sql.DB) error

func IsCheckpointFile added in v0.2.0

func IsCheckpointFile(name string) bool

IsCheckpointFile decides whether a file in a session directory is one of ours.

Not every .md here is a checkpoint: uncommitted.md holds working notes, and a user is free to leave a stray file of their own beside their history. The name is the test because the name is also the sort key — "most recent" is a reverse sort of these filenames, so anything not stamped with a timestamp does not merely add a bad row, it sorts to the front and becomes what `resume` reports as the last thing that happened.

func OpenCount added in v0.1.3

func OpenCount(db *sql.DB) (int, error)

OpenCount reports how many sessions, across every project, are still open — started but not yet folded into a checkpoint by Commit. It is what lets a caller say "capture is live" from real state rather than from the mere presence of a process, the way a terminal-style inspector needs to.

func Projects

func Projects(vaultDir string) ([]string, error)

Projects lists the projects that have at least one checkpoint. Worktree scopes are folders inside a project's, so they are not listed as projects of their own — which is what they are not.

func SavePlan added in v0.2.0

func SavePlan(vaultDir string, p Plan) (string, error)

SavePlan writes an approved plan to the vault and returns its slug.

Called from the ExitPlanMode hook path, which runs on the critical path of every plan a user approves and must never block or fail loudly — so unlike Checkpoint.Commit this has no session to close and no index to touch; it only ever appends one new file. `brain index` picks it up on the next run the same as any other vault note.

func SetTask

func SetTask(db *sql.DB, id, task string) error

SetTask records what the session is for. Agents usually learn this a few turns in, not at the start, so it is a separate call rather than required up front.

func SetVault added in v0.1.3

func SetVault(db *sql.DB, dir string)

SetVault binds a database to the vault its notes belong in. An empty dir unbinds it, which is what a closing index does and what a test that only cares about the cache wants.

Types

type Abandoned added in v0.1.3

type Abandoned struct {
	Session string
	Project string
	Agent   string
	Task    string
	// Notes is how much work is sitting in it — the reason this matters. Zero
	// is still worth reporting: a session opened and never used is a smaller
	// story than one holding findings, but it is still an agent that did not
	// hand off cleanly.
	Notes int
	// LastActivity is the last note's timestamp, or the session's start time if
	// it never got one. Measuring from here rather than from Started is what
	// keeps a long but continuously-active session from being flagged: what
	// matters is silence, not duration.
	LastActivity int64
}

An Abandoned session is one that opened, may or may not have accumulated notes, and then went silent past AbandonAfter without ever being committed.

func FindAbandoned added in v0.1.3

func FindAbandoned(db *sql.DB, after time.Duration) ([]Abandoned, error)

FindAbandoned returns every open session across the whole vault that has gone silent for longer than after. This is the query the health check and the continuity report both run — the vault-wide view of "what got dropped".

func FindAbandonedInProject added in v0.1.3

func FindAbandonedInProject(db *sql.DB, project string, after time.Duration) ([]Abandoned, error)

FindAbandonedInProject narrows FindAbandoned to one project — the view `brain sessions <project>` surfaces, where an agent (or a person) is looking at one piece of work rather than the whole vault.

type Checkpoint

type Checkpoint struct {
	Session   string
	Project   string
	Agent     string
	Task      string
	State     string
	Decisions []string
	Failed    []string
	// The verified half. Failed says what was ruled out; these three say what the
	// next agent may stand on without checking it again.
	//
	// Prose cannot carry this. "Auth is done" is a claim, and it reads the same
	// whether a test demonstrated it or the last agent believed it while its
	// context was running out. An arriving agent that cannot tell those apart
	// either re-verifies everything or builds on a sentence, and the second is
	// how a session inherits a foundation that was never true.
	//
	// So a verified entry names its evidence alongside the claim ("rejects
	// expired tokens — go test ./internal/auth -run TestExpiry"), Commands are
	// the invocations that produced it, and Blockers are the standing refutation:
	// what is known broken, and what it stops.
	Verified  []string
	Blockers  []string
	Commands  []string
	Questions []string
	Files     []string
	Next      string
	// Git is the repository as it stood, read rather than reported. The half of
	// a checkpoint an agent cannot forget to fill in — see the comment in
	// Commit, and package gitstate.
	Git gitstate.State
	// HandoffTo names who this was left for. Empty for a plain checkpoint —
	// the difference is intent, not mechanism.
	HandoffTo string
	// AutoClosed marks a checkpoint nobody wrote — see CloseAbandoned. It exists
	// so this is never confused with a real handoff: an agent (or a person)
	// reading one of these later must be able to tell, from the file itself,
	// that the work was simply abandoned rather than deliberately wrapped up.
	AutoClosed bool
	Slug       string // vault slug, set once written
	TS         int64
}

A Checkpoint is where an agent stopped, written down well enough that a different agent can start.

Failed is the field that earns its keep. Anyone can restate the goal; the expensive knowledge is the three approaches already ruled out, and it is exactly what gets lost when a session ends. A checkpoint without it saves the next agent a paragraph of reading and costs it an afternoon of rediscovery.

func CloseAbandoned added in v0.4.2

func CloseAbandoned(db *sql.DB, vaultDir, id string) (Checkpoint, error)

CloseAbandoned resolves one session doctor has flagged abandoned: it writes a minimal checkpoint for it, marked AutoClosed so it can never be mistaken for a real handoff, and marks that one session closed.

Refuses anything not already past AbandonAfter — the real failure mode this exists to guard against is closing a session that is merely slow, not dead. Rather than trust the caller to have checked FindAbandonedInProject first, this runs the same query itself and refuses if the id is not in it, so the guarantee holds regardless of what the caller looked at.

Only the named session closes. A project can have more than one open session — a different agent's own in-progress work — and this must not fold a stranger's notes into a checkpoint it never asked for, or mark its session ended out from under it. See Commit, which closes every open session for a project on purpose: that is right for a checkpoint the project's own agent is writing about its own working tree, and wrong here, where the caller is resolving one specific session on someone else's behalf.

func History

func History(vaultDir, project string, n int) ([]Checkpoint, error)

History returns up to n checkpoints for a project, newest first.

func Latest

func Latest(vaultDir, project string) (*Checkpoint, error)

Latest returns the most recent checkpoint for a project, or nil if there is none.

It reads the vault directory rather than a table on purpose. Checkpoints are the one thing here that must survive `rm -rf .brain` — if resume depended on the index, the markdown would be a souvenir rather than the record. Filenames begin with a sortable timestamp, so "most recent" is a sort, not a query.

func ParseCheckpoint

func ParseCheckpoint(raw string) Checkpoint

ParseCheckpoint reads a checkpoint back from its note. It is forgiving in the same way internal/vault is: a note that has been hand-edited into a shape we did not write should still yield whatever is recognisable, because the alternative is silently losing the record.

func (Checkpoint) Empty

func (c Checkpoint) Empty() bool

func (Checkpoint) Markdown

func (c Checkpoint) Markdown(follows string) string

Markdown renders the checkpoint as a vault note. follows is the session id of the previous checkpoint, linking the chain backwards; empty for the first.

type Mention

type Mention struct {
	Checkpoint
	// Matched is the path as the checkpoint recorded it, which is rarely
	// character-for-character what the caller asked about.
	Matched string
}

A Mention is one checkpoint that touched a path, and what was being worked out at the time.

func Touching

func Touching(vaultDir, path string, limit int) ([]Mention, error)

Touching finds every checkpoint whose Files list refers to path, newest first.

It searches every project rather than one, because the question "why is this file like this" does not come with a project attached — the caller has a path and nothing else. A file touched by two projects is a real situation and both answers are wanted.

type Note

type Note struct {
	ID      int64
	Session string
	// Agent is denormalised from the session because notes outlive the agent
	// that wrote them — when work passes between two agents, which of them
	// found a thing is part of the finding.
	Agent string
	Text  string
	TS    int64
}

A Note is one line of progress inside a session. Deliberately unstructured: the moment this needs fields, agents will stop writing them.

func AddNote

func AddNote(db *sql.DB, project, agent, text string) (Note, error)

AddNote appends one line of progress to the project's open session.

func AddNoteAt

func AddNoteAt(db *sql.DB, project, agent, text string, ts int64) (Note, error)

AddNoteAt appends a note that happened at a given time rather than now.

Backdating exists for the cases where the writing and the doing are not the same moment: importing a trail from elsewhere, replaying a transcript, or a benchmark that needs "thirteen days ago" to actually be thirteen days ago. Anything that reasons about how old a note is depends on this being real.

func Notes

func Notes(db *sql.DB, sessionID string) ([]Note, error)

Notes returns a session's working notes, oldest first — the order they were written is the order they make sense in.

func Uncommitted

func Uncommitted(db *sql.DB, project string) ([]Note, error)

Uncommitted returns the working notes for a project that no checkpoint has captured yet — everything written into still-open sessions, whoever wrote them. This is the "uncommitted changes" half of resume: work that happened but was never written down properly, including the work of an agent that died before it could.

type Plan added in v0.2.0

type Plan struct {
	Project string
	Agent   string
	Text    string
	TS      int64
	Slug    string // vault slug, set once written
}

A Plan is what an agent proposed before any of it was carried out — the half of a session a Checkpoint cannot supply, because a checkpoint is written after the fact and records what happened, not what was approved beforehand. Claude Code's plan mode already produces this text and a person already reviewed it; today it is discarded the moment the session that wrote it ends, and the next agent to resume sees only the checkpoint's one-sentence Next field.

func ListPlans added in v0.2.0

func ListPlans(vaultDir, project string) ([]Plan, error)

ListPlans returns a project's captured plans, newest first. This is what `brain plans <project>` reads, and what proves a plan the hook saved is actually reachable rather than write-only.

func ParsePlan added in v0.2.0

func ParsePlan(raw string) Plan

ParsePlan reads a plan note back. Markdown and ParsePlan are inverses, the same contract Checkpoint's vault note keeps, and for the same reason: it is what lets a plan survive a rebuilt index.

func (Plan) Empty added in v0.2.0

func (p Plan) Empty() bool

func (Plan) Markdown added in v0.2.0

func (p Plan) Markdown() string

Markdown renders the plan as a vault note: indexed by `brain index` like any other, reachable by `why` and `recall` through the plan_of relation, exactly the way a checkpoint reaches its project through checkpoint_of.

type ProjectContinuity added in v0.1.3

type ProjectContinuity struct {
	Project string
	// Checkpoints is how many exist in total; LastCheckpoint and LastAgent
	// describe the most recent one. LastCheckpoint is 0 when there has never
	// been one — a project with notes or sessions but no handoff yet.
	Checkpoints    int
	LastCheckpoint int64
	LastAgent      string
	// Uncommitted is working notes recorded since the last checkpoint (or ever,
	// if there is none) that have not been folded into one yet.
	Uncommitted int
	// Abandoned is open sessions that have gone silent past AbandonAfter — see
	// abandon.go. A project can have both a healthy checkpoint history and an
	// abandoned session sitting alongside it; the two are independent signals.
	Abandoned int
}

ProjectContinuity is one project's continuity health.

func AllContinuity added in v0.1.3

func AllContinuity(db *sql.DB, vaultDir string) ([]ProjectContinuity, error)

AllContinuity reports on every project with any continuity footprint at all — a checkpoint, or a session, committed or not — sorted most recently active first, with projects that have never checkpointed last. That order puts what needs attention at the bottom of a normal-length vault rather than requiring a second pass to find it.

The project set comes from two places because either alone misses a case that matters: Projects(vaultDir) lists whoever has a checkpoint, and misses a project an agent is actively working that has not committed yet — which is exactly the project this report most needs to surface. The sessions table lists whoever has a session, open or closed, and misses a project someone imported checkpoints for without ever running brain through it locally.

func Continuity added in v0.1.3

func Continuity(db *sql.DB, vaultDir, project string) (ProjectContinuity, error)

Continuity reports one project's continuity health.

func (ProjectContinuity) Quiet added in v0.1.3

func (pc ProjectContinuity) Quiet() bool

Quiet reports whether this project's continuity has gone stale: no checkpoint at all, or none within QuietAfter.

type Session

type Session struct {
	ID string // "20260814-143207-claude", lexically sortable by time
	// Project is the scope the work is filed under: a project name, or
	// "project/worktree" when it is happening in a linked git worktree. Two
	// parallel worktrees are the same codebase and the same memory, but not the
	// same working stretch, and resuming into the other one's checkpoint is a
	// wrong handoff rather than an untidy one.
	Project string
	Agent   string // free text: "claude", "cursor", "codex", "pragun"
	Task    string
	Started int64
	Ended   int64  // 0 while open
	Slug    string // vault slug of the checkpoint, once committed
}

A Session is one agent's working stretch on one project. It exists to give working notes something to hang off and to record who was at the keyboard — a handoff is meaningless without knowing who is handing off.

func Current

func Current(db *sql.DB, project, agent string) (Session, error)

Current returns this agent's open session for a project, opening one if there is none. Agents call note_progress without ceremony; making them manage a session lifecycle would mean they simply wouldn't.

Scoped to the agent, not just the project. A session is one agent's working stretch: when a previous agent died without committing, its session is still open, and handing it to whoever arrives next would file their checkpoint under the dead agent's id — so the record of a handoff would name the wrong author. Their uncommitted notes are still picked up; see Commit.

func Get

func Get(db *sql.DB, id string) (Session, bool, error)

Get loads one session by id.

func Start

func Start(db *sql.DB, project, agent, task string) (Session, error)

Start opens a session. Agent defaults to "agent" rather than erroring: a caller that cannot be bothered to identify itself should still be able to leave a trail, and an anonymous trail beats no trail.

Jump to

Keyboard shortcuts

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