Documentation
¶
Overview ¶
Package memdir loads the on-disk memory files that seed the agent's system prompt at session start, and provides the write helpers the auto-memory tools call mid-session:
- <workdir>/EVVA.md workdir memory — repo conventions (user-authored)
- <appHome>/USER_PROFILE.md user memory — preferences, working style (auto)
- <appHome>/projects/<projectKey(workdir)>/MEMORY.md project memory (auto)
All files are optional. Missing files yield zero-value Snapshot fields and no warning; the prompt builder skips empty sections cleanly. Any non-missing read failure (permission, oversize) is recorded in Snapshot.Warnings — Load itself never returns an error so the agent can always boot.
This package depends only on stdlib. It is not imported by the sysprompt package; the caller threads Snapshot fields into the prompt context, keeping the dependency arrow one-way.
Section parsing and merge helpers for the auto-memory files.
USER_PROFILE.md and per-project MEMORY.md both follow a "fixed set of H2 sections" shape: every save call hands us the bodies for a subset of the allowed sections, and we merge them into the existing file while preserving order, untouched sections, and any incidental whitespace.
We deliberately do NOT parse arbitrary markdown — only top-level "## " headings count as section boundaries. Nested headings, fenced code blocks containing "## ...", lists with "## " prefixes — all are treated as section body content. The trade-off: the parser is small and predictable, and the section bodies stay machine-mergeable. Real markdown ASTs would introduce a dependency and a class of bugs the tool description already rules out (sections are flat, edits are surgical).
Index ¶
- Constants
- Variables
- func EnsureProjectMemoryDir(appHome, workdir string) error
- func IndexSummary(content string, maxBodyChars int) string
- func MergeSections(existing string, updates map[string]string, allowed []string) (string, error)
- func ProjectKey(absPath string) string
- func ProjectMemoryPath(appHome, workdir string) string
- func ReadProjectMemory(appHome, workdir string) (string, string)
- func UserProfilePath(appHome string) string
- func WriteProjectMemory(appHome, workdir, content string) error
- func WriteUserProfile(appHome, content string) error
- type Snapshot
Constants ¶
const ( ProjectMemoryFile = "EVVA.md" UserProfileFile = "USER_PROFILE.md" )
File names. Exposed so other packages (Phase 9 user-profile background agent, future /memory slash commands) can write to the same paths without re-spelling them.
const MaxFileBytes = 64 * 1024
MaxFileBytes caps each memory file at 64 KiB. Past that the user is almost certainly using EVVA.md for the wrong thing (knowledge base, not conventions doc); we truncate and warn rather than refuse outright so a bloated file doesn't break the session.
const ProjectMemoryFileName = "MEMORY.md"
ProjectMemoryFileName is the on-disk basename of per-project memory. Lives under <APP_HOME>/projects/<key>/MEMORY.md.
const ProjectsSubdir = "projects"
ProjectsSubdir is the directory under APP_HOME that holds per-project memory keyed by ProjectKey(workdir).
Variables ¶
var ProjectMemorySections = []string{
"Project facts",
"Decisions",
"Open issues",
"References",
}
ProjectMemorySections is the closed set of headings allowed in <APP_HOME>/projects/<key>/MEMORY.md. Adapted from the ref taxonomy (user / feedback / project / reference) — "user" lives in USER_PROFILE.md, the remaining three are restated in project-centric language.
var UserProfileSections = []string{
"Preferences",
"Working style",
"Recurring topics",
}
UserProfileSections is the closed set of headings allowed in USER_PROFILE.md. Order is the canonical render order for newly-scaffolded files.
Functions ¶
func EnsureProjectMemoryDir ¶
EnsureProjectMemoryDir creates the parent directory for the per-project MEMORY.md, mirroring the ref pattern of pre-creating memory dirs at boot so the model never has to mkdir before its first write.
func IndexSummary ¶
IndexSummary renders a compact one-line-per-section overview of the supplied memory content, suitable for cache-static system-prompt injection.
For each "## " section in `content`, the output contains:
## <heading> — <first non-empty line, truncated to maxBodyChars>
Sections with empty bodies render as `## <heading> — (empty)`. The result is meant to be small (a few hundred chars even for a fully-populated file) so the model sees what's recorded without paying the full file cost.
func MergeSections ¶
MergeSections updates `existing` so that each entry in `updates` replaces the body of its corresponding "## <heading>" section. Sections not in `updates` are preserved verbatim. Headings that exist in `allowed` but not in the file are appended in `allowed` order. Headings present in `updates` but not in `allowed` cause an error before any merge happens.
Body semantics: an entry's value is treated as the new section body (trimmed of leading/trailing whitespace). A value of "" clears the body — useful when the model wants to drop a section's content while keeping the heading visible. To leave a section untouched, omit the key entirely.
func ProjectKey ¶
ProjectKey turns an absolute filesystem path into a stable directory key used under <APP_HOME>/projects/. Mirrors the convention Claude Code uses at ~/.claude/projects/ — the path's separators are flattened to "-" so the result is one segment that round-trips losslessly enough for human inspection.
Examples (POSIX):
/Users/johnny/lab/evva -> "-Users-johnny-lab-evva" /home/alice/work/api -> "-home-alice-work-api"
On Windows the volume colon is dropped and backslashes are flattened the same way:
C:\Users\Alice\proj -> "C-Users-Alice-proj"
An empty input yields "". Inputs are cleaned via filepath.Clean first so "/a//b/../c" and "/a/c" produce the same key.
func ProjectMemoryPath ¶
ProjectMemoryPath returns the absolute path of MEMORY.md for the given workdir, scoped to APP_HOME. Empty inputs yield "".
func ReadProjectMemory ¶
ReadProjectMemory reads the per-project MEMORY.md for the given workdir. Missing file returns ("", nil) — callers treat empty content as "no memory yet." Truncation and oversize behave like the read path in memdir.Load: cap at MaxFileBytes with a warning returned.
func UserProfilePath ¶
UserProfilePath returns the absolute path of USER_PROFILE.md given the caller's APP_HOME. Empty appHome yields "".
func WriteProjectMemory ¶
WriteProjectMemory writes `content` to <APP_HOME>/projects/<key>/MEMORY.md atomically. Creates the parent directory chain if missing.
func WriteUserProfile ¶
WriteUserProfile writes `content` to USER_PROFILE.md atomically (temp + rename in the same directory). Creates the parent directory if missing. Returns an error only on real I/O failure.
Types ¶
type Snapshot ¶
type Snapshot struct {
WorkdirMemory string // raw contents of <workdir>/EVVA.md (user-authored, repo-scoped)
UserProfile string // raw contents of <appHome>/USER_PROFILE.md (auto, user-scoped)
ProjectMemory string // raw contents of <appHome>/projects/<key>/MEMORY.md (auto, project-scoped)
ProjectMemoryIndex string // compact one-line-per-section summary of ProjectMemory; empty when no sections
Warnings []string // non-fatal: oversize-truncation, permission errors
}
Snapshot is one session's view of the on-disk memory files. Any body field may be empty when the file is missing, empty, or unreadable; callers treat empty as "skip the section."
func Load ¶
Load reads the memory files. Empty workdir or appHome silently skips the files anchored at that path. Files larger than MaxFileBytes are truncated with a warning. The function never returns an error.
loadProjectMemory gates the per-project MEMORY.md read — callers set it to cfg.EnableAutoMemory so disabled users avoid the extra stat.