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 logos 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
- func ActivityLogStateFor(id string) string
- func ClaimsDoneUnverified(c Checkpoint) bool
- func Commit(db *sql.DB, vaultDir string, c *Checkpoint) error
- func ConversationLine(c Conversation) string
- func DecisionKey(d string) string
- func DecisionsWithoutReason(decisions []string) int
- func DerivedFileAt(vaultDir, path string) bool
- func DropPlaceholders(failed []string) ([]string, int)
- func FindConversation(convs []Conversation, query string) (*Conversation, []Conversation, error)
- func FindTranscript(vaultDir, hostSession string) string
- func HistoryScope(vaultDir, project string) string
- func ImportNotes(db *sql.DB, vaultDir string) (restored, rescued, empty int, err error)
- func Init(db *sql.DB) error
- func IntentDropped(c Checkpoint, earlier []Checkpoint) bool
- func IsAbandonedClaim(rel string, info os.FileInfo) bool
- func IsCheckpointFile(name string) bool
- func IsClaimPath(rel string) bool
- func IsDerivedFile(rel string, head []byte) bool
- func IsPlaceholder(s string) bool
- func IsTranscriptFile(name string) bool
- func NameInside(given string, known []string) string
- func NextReadsAsMoreThanOneStep(next string) bool
- func NoteText(text string) string
- func OpenCount(db *sql.DB) (int, error)
- func Projects(vaultDir string) ([]string, error)
- func Reconfirm(vaultDir, scope, ruling, commit string) error
- func Refile(vaultDir string) (refiled, rejoined int, err error)
- func RemoveAbandonedClaims(vaultDir string, rels []string) (removed []string, err error)
- func RenderConversation(projectName string, c Conversation) string
- func ResolvePath(vaultDir, slug string) (string, error)
- func Retire(vaultDir, scope, ruling string) error
- func ReviewKey(text string) string
- func Reviews(vaultDir, scope string) (map[string]Review, error)
- func SafeScope(s string) string
- func SavePlan(vaultDir string, p Plan) (string, error)
- func Scopes(vaultDir string) ([]string, error)
- func SetTask(db *sql.DB, id, task string) error
- func SetVault(db *sql.DB, dir string)
- func SkippedAhead(vaultDir, project string, shown *Checkpoint) []string
- func Split(vaultDir, project string) string
- func SplitNotice(scope, old string) string
- func Stranded(vaultDir, project string) string
- func StrandedNotice(scope, old string) string
- func TranscriptDir(vaultDir, project, hostSession string) (string, error)
- func Unflushed(db *sql.DB) (int, error)
- type Abandoned
- type Checkpoint
- func ByHostSession(vaultDir, hostSession string) ([]Checkpoint, error)
- func CloseAbandoned(db *sql.DB, vaultDir, id string) (Checkpoint, error)
- func GrowAuto(vaultDir string, old, c Checkpoint) (Checkpoint, error)
- func History(vaultDir, project string, n int) ([]Checkpoint, error)
- func InferAuto(vaultDir, slug string, in Inference) (Checkpoint, error)
- func IntentFor(c Checkpoint, earlier []Checkpoint) (string, *Checkpoint)
- func Latest(vaultDir, project string) (*Checkpoint, error)
- func ParseCheckpoint(raw string) Checkpoint
- func WriteAuto(vaultDir string, c Checkpoint) (Checkpoint, error)
- type CheckpointEntry
- type Conversation
- type Inference
- type Mention
- type MigrateReport
- type Note
- type Plan
- type ProjectContinuity
- type Review
- type Session
Constants ¶
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.
const ActivityLogState = "Built from the activity log when the session ended without a checkpoint."
ActivityLogState is what an auto record built from the activity log said before it named the session it came from. The sweep still matches it, by time, so records written before that are not recorded a second time.
const AutoLabel = "auto — not written by an agent, unverified"
AutoLabel is what an auto checkpoint says about itself, in the file and wherever it is shown.
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.
const ConvosDir = "_convos"
ConvosDir is the reserved folder name directly under a scope — a project, or a project/worktree pair — that holds its conversations. Reserved, not inferred: a worktree folder and a conversation folder sit at the exact same depth and look identical on disk (both are just a directory name under a project), so without one fixed, never-a-worktree name marking where the conversations start, Scopes could not tell a sibling worktree from one of this project's own conversations, and would either miss real worktrees once their checkpoints moved a level deeper, or list every conversation folder as a phantom worktree scope of its own.
const DecisionWindow = 200
DecisionWindow is how many of a project's newest checkpoints decision promotion reads, and resume counts a promoted decision over. Every checkpoint and every resume reads them, and a year of checkpoints is thousands of files; a decision the last 200 never recorded is not one the project is still holding.
const IntentDepth = 50
IntentDepth is how far back a checkpoint's reason is looked for. Resume reads only a handful of checkpoints for everything else, but a task's reason is stated once at its start, and a long task outlives that window by its fifth save — after which resume would say what was being done and never why.
const LedgerFile = "ledger.md"
LedgerFile is the project's long-term history derived from its checkpoints (internal/ops/ledger.go). It sits beside reviewed.md for the same reason: IsCheckpointFile tells it apart by its name, so History never reads the ledger back as a checkpoint it was built from.
const LedgerType = "ledger"
LedgerType is the frontmatter type a ledger is written under. Derived files are told apart by it, not by their name: a sync client's conflict copy, "ledger (conflicted copy).md", is the same view under another name, and keying on "ledger.md" let it into the index and through a rename as if someone had written it.
const NotesFile = "uncommitted.md"
NotesFile is the working-notes file inside a scope's session directory.
const ParallelGrace = 30 * time.Minute
ParallelGrace is how long another agent's open session may sit silent before a checkpoint treats it as dead and takes its notes.
A checkpoint used to fold in and close *every* open session on the project. That is right for the agent that died mid-task — its findings are exactly what the next agent is building on, and they reach the durable record only if somebody else's checkpoint carries them. It is wrong for an agent that is still working: with four agents on one project, whichever checkpointed first took all four sets of notes, and the other three checkpoints had no State section at all. The handoff of everyone who finished second was filed under someone else's task.
The two cases look identical only if the index is assumed to have no liveness signal. It has one already: session_notes carries a timestamp, so "has said nothing for half an hour" is recorded without adding a heartbeat. An agent mid-task writes notes; an agent that is gone does not.
Shorter than AbandonAfter because they answer different questions. That one asks when a human should be *told* a session was dropped, and is deliberately long enough to sit through a lunch break. This one asks whether a parallel agent is still coming back for its own notes, and six hours of silence is far past the point where the answer is yes.
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.
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.
const ReviewedFile = "reviewed.md"
ReviewedFile records what `logos review --stale` decided about a ruling a checkpoint holds. A checkpoint is never edited — it is the record of what the agent said at the time — so a person's later "this no longer holds" or "still true at this commit" cannot be written into it, and it cannot live in the index either: deleting .logos would quietly un-retire every ruling and send the drift warnings back. It sits beside uncommitted.md, which IsCheckpointFile already tells apart from a checkpoint by its name.
const Schema = `` /* 681-byte string literal not displayed */
const TranscriptStem = "transcript"
TranscriptStem is the archived host transcript's file name, before the extension the host gave it: sessions/<scope>/_convos/<host id>/transcript.jsonl.
One fixed name per conversation folder, not one per harvest, because the host appends to the same file for the whole conversation: every harvest's copy is a prefix of the next one's, so keeping more than the newest is keeping the same bytes again, and a reader asking "what did this conversation say" wants one file, not a pick among several.
const UnattachedFolder = "_unattached"
UnattachedFolder is the conversation folder a checkpoint lands in when nothing told Logos which conversation produced it — no host id, no folder named after data nobody gave us (see Checkpoint.HostSession's doc comment on why guessing is worse than this).
Variables ¶
This section is empty.
Functions ¶
func ActivityLogStateFor ¶ added in v0.4.9
ActivityLogStateFor is the provenance of an auto record built from the activity log of host session id. The id in parentheses is how the sweep knows that session's transcript was recorded, in the same shape the transcript-built record uses.
func ClaimsDoneUnverified ¶ added in v0.5.0
func ClaimsDoneUnverified(c Checkpoint) bool
ClaimsDoneUnverified reports a state that says the work is finished while verified is empty. That is the most misleading checkpoint there is: the next agent reads "fixed and merged" as settled and builds on it, and the record holds nothing that shows it was ever run. A receipt, not a refusal, for the reason NextReadsAsMoreThanOneStep gives.
A claim word counts only when nothing in the two words before it takes it back — "half done", "not fixed yet", "nothing finished" are the opposite claim, and flagging them would teach agents to ignore the nudge.
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 ConversationLine ¶ added in v0.5.6
func ConversationLine(c Conversation) string
ConversationLine is one row of a candidate list or the picker: id, age, name. Everything in it but the age came from the vault, so it is flattened to one line whatever an agent wrote — and stripped of control characters, because this line is written straight to a terminal, the picker's in raw mode, and an intent carrying an escape sequence would otherwise be one an agent wrote into the vault and the user's terminal then executed.
func DecisionKey ¶ added in v0.5.5
DecisionKey is what makes two recorded decisions the same one: case, spacing, a closing full stop, and the "Decision:" a promoted memory is worded with all fall away. Mechanical on purpose — a decision restated in other words is a model's call, and two agents copying the line forward is the common case this has to catch without one.
func DecisionsWithoutReason ¶ added in v0.5.0
DecisionsWithoutReason counts the decisions that state a choice and not what forced it. "use sqlite" reads as a preference, and the next agent with a different one reopens it; "use sqlite, because the vault stays on this machine" is a constraint it can check. The reason is the part only the transcript held, so without it resuming means rereading the conversation.
A receipt, not a refusal, for the reason NextReadsAsMoreThanOneStep gives. And lenient on purpose: it looks for any sign of a reason — a connective, a dash or colon introducing one, a second sentence — rather than judging whether the reason is good. In a working vault almost every decision already carries one, and a nudge that fires on those is noise that trains agents to ignore the one that matters.
func DerivedFileAt ¶ added in v0.5.6
DerivedFileAt is IsDerivedFile for the file at path inside vaultDir, read only as far as its frontmatter, and only once its place says it could be a ledger. A file it cannot read is not derived: the caller's own read reports the failure.
func DropPlaceholders ¶
DropPlaceholders returns failed without its placeholder bullets, and how many it dropped so the caller can say so.
func FindConversation ¶ added in v0.5.6
func FindConversation(convs []Conversation, query string) (*Conversation, []Conversation, error)
FindConversation picks the one conversation query names, or says why it cannot.
The order is fixed and never falls through to a guess: an exact id first (as typed, or as the vault would have sanitized it), then the stem of any checkpoint in it — the only id an unmigrated, host-session-less project has, and the tail of every citation — and only then a case-insensitive substring of the conversation's name. More than one name match returns all of them with an error: resuming the wrong conversation hands an agent somebody else's next step, and it would not know.
func FindTranscript ¶ added in v0.5.6
FindTranscript is the archived transcript for hostSession, wherever its conversation folder is, or "" when none was ever archived. The lookup is by the exact id: a conversation's name or a prefix of its id is a question for a resolver this does not try to be, and a near match that served the wrong conversation's text would be worse than a miss.
func HistoryScope ¶ added in v0.5.8
HistoryScope is historyScope for callers outside this package that read something filed beside a project's checkpoints — the verdicts in reviewed.md, the dead ends shown at edit time, the ledger. Built from SafeScope instead, they read the new, empty folder while resume shows the old one, and a dead end a person retired there came back as live.
func ImportNotes ¶
ImportNotes restores working notes from the vault into an empty index.
Called by `logos 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, how many it rescued out of the cache, and how many it left out because they say nothing once masked.
func IntentDropped ¶ added in v0.5.0
func IntentDropped(c Checkpoint, earlier []Checkpoint) bool
IntentDropped says c will reach the next agent without a reason although the work just before it had one. Inheritance matches the task's wording exactly, so a reworded task — "cut the BOM to $118" becoming "keep cutting the BOM toward $118" — silently loses a reason the agent believes it already gave. Fuzzy matching was the other way out and was ruled out: on tasks this short, word overlap cannot tell a rewording from a different task, and handing one task another's reason is worse than handing it none.
func IsAbandonedClaim ¶ added in v0.5.8
IsAbandonedClaim reports whether the file at rel is a claim a killed write left empty, for index's walk to collect.
func IsCheckpointFile ¶
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 IsClaimPath ¶ added in v0.5.8
IsClaimPath reports whether rel (vault-relative, slash-separated) names a checkpoint or plan: the two files reserved by an empty O_EXCL create of their final name and filled by a write that comes after (claimCheckpoint, claimPlan). The name is what makes them unique across processes, so the reservation cannot move to a temp name; a kill between the two steps leaves the empty file instead.
func IsDerivedFile ¶ added in v0.5.6
IsDerivedFile is whether the vault file at rel (slash-separated, from the vault root), starting with head, is a view Logos rebuilds from checkpoints rather than a record: where a ledger is written (derivedPlace), and typed ledger by its frontmatter's own top-level key. A rename deletes what this says yes to, so both have to hold: a note of the user's at sessions/ledger.md, one under another name, or one nesting "type: ledger" under some other key is theirs whatever it says it is.
func IsPlaceholder ¶
IsPlaceholder reports a Failed bullet that says nothing was ruled out. Agents fill the field with "none this session" rather than leave it empty, and stored as a bullet it read as a dead end: resume listed it as ruled out and before_you_try matched it to unrelated proposals.
func IsTranscriptFile ¶ added in v0.5.6
IsTranscriptFile reports whether a file in a conversation folder is the archived transcript. The in-flight copy (".partial", see the ingest archiver) is not: it is either about to be renamed over the real one or is the debris of a run that died, and reading it as the transcript would serve half a conversation as though it were the whole.
func NameInside ¶ added in v0.5.0
NameInside is the one known project whose name appears, as whole words, in a name that matched none of them — "saathi" inside "Saathi backend" — or "" when no known name does, or more than one does.
Agents describe a project rather than quote it, and a resume that answered "nothing recorded … say so rather than inferring" to "Saathi backend" told the agent to give up on work filed one word away. It is a suggestion, never a substitution: a name the caller gave is honoured, and handing them another project's work unasked is the worse failure. Whole words, so "saath" is not taken for "saathi", and exactly one, so a name that holds two projects gets the plain list rather than a coin flip.
func NextReadsAsMoreThanOneStep ¶ added in v0.4.5
NextReadsAsMoreThanOneStep reports that `next` is not the single step the schema asks for — a conditional ("if asked, populate Appendix A, otherwise ship it") or a second sentence.
The receipt is deliberately not a refusal. A checkpoint is written when an agent's context is running out, which is the worst possible moment to argue about grammar and lose the record; the writing agent reads fine to itself and only a different tool pays for the ambiguity, so the record is kept and the doubt is said out loud instead.
func NoteText ¶ added in v0.5.0
NoteText is text as a note keeps it, for a caller comparing what it is about to write with what is already there. Compared as typed, a note with a key in it never matched its own masked copy and was stored again.
func OpenCount ¶
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 ¶
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 Refile ¶ added in v0.5.4
Refile moves every checkpoint whose host_session frontmatter disagrees with the conversation folder it is physically sitting in, across the whole vault, and returns how many it moved.
Frontmatter is the truth (see Checkpoint.HostSession's doc comment); the ConvosDir folder is a derived layout, rebuilt from it here exactly the way `logos index` rebuilds the cache from the vault. A file can end up in the wrong folder after an interrupted migration, a sync conflict copy, or a hand move — never after an ordinary Commit, which already files a fresh checkpoint where its own HostSession says it belongs.
The new location is written before the old one is removed, so a crash between the two leaves the checkpoint readable twice — the next Refile call finishes the move and removes the stale copy — never a checkpoint that briefly existed nowhere.
It also rejoins a checkpoint filed under a cut id (see rejoinCutIDs): the second count is how many of the moved ones had their host_session rewritten.
func RemoveAbandonedClaims ¶ added in v0.5.8
RemoveAbandonedClaims removes the empty checkpoints and plans killed writes left (#380). An empty one has nothing to lose, but it was read as a checkpoint with no frontmatter and sat in every sync client and git status for good. Each is checked again before removal, since the walk that found it may be minutes old; it keeps going past a failure, as RemoveTemps does, so the ones it did remove can still be said.
func RenderConversation ¶ added in v0.5.6
func RenderConversation(projectName string, c Conversation) string
RenderConversation is resume narrowed to one conversation: its latest checkpoint in full, and the rest of its chain as a list to pull on.
Deliberately not the whole context pack. The pack answers "what does this project know", and its memories and other open work are why a narrowed resume was asked for in the first place: someone with three conversations in flight wants this one's stopping place without the other two mixed in.
func ResolvePath ¶ added in v0.5.4
ResolvePath finds a checkpoint a stored citation names, wherever it actually sits in the vault now.
slug is the citation as something else wrote it down: "sessions/<project>/ <stem>", the only shape every citation before ConvosDir existed ever used, and still the shape already sitting in memories, checkpoints and ingest records nobody is going to rewrite. Tried literally first, since that is still correct for any checkpoint Refile has had no reason to move, and a direct stat is one syscall where a vault-wide search is a walk of every scope. Only on a miss does it fall back to a search by stem — the part of the citation that survives a move, because Refile renames the directory a checkpoint sits under and never the checkpoint's own filename.
func ReviewKey ¶ added in v0.5.5
ReviewKey is how a reviewed ruling is matched back to its checkpoint entry: case and whitespace folded, the same key resume dedups rulings by, so a ruling restated in a later checkpoint with different wrapping is the same ruling reviewed once.
func Reviews ¶ added in v0.5.5
Reviews reads a scope's verdicts, keyed by ReviewKey. The file is append-only and the last line about a ruling wins, so re-confirming a ruling retired by mistake brings it back. A missing file is no verdicts, not an error.
func SafeScope ¶ added in v0.4.8
safeScope is safe for a scope rather than a plain name: "kestrel" is one, and so is "kestrel/feature-x", the same project worked on in a linked worktree.
A scope is what sessions are keyed by and what a checkpoint's path is built from, and its two levels have to stay two levels — safe collapses "/" to a dash, which would file a worktree as a sibling project called "kestrel-feature-x" rather than as a folder inside kestrel's. Each segment is still run through safe, so `..` and separators cannot survive and a scope cannot climb out of the vault however it was spelled. SafeScope is safeScope for callers outside this package that have to key something by the same project a checkpoint will be filed under. The MCP server counts unsaved progress per project and clears it when that project is checkpointed; keyed on the agent's raw spelling, the two halves used different keys and a checkpoint never cleared its own notes.
func SavePlan ¶
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. `logos index` picks it up on the next run the same as any other vault note.
func Scopes ¶ added in v0.4.8
Scopes lists every scope that actually holds a checkpoint, worktree scopes included and spelled the way resume takes them back ("shop/fix-auth").
Projects deliberately returns only the top level, which is correct for "which projects exist" and wrong for every caller that wants something it can look history up under. safeScope preserves "/" on the way in, so a checkpoint written from a linked git worktree — including the worktrees Claude Code creates for its own agents — lands one level down, where a single ReadDir cannot see it. Everything that enumerated through Projects skipped that work while reporting a number as though it were complete: `logos projects` undercounted, list_projects named a parent with "(0 checkpoints)", and internal/deadend never reached a ruling recorded from a worktree — the one thing that package exists to surface.
Still two levels of *scope* — project, and optionally project/worktree — because two levels is the whole of the scope layout a caller ever asks History for by name. A third level exists on disk now, ConvosDir and the conversation folders under it, but it is not a scope of its own: it is where a scope's own checkpoints physically sit, and holdsRecord already looks there, so every caller that walks Scopes and then calls History on what it finds keeps seeing every checkpoint without needing to know conversation folders exist at all. ConvosDir itself is skipped as a worktree candidate below — without that, every project's own conversation folder would be mistaken for a sibling worktree once checkpoints moved a level deeper into it (see ConvosDir's doc comment).
func SetTask ¶
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 ¶
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.
func SkippedAhead ¶ added in v0.5.7
func SkippedAhead(vaultDir, project string, shown *Checkpoint) []string
SkippedAhead names the checkpoint files in a project's scope that sort after shown — the checkpoint History handed back as the latest — and so were passed over because they are empty or could not be read. With shown nil, every checkpoint file was passed over.
History skips them on purpose, so one torn file does not hide a year of history. But the reader is then told an older checkpoint is where work left off, and a newer file was right there: that has to be said (#367).
func Split ¶ added in v0.5.8
Split names the old folder once the fallback has ended without an agent saying so: the project's own folder holds checkpoints now, but only auto records. The hook writes those at session end, where nothing it prints is read, so the first one quietly took every earlier checkpoint out of resume; resume says it instead until an agent checkpoints, which says it too.
func SplitNotice ¶ added in v0.5.8
SplitNotice is what checkpoint and resume say once a project's own folder has taken over from the one 0.5.6 filed it under: from here resume reads the new folder only, so the earlier history is no longer shown.
func Stranded ¶ added in v0.5.8
Stranded names the folder a project's history is still in when that is not the folder it is filed under now: the name has a symbol the old rule dropped, its own folder holds no checkpoint, and the old one does. Empty otherwise.
The old folder may hold another project's history instead — that merge is #368 itself, and the checkpoints in it record only the folder name, so which project wrote them cannot be told — so this is said to the user with the rename that moves it, never acted on.
func StrandedNotice ¶ added in v0.5.8
StrandedNotice is the one line resume says while Stranded finds a folder. It does not call the history this project's: a new "C#" in a vault that has "C" finds C's folder the same way, and nothing in the files tells them apart.
func TranscriptDir ¶ added in v0.5.6
TranscriptDir is the conversation folder a harvested transcript for hostSession is archived into.
An existing folder for that id wins over the one project would name, because the folder is already where that conversation's checkpoints are — including a worktree scope (sessions/<project>/<worktree>/_convos/<id>) the harvest has no way to know about: a transcript records the directory the host stood in, and a worktree's basename is not its scope. Found by the folder's name rather than by parsing every checkpoint for its host_session, because the name is derived from exactly that frontmatter (Refile keeps the two agreeing) and a harvest of a few hundred transcripts would otherwise read the whole vault once per transcript.
With no folder yet, the transcript gets the one a checkpoint from the same conversation would get under project. No host id, or no project, is an error rather than a guess: UnattachedFolder is shared by every conversation nothing named, so one transcript there would be overwritten by the next, and a project folder made up for an unattributed session is one nobody resuming would look in.
Types ¶
type Abandoned ¶
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 ¶
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 ¶
FindAbandonedInProject narrows FindAbandoned to one project — the view `logos 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 `json:"session,omitempty"`
// HostSession is the id the *host* uses for this conversation — Claude
// Code's hook session_id, or a transcript reader's own s.ID — never
// Logos's own Session stem above. Session names the checkpoint file;
// HostSession names the conversation that produced it, which is what
// lets every checkpoint from one sitting be found together regardless of
// how many times it was checkpointed. Empty when nothing told us: an
// MCP host that gives no session_id (Cursor, Claude Desktop) leaves this
// blank rather than guessing, which is more honest than a wrong id.
HostSession string `json:"host_session,omitempty"`
Project string `json:"project,omitempty"`
Agent string `json:"agent,omitempty"`
Task string `json:"task,omitempty"`
// Intent is why the task matters: the outcome it serves and the constraint
// that shapes it. Task says what; a resume that only says what leaves the
// next agent rereading the transcript to learn which trade-offs are open,
// and picking the one the user already refused. It is written once per task
// and handed forward to later checkpoints of it — see contextpack.
Intent string `json:"intent,omitempty"`
State string `json:"state,omitempty"`
Decisions []string `json:"decisions,omitempty"`
Failed []string `json:"failed,omitempty"`
// 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 `json:"verified,omitempty"`
// Unseen is the verified lines that name a command the session's activity
// log never saw run — see package claims. A copy of those lines, not a
// flag on each, so a checkpoint written before this existed reads the same
// and a reader of the file sees the doubt under its own heading.
Unseen []string `json:"unseen,omitempty"`
Blockers []string `json:"blockers,omitempty"`
Commands []string `json:"commands,omitempty"`
Questions []string `json:"questions,omitempty"`
Files []string `json:"files,omitempty"`
Next string `json:"next,omitempty"`
// 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 `json:"git,omitempty"`
// HandoffTo names who this was left for. Empty for a plain checkpoint —
// the difference is intent, not mechanism.
HandoffTo string `json:"handoff_to,omitempty"`
// 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 `json:"auto_closed,omitempty"`
// Auto marks a checkpoint built from the activity log when a session ended
// without one — see WriteAuto. It records what ran, never what was learned,
// so a reader must be able to tell it apart from an agent's own.
Auto bool `json:"auto,omitempty"`
// Inferred is what a later agent read out of an auto record's transcript:
// what it verified, ruled out and decided, each citing the turn it came from.
// Kept apart from Verified, Failed and Decisions on purpose. Those are an
// agent's own testimony, and the next agent treats a failed entry as a
// paid-for ruling and will not re-try what it names; a reading of somebody
// else's transcript is not that, however good the reader. Nil until an
// agent distils the record.
Inferred *Inference `json:"inferred,omitempty"`
// Turns is how many turns the transcript behind an auto record held when
// the record was written or last grown, as its harness's reader counts
// them. A session can be resumed, and its transcript goes on past the
// record: without the count, a reading served "the transcript" would cover
// work this record never saw and put conclusions about it into this
// record. Zero on a record that could not count, which is then not served.
Turns int `json:"turns,omitempty"`
// Redactions is what Commit masked before writing, so the caller can say
// so. Not written to the file: the file carries the marker in place.
Redactions []secret.Redaction `json:"-"`
Slug string `json:"slug,omitempty"` // vault slug, set once written
TS int64 `json:"ts,omitempty"`
}
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 ByHostSession ¶ added in v0.5.4
func ByHostSession(vaultDir, hostSession string) ([]Checkpoint, error)
ByHostSession returns every checkpoint anywhere in the vault that carries the given host session id, oldest first — the whole follows chain one conversation produced, however many times it checkpointed and whatever scope it checkpointed under.
`logos resume <session-id>` needs this because a conversation's project can change mid-work (a worktree spun up partway through) and because "what did this conversation do" is a question about the id, not about any one project's history. Built on Scopes and History rather than a parallel directory walk, so a torn file is skipped exactly as resume already skips one, and a checkpoint missing from Latest/History is never found here either.
func CloseAbandoned ¶
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 GrowAuto ¶ added in v0.4.9
func GrowAuto(vaultDir string, old, c Checkpoint) (Checkpoint, error)
GrowAuto rewrites the auto record old with what its session has done since, in the same file under the same id, and returns it as written.
A session can outlive its record: a window left idle long enough to be swept and then used again, or a host resumed on the transcript it had already written. A second record would list the first one's work again beside it, and ignoring the rest would lose it, which is the thing the record is for. The file stays where it is so whatever already links to it still finds it.
The file is read again first, and anything no longer marked auto is left alone: a person may have made it their own since, and a mechanical list must never be written over what somebody reviewed.
func History ¶
func History(vaultDir, project string, n int) ([]Checkpoint, error)
History returns up to n checkpoints for a project, newest first.
func InferAuto ¶ added in v0.5.0
func InferAuto(vaultDir, slug string, in Inference) (Checkpoint, error)
InferAuto writes an agent's distillation of an auto record's transcript into that record, in place, and returns it as written. The file is read again first for the same reason GrowAuto reads it: a record somebody has since made their own is theirs, and a reading of the transcript must not be written into what they reviewed. Only the vault is written; the caller reindexes.
in.Turns is how much of the transcript the reading covered. A record that has since grown or been rewritten is refused rather than written: the reading would be stored as covering work it never saw.
func IntentFor ¶ added in v0.5.0
func IntentFor(c Checkpoint, earlier []Checkpoint) (string, *Checkpoint)
IntentFor is why c's task matters, and the earlier checkpoint the reason was stated in when c did not state it itself (nil when it did). earlier is the project's history before c, newest first.
Only the same task inherits: another task's reason is not this one's. An auto record is the exception — its task is the user's first prompt, never the agent's wording, so it could never match; it carries on whatever the agent's checkpoint before it was doing, and takes that checkpoint's reason.
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 .logos` — 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 WriteAuto ¶ added in v0.4.4
func WriteAuto(vaultDir string, c Checkpoint) (Checkpoint, error)
WriteAuto writes a checkpoint for a session that ended without one, from what the host recorded it doing. The caller fills Task, Files and Commands; this marks it Auto and leaves every field that would be a claim empty.
Only the file is written. Commit closes the project's open sessions and folds every open session's notes in, which is right for an agent vouching for its own work and wrong for a record nobody reviewed: another agent still working in the project would have its notes swept into an unverified checkpoint and its session closed under it. Notes left open stay listed as uncommitted beneath this checkpoint instead.
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 CheckpointEntry ¶ added in v0.5.4
CheckpointEntry is one checkpoint file found under a scope's own directory, wherever it actually sits: directly in the scope (every checkpoint written before this migration, and anything hand-placed since), or inside one of the scope's conversation folders under ConvosDir (everything Commit writes now). Rel is the path to open, relative to the scope directory; Name is the checkpoint's own filename, kept separately because it is the sort key — a conversation folder is named after a host id or UnattachedFolder, not a timestamp, so sorting by Rel would interleave conversations by folder name instead of ordering checkpoints by when they were made. Exported so internal/deadend's own cache of ruling files can key on the same relative path History does, rather than hand-rolling a second walk that only knows the pre-ConvosDir layout.
func CheckpointEntries ¶ added in v0.5.4
func CheckpointEntries(dir string) ([]CheckpointEntry, error)
CheckpointEntries finds every checkpoint file a scope's directory reaches, one level into ConvosDir and no further — a worktree scope's own directory is a *different* scope, reached by asking History for it directly, and descending into it here would merge two deliberately isolated histories into one.
type Conversation ¶ added in v0.5.6
type Conversation struct {
ID string
Name string
Latest Checkpoint
// Chain is newest first, Latest included at Chain[0].
Chain []Checkpoint
// Flat says at least one of this conversation's checkpoints still sits
// in the pre-folder layout, so the caller can point at `logos migrate
// sessions` instead of leaving an old vault looking half-understood.
Flat bool
}
A Conversation is every checkpoint one host conversation left in a project, read back out of the folder layout C5 files them in.
ID is the conversation folder's own name — the sanitized host session id — because that is the one handle that is the same in the vault, in the hook's session_id, and on the command line. Name is not stored anywhere: it is the latest checkpoint's intent, derived on every read, so a conversation whose purpose was restated is named by what it is for now, and there is no second field to drift out of step with the checkpoints (and nothing for an index rebuild to lose).
func Conversations ¶ added in v0.5.6
func Conversations(vaultDir, projectName string) ([]Conversation, error)
Conversations lists a project's conversations, most recently active first.
The project's worktree scopes are read too: a conversation that started in the main checkout and moved into a worktree partway through is still one conversation, and ByHostSession already treats it as one.
Grouping follows where a checkpoint would be filed, not only where it is. A flat file with a host_session belongs to that conversation already — migrating it changes its path, never which conversation it is — so an unmigrated project resumes by the same id it will have afterwards. A checkpoint with no host session (hand-written, a hook-less host, or every checkpoint from before host_session existed) is its own conversation, named by its stem: lumping them into one "_unattached" conversation would present unrelated work by different agents as one sitting, which is the fabrication UnattachedFolder exists to avoid.
type Inference ¶ added in v0.5.0
type Inference struct {
By string `json:"by,omitempty"`
At int64 `json:"at,omitempty"`
Verified []string `json:"verified,omitempty"`
Failed []string `json:"failed,omitempty"`
Decided []string `json:"decided,omitempty"`
// Wanted is what the person asked for or corrected the agent toward, each
// citing a turn of theirs: the session's intent, read rather than
// summarised, since an agent's account of what it was asked is the guess
// the auto record's quoted task line exists to avoid.
Wanted []string `json:"wanted,omitempty"`
// Turns is the record's Turns when it was read. A record that has grown
// past it holds work nobody read, and is offered for reading again.
Turns int `json:"turns,omitempty"`
}
Inference is a distillation of an auto record's transcript. By and At say who read it and when, because an unreviewed claim nobody can attribute is one nobody can weigh.
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 ¶
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 MigrateReport ¶ added in v0.5.6
type MigrateReport struct {
Moved int
Skipped []string // "<path>: <why>", left exactly where they were
Failed []string // "<path>: <error>", the old file still readable
}
MigrateReport is what `logos migrate sessions` did, counted, so it can say so — a migration that moved files without a number reads as one that might have lost some.
func MigrateFlat ¶ added in v0.5.6
func MigrateFlat(vaultDir string, dryRun bool) (MigrateReport, error)
MigrateFlat moves every checkpoint still sitting directly in a scope directory — the pre-C5 layout — into the conversation folder its own host_session names.
Refile already does this on every `logos index`, but it stops at the first error and returns a bare count. This is the explicit, reported version: it carries on past a file it cannot move, and names every one it skipped or failed, because one unwritable folder must not leave the rest of a vault half-migrated without saying which half.
The new copy is written before the old one is removed, so a crash or a failed remove leaves the checkpoint readable twice — Conversations folds the two into one — and never nowhere. A target that already holds the same bytes is that interrupted move, finished here; a target holding different bytes is a real collision, and both files are left for a person to look at rather than one overwriting the other.
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
// Unflushed means this note is in the cache and not in the vault, because
// the write to uncommitted.md failed. Carried onto the note so resume can
// say so: the whole promise of note_progress is that a line survives the
// agent's context running out, and a line held only in a deletable cache
// does not. See flushNotes.
Unflushed bool
// Redactions is what AddNote masked before storing Text. Set only on the
// note AddNote returns, for the receipt.
Redactions []secret.Redaction
}
A Note is one line of progress inside a session. Deliberately unstructured: the moment this needs fields, agents will stop writing them.
func AddNoteAt ¶
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 ¶
Notes returns a session's working notes, oldest first — the order they were written is the order they make sense in.
func Uncommitted ¶
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 ¶
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 ¶
ListPlans returns a project's captured plans, newest first. This is what `logos plans <project>` reads, and what proves a plan the hook saved is actually reachable rather than write-only.
type ProjectContinuity ¶
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 ¶
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 logos through it locally.
func Continuity ¶
func Continuity(db *sql.DB, vaultDir, project string) (ProjectContinuity, error)
Continuity reports one project's continuity health.
func (ProjectContinuity) Quiet ¶
func (pc ProjectContinuity) Quiet() bool
Quiet reports whether this project's continuity has gone stale: no checkpoint at all, or none within QuietAfter.
type Review ¶ added in v0.5.5
Review is the latest verdict on one ruling. Retired drops it from what is handed over; otherwise Commit is where a person last confirmed it, and the drift is measured from there instead of from the checkpoint's commit.
func (Review) For ¶ added in v0.5.5
For applies a verdict to one checkpoint's copy of the ruling, and says whether that copy is retired and which commit to measure it from.
A verdict covers only checkpoints recorded up to when it was given. A dead end retired and then recorded again by a later agent is a new finding — the file regressed, say — and dropping it on a text match would send the next agent straight back into it. Likewise a later restatement's commit is newer than the re-confirm, and measuring from the re-confirm would warn again about commits that agent already worked on top of.
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 ¶
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.