Documentation
¶
Overview ¶
Package sessionlog is memcode's episodic memory: a local, append-only record of the high-level causal trail of a session — user messages, assistant messages, meaningful actions (commands, edits, commits), and approvals — written to disk as it happens, independent of the LLM context window. It is NOT a stdout landfill: internal reads/greps and raw tool output stay out (raw mechanics belong in an optional debug trace, not canonical memory).
The doctrine it serves:
working context → fast but lossy session log → episodic truth (why/when/under whose approval/side effects) git → code-diff truth claims/memories → distilled doctrine
events.jsonl is the append-only source of truth (replayable); transcript.md is a human-readable rendering written alongside. Readers are deterministic scans — no model, no index — so the agent can self-recall before acting on fuzzy memory.
Index ¶
- Constants
- Variables
- func HasActions(threads []Thread) bool
- func IsGitCommitPush(input string) bool
- func RenderRecap(threads []Thread) string
- type Digest
- type PlanRec
- type RankingOptions
- type Record
- func AdherenceRecords(root string) ([]Record, error)
- func Commits(root string) ([]Record, error)
- func LastShell(root string) (Record, bool)
- func LatestRecent(root string, n int) ([]Record, error)
- func LatestRecentExcluding(root, excludeID string, n int) ([]Record, error)
- func LessonSignals(root string) ([]Record, error)
- func PreferenceSignals(root string) ([]Record, error)
- func Recent(root, sessionID string, n int) ([]Record, error)
- func RecentBurstExcluding(root, excludeID string, maxSessions int, burstGap time.Duration, ...) ([]Record, error)
- func Search(root, query string, n int) ([]Record, error)
- func SessionRecords(root, id string) ([]Record, error)
- func Sidequests(root, sessionID string) ([]Record, error)
- type SessionSummary
- type Thread
- type Writer
Constants ¶
const ( KindSessionStarted = "session_started" KindUserMessage = "user_message" KindAssistantMessage = "assistant_message" KindToolCall = "tool_call" // a meaningful action: command, edit, commit — NOT internal reads KindApproval = "approval" // a user allow/deny decision KindToolResult = "tool_result" // reserved for an optional debug trace; not written to the canonical log KindCompaction = "compaction" // older turns summarized in-session (the warm layer's durable record) KindPlanPresented = "plan_presented" // a plan was presented for approval — full text + slug (recoverable via RecentPlans) KindPlanCancelled = "plan_cancelled" // planning was ABANDONED before execution — the ask is still open work KindSessionFinished = "session_finished" KindPreferenceSignal = "preference_signal" // a captured user directive — the CANONICAL copy for the prefs reducer KindLessonSignal = "lesson_signal" // a distilled lesson — the CANONICAL copy for the lessons reducer KindContextInlined = "context_inlined" // which promoted rules (lesson/pref ids) rode this session's system prompt KindAdherence = "adherence" // a rule-adherence verdict about TargetSession — the CANONICAL copy for reducer weighting KindFacts = "facts" // one atomic extracted fact about THIS session (post-session cognition) — indexed by ranked search )
Record kinds. Keep these stable — they're the on-disk contract.
Variables ¶
var Ranking = RankingOptions{}
Ranking is the live configuration. The bench mutates it between adapter runs; the product path only ever reads it.
Functions ¶
func HasActions ¶
HasActions reports whether any thread carries a recorded action (command, edit, approval) — i.e. the session actually did something rather than just exchanging messages. A brand-new session that has only said "hi" has none; that's the cue to recap the previous distinct session instead of this near-empty one.
func IsGitCommitPush ¶
IsGitCommitPush reports whether a tool-call input is an actual `git commit`/`git push` INVOCATION — parsed with the shell AST, never a substring match. `rg "git commit"` (the phrase inside a quoted search arg) is NOT a commit; the old substring check flagged it, marking a strand Completed and logging a phantom commit. The input may be a raw command or a bash tool's JSON ({"command":"…"}); both are handled.
func RenderRecap ¶
RenderRecap formats threads as a numbered checklist with a drill-deeper hint.
Types ¶
type Digest ¶
type Digest struct {
Requests []string // user asks in order — the session's sidequests
Commits []string // git commit/push commands run
Tests int // verification runs (test/vet/build/lint)
Edits int // file-edit tool calls
}
Digest is a compact, reduced projection of a session's raw events — the form orientation/prediction should consume (NOT raw events.jsonl, which is noisy and private). It answers "where did we leave off": what was asked, what was committed/pushed, how much was tested/edited.
type PlanRec ¶
PlanRec is a presented plan recovered from a session log — the full markdown plus its slug and where/when it was presented. The canonical, project-scoped record of a plan (independent of the user-level ~/.memcode/plans store), so a plan is recoverable even if that store is empty.
func RecentPlans ¶
RecentPlans scans every session's log for presented-plan records, newest first, deduped by slug (the most recent revision of each plan wins). limit ≤ 0 means no limit. Records with no slug (legacy) are kept individually. This is the recovery path recall reaches for when the user-level store doesn't have a plan — e.g. a plan from a prior session, a wiped store, or a new machine.
type RankingOptions ¶
type RankingOptions struct {
FieldWeights bool // per-kind multipliers: facts > compaction > user > assistant > tool
RM3 bool // pseudo-relevance feedback query expansion (skipped on exact-tier hits)
Adjacency bool // a strong turn credits its immediate neighbors — evidence sits next door
EntityPPR bool // personalized PageRank over the facts entity graph, RRF-fused at session level
}
RankingOptions toggles the retrieval features layered on the BM25 core so membench can isolate each one's lift. Defaults are the measured winners (membench ablation, 2026-07-25): ALL OFF. Facts indexing — the data layer, not a knob here — delivered the lift on both benchmarks (LoCoMo R@5 0.502→0.612, LME R@5 held 0.93+); every global reranking layer measured neutral-to-harmful on at least one dataset (field weights flip sign across granularities, RM3 and adjacency trade R@1 for tail recall, session-level PPR+RRF scrambles good lexical orderings). They remain as options for ablation and for callers with a matching corpus shape.
type Record ¶
type Record struct {
TS time.Time `json:"ts"`
Kind string `json:"kind"`
Text string `json:"text,omitempty"` // user/assistant message text
Tool string `json:"tool,omitempty"` // tool_call/result tool name
Input string `json:"input,omitempty"` // tool_call input (redacted JSON)
Decision string `json:"decision,omitempty"` // approval: approved | denied | …
ToolUseID string `json:"tool_use_id,omitempty"` // links a result to its call (debug trace)
Content string `json:"content,omitempty"` // tool_result output (debug trace) / $ shell output
IsError bool `json:"is_error,omitempty"` // tool_result / $ shell failed
Exit int `json:"exit,omitempty"` // $ shell command exit code
Model string `json:"model,omitempty"` // session_started
Mode string `json:"mode,omitempty"` // session_started
HeadSHA string `json:"head_sha,omitempty"` // session_started
Slug string `json:"slug,omitempty"` // plan_presented: the saved-plan slug (~/.memcode/plans/<slug>.md)
Axis string `json:"axis,omitempty"` // preference_signal: which axis (workflow/gating/…)
Scope string `json:"scope,omitempty"` // preference_signal: this-repo vs global
Strength float64 `json:"strength,omitempty"` // preference_signal / lesson_signal: evidence weight
Trigger string `json:"trigger,omitempty"` // lesson_signal: the recurring failure condition
Strategy string `json:"strategy,omitempty"` // lesson_signal: what to do when the trigger holds
Entities []string `json:"entities,omitempty"` // facts: lowercase entity keys the fact is about (feeds the session entity graph)
// Post-session learning loop fields.
LessonIDs []string `json:"lesson_ids,omitempty"` // context_inlined: promoted lesson ids in this session's prompt
PrefIDs []string `json:"pref_ids,omitempty"` // context_inlined: confirmed pref ids in this session's prompt
RuleKind string `json:"rule_kind,omitempty"` // adherence: "lesson" | "pref"
RuleID string `json:"rule_id,omitempty"` // adherence: the rule's stable id
Verdict string `json:"verdict,omitempty"` // adherence: followed | violated | not_applicable
Outcome string `json:"outcome,omitempty"` // adherence: the target session's git outcome (accepted/corrected/rejected)
TargetSession string `json:"target_session,omitempty"` // adherence / lesson_signal: the session the verdict/lesson is ABOUT (the record lives in the judging session's log)
// SessionID is stamped IN MEMORY by the multi-session readers (RecentBurstExcluding,
// Recent) so a merged record knows which session it came from — used to name a
// thread's source session in orientation. json:"-": never written to disk (the dir
// name IS the id; persisting it would be redundant and could drift).
SessionID string `json:"-"`
}
Record is one append-only line in events.jsonl. Fields are sparse (omitempty); which ones are set depends on Kind.
func AdherenceRecords ¶
AdherenceRecords returns every adherence record across all sessions, oldest first — the reducers' backfill path for adherence weighting (files canonical, SQLite derived; same contract as LessonSignals).
func Commits ¶
Commits returns the git commit/push tool calls across all sessions (newest first) — the accountability trail of "did we already commit/push this?".
func LastShell ¶
LastShell returns the most recent `$` direct-shell command (Tool "shell"), across all sessions newest-first — its command (Input), output (Content), and exit (Exit/IsError). This is the data behind explain/fix-last: "why did that fail?" / "fix it" consult it. Empty (false) when no `$` command has run.
func LatestRecent ¶
LatestRecent returns the last n records of the most recently active session (by directory mtime) — the signal orientation/prediction pulls from to know "where you left off." Empty (nil, nil) if no sessions exist yet.
func LatestRecentExcluding ¶
LatestRecentExcluding returns the last n records of the most recently active session whose id differs from excludeID — i.e. "the previous distinct session" when excludeID is the current one. This is what answers "what was the last session about?" from a brand-new session, which is already non-empty (it holds the user's opening message) and would otherwise shadow the prior one. Empty (nil, nil) if there is no other session.
func LessonSignals ¶
LessonSignals returns every lesson_signal record across all sessions, oldest first, each stamped with its session id. The lessons reducer's backfill path — same contract as PreferenceSignals (files canonical, SQLite derived).
func PreferenceSignals ¶
PreferenceSignals returns every preference_signal record across ALL sessions on disk, oldest-first, each stamped with its source session id. This is the CANONICAL read for the prefs reducer's backfill: the SQLite events table is a derived index, so after a state.db wipe the signals are recovered from the append-only files here.
func RecentBurstExcluding ¶
func RecentBurstExcluding(root, excludeID string, maxSessions int, burstGap time.Duration, perSession int) ([]Record, error)
RecentBurstExcluding returns the merged records of the current WORK BURST of prior sessions (excludeID skipped), in CHRONOLOGICAL order — oldest session first, so a reducer sees one continuous stream and later work supersedes earlier work naturally. perSession caps each session's tail (0 = all).
The burst rule replaces both bad windows. A flat "last session" baked in amnesia (the thread from two sessions ago — minutes older — vanished); a flat "last K sessions" hauls year-old work into today's orientation just to fill a quota. Instead: ALWAYS take the most recent prior session, however old — that is "where you left off" — then keep walking older only while the gap between CONSECUTIVE sessions stays within burstGap, capped at maxSessions. Sessions clustered in one stretch of work arrive together; a long break ends the window.
func Search ¶
Search scans every session (newest first) for records whose text/content/input /tool contains query (case-insensitive). An empty query matches everything. Returns at most n hits (n<=0 = unlimited).
func SessionRecords ¶
SessionRecords returns the full record list of ONE session by id, oldest first — the post-session learning loop reads a finished session's trail to build the adherence digest. (nil, nil) when the session has no log.
func Sidequests ¶
Sidequests returns the user messages of one session — the sequence of things the user actually asked for, in order.
type SessionSummary ¶
type SessionSummary struct {
ID string
Started time.Time
Headline string
Actions int // meaningful tool calls (commands/edits) — a sense of how much happened
}
SessionSummary is a one-line digest of a past session for "what were the last couple sessions about?" — the headline is the session's first user request.
func RecentSessions ¶
func RecentSessions(root, excludeID string, n int) ([]SessionSummary, error)
RecentSessions returns the most recent sessions newest-first, EXCLUDING excludeID (the current one). n<=0 means all. This is the "recent sessions" affordance the agent needs to answer about session history — distinct from Recent(), which reads a SINGLE session's records.
type Thread ¶
Thread is one strand of work in a session: a user request and the meaningful actions taken under it. A session's Threads read like a checklist of "what we did," each with a line of detail — the right grain for recall (signal, not the raw event firehose; drill into events.jsonl when you need 100%).
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer appends records for one session to .memcode/sessions/<id>/.
func Open ¶
Open creates (or reopens) the on-disk log for a session and returns a Writer. A nil Writer is safe to Append/Close on, so callers can ignore the error and degrade gracefully if the directory can't be created.