memory

package
v0.9.3 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package memory implements jcode's cross-session learned memory: a file-based store under ~/.jcode/memory with a per-project root, an online note inbox (L1), a summary/index read path injected into the system prompt, and usage accounting that feeds the offline distillation pipeline (L2). See internal-doc/agent-memory-design.md.

Index

Constants

View Source
const (
	SummaryFile  = "memory_summary.md"
	IndexFile    = "MEMORY.md"
	NotesDir     = "notes"
	SummariesDir = "session_summaries"
	StateFile    = "state.json"
)

Layout, relative to a scope root (global/ or projects/<slug>/):

memory_summary.md   consolidated summary, injected into the system prompt
MEMORY.md           grep-able index (maintained by the consolidation agent)
notes/              L1 inbox: one small fact per file, <ts>-<slug>.md
session_summaries/  phase-1 products (M2)
skills/             distilled reusable workflows (M3, SKILL.md format)
state.json          usage accounting / pipeline coordination
View Source
const MaxNoteBytes = 64 * 1024

MaxNoteBytes caps a single note (memory tool official guidance: bound file sizes at the implementation layer).

Variables

This section is empty.

Functions

func BuildInjection

func BuildInjection(projectDir string, cfg *config.Config) string

BuildInjection renders the memory section appended to the system prompt. Returns "" when there is nothing worth injecting (zero cost for fresh projects). The content is injected transiently per model call — it never enters the session history, so it cannot be compacted away or pollute summaries (same principle as eino's agentsmd middleware).

func ClearScope

func ClearScope(scopeRoot string) (busy bool, err error)

ClearScope removes a scope's memory directory, coordinating with the pipeline lock so a running distillation cannot resurrect a half-cleared scope.

It reports busy=true (deleting nothing) if the pipeline currently holds the lock — the caller should ask the user to retry. Otherwise it holds the lock across the delete (a concurrent pipeline's non-blocking TryLockPipeline keeps failing), which closes the release-then-delete race the naive version had. On Windows RemoveAll can hit a sharing violation on the still-open lock file; once the handle is released a retry succeeds, so we release then retry.

func EnsureScope

func EnsureScope(scopeRoot string) error

EnsureScope creates the standard layout for a scope root.

func GlobalRoot

func GlobalRoot() string

GlobalRoot returns the scope root for cross-project memory.

func NewPathGuardMiddleware

func NewPathGuardMiddleware(root string) adk.ChatModelAgentMiddleware

NewPathGuardMiddleware confines every tool call to root: any path-bearing argument that resolves outside root is rejected before the tool runs. This is the implementation-level containment for the consolidation subagent — it does not rely on the prompt.

func NewUsageMiddleware

func NewUsageMiddleware() adk.ChatModelAgentMiddleware

NewUsageMiddleware returns the middleware; safe to add unconditionally (it is a no-op for tool calls that never touch the memory root).

func ProjectRoot

func ProjectRoot(projectDir string) string

ProjectRoot returns the scope root for a project working directory.

func ProjectSlug

func ProjectSlug(projectDir string) string

ProjectSlug derives the stable per-project directory name: <sanitized-basename>-<hash8-of-canonical-path>. The hash keeps same-named projects apart; the basename keeps the directory human-readable.

func RecordUsage

func RecordUsage(absPath string)

RecordUsage bumps the usage counter for a memory file. absPath must be an absolute path somewhere under Root(); anything else is silently ignored so the middleware can call this unconditionally.

func Redact

func Redact(s string) string

Redact masks common credential shapes before anything is persisted to the memory store. It runs on memory_note input, phase-1 pipeline input and output (see design §6.1). Idempotent: redacted text passes through unchanged.

func Root

func Root() string

Root returns the memory root directory (~/.jcode/memory). It follows config.ConfigDir() so isolated-HOME test environments are respected.

func ScopeRootFor

func ScopeRootFor(scope, projectDir string) string

ScopeRootFor maps a memory_note scope value to its directory.

func TruncateRunes

func TruncateRunes(s string, maxChars int, suffix string) string

TruncateRunes truncates s to at most maxChars bytes without splitting a UTF-8 rune, then appends suffix. Byte-count budgeting (not rune count) is intentional — token/size limits are byte-based — but the cut lands on a rune boundary so multibyte text (e.g. Chinese) is never corrupted.

func TryLockPipeline

func TryLockPipeline(scopeRoot string) (func(), bool, error)

TryLockPipeline takes the scope's non-blocking pipeline lock. Returns a release func and whether the lock was acquired (false = another process is already running the pipeline).

func UpdateState

func UpdateState(scopeRoot string, fn func(*State) error) error

UpdateState applies fn to the scope's state under an exclusive file lock and persists the result atomically. Lost updates are prevented by re-reading inside the lock.

func WithoutUsageAccounting

func WithoutUsageAccounting(ctx context.Context) context.Context

WithoutUsageAccounting marks a context so the usage middleware ignores tool calls made under it. The consolidation agent reads every memory file each run; letting that count as "usage" would distort the ranking signal.

func WriteNote

func WriteNote(n Note) (string, error)

WriteNote validates, redacts and persists a note into the scope's inbox. Returns the absolute path of the created file.

Types

type ConsolidationRecord

type ConsolidationRecord struct {
	At           string         `json:"at"` // RFC3339
	NoopFastPath bool           `json:"noop_fast_path"`
	Decisions    map[string]int `json:"decisions,omitempty"` // op → count
	Commit       string         `json:"commit,omitempty"`
}

ConsolidationRecord summarizes a phase-2 run (M3). Decisions holds the ADD/UPDATE/DELETE/NOOP protocol output so runs are assertable.

type ExtractRecord

type ExtractRecord struct {
	At          string `json:"at"` // RFC3339
	SummaryFile string `json:"summary_file,omitempty"`
	UsageCount  int    `json:"usage_count"`
	LastUsage   string `json:"last_usage,omitempty"`
	Failed      bool   `json:"failed,omitempty"`
	FailCount   int    `json:"fail_count,omitempty"` // consecutive extraction failures (backoff)
	Error       string `json:"error,omitempty"`
}

ExtractRecord tracks one extracted session (phase 1, M2).

type FileUsage

type FileUsage struct {
	UsageCount int    `json:"usage_count"`
	LastUsage  string `json:"last_usage,omitempty"` // RFC3339
}

FileUsage is the usage-feedback loop: bumped whenever the agent reads a memory file (see UsageMiddleware), consumed by consolidation ranking.

type Note

type Note struct {
	Scope     string // "project" (default) | "global"
	Kind      string // preference | fact | pitfall | workflow
	Source    string // "user" (explicit "remember X") | "agent"
	Text      string
	SessionID string
	Cwd       string
}

Note is one L1 inbox entry. Notes only ever land in the notes/ inbox — the curated files (MEMORY.md, memory_summary.md) are maintained solely by the phase-2 consolidation agent, keeping cheap-and-fast decoupled from expensive-and-curated.

type NoteFile

type NoteFile struct {
	Path   string
	Name   string
	Kind   string
	Source string
	Time   string
	Text   string
}

NoteFile is a parsed inbox note (for injection and /memory display).

func RecentNotes

func RecentNotes(scopeRoot string, limit int) []NoteFile

RecentNotes returns up to limit inbox notes for a scope, newest first.

type State

type State struct {
	Version int `json:"version"`
	// Files tracks read-usage per memory file (scope-root-relative path).
	// Consolidation ranks by usage and expires long-unused entries.
	Files map[string]*FileUsage `json:"files,omitempty"`
	// Extracted tracks phase-1 work per source session UUID (M2).
	Extracted map[string]*ExtractRecord `json:"extracted,omitempty"`
	// Budget is the pipeline token ledger per day ("2026-07-04" → tokens).
	Budget map[string]int64 `json:"budget,omitempty"`
	// LastConsolidation records the most recent phase-2 outcome (M3).
	LastConsolidation *ConsolidationRecord `json:"last_consolidation,omitempty"`
	// LastPipelineAt is when the pipeline last ran (cooldown gate). RFC3339.
	LastPipelineAt string `json:"last_pipeline_at,omitempty"`
}

State is the per-scope coordination file (state.json). It replaces the SQLite database Codex uses: entry counts are in the thousands at most, and flock + atomic rename matches the concurrency conventions of internal/session and internal/automation.

func LoadState

func LoadState(scopeRoot string) *State

LoadState reads state.json without locking (callers that mutate must use UpdateState). A missing or corrupt file yields a fresh state rather than an error: memory must never take the agent down.

Directories

Path Synopsis
Package pipeline implements the offline memory distillation pipeline (design §5): phase 1 extracts durable facts per ended session with a cheap model; phase 2 consolidates them into curated artifacts with a restricted subagent, git-diff driven with a zero-token no-op fast path.
Package pipeline implements the offline memory distillation pipeline (design §5): phase 1 extracts durable facts per ended session with a cheap model; phase 2 consolidates them into curated artifacts with a restricted subagent, git-diff driven with a zero-token no-op fast path.

Jump to

Keyboard shortcuts

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