Skip to content

Structured memory with USER.md and character limits #910

@matt-carvalho

Description

@matt-carvalho

Task: Structured memory with USER.md and character limits

Description

Split agent memory into two files: MEMORY.md (agent notes, environment facts, lessons learned) and USER.md (user preferences, habits, personal details). Both live in the group's directory alongside the existing CLAUDE.md. Inject both files into the agent's system prompt at session start via the container runner. Character limits force the agent to consolidate entries rather than growing unbounded.

Acceptance Criteria

  • USER.md is created alongside MEMORY.md in groups/{name}/ when a group is first registered (or on first agent invocation if missing)
  • Both files are injected into the system prompt append string in container/agent-runner/src/index.ts (via the global CLAUDE.md injection path or container-runner mount)
  • MEMORY.md has a 3000-character limit documented in the injected prompt instructions
  • USER.md has a 2000-character limit documented in the injected prompt instructions
  • Character limits are enforced by prompt instructions (agent-driven consolidation), not by code truncation
  • Files use atomic writes (write to .tmp then rename) to prevent corruption from concurrent access
  • Existing MEMORY.md content is preserved — no data loss on upgrade
  • Groups without USER.md get an empty one created automatically

Technical Details

System prompt injection

In container/agent-runner/src/index.ts, the globalClaudeMd variable already reads /workspace/global/CLAUDE.md. Extend this to also read /workspace/group/USER.md and /workspace/group/MEMORY.md:

// After loading globalClaudeMd:
const userMdPath = '/workspace/group/USER.md';
const memoryMdPath = '/workspace/group/MEMORY.md';
const userMd = fs.existsSync(userMdPath) ? fs.readFileSync(userMdPath, 'utf-8') : '';
const memoryMd = fs.existsSync(memoryMdPath) ? fs.readFileSync(memoryMdPath, 'utf-8') : '';

// Append memory context to system prompt
const memoryContext = [
  userMd ? `\n## User Profile (USER.md — max 2000 chars)\n${userMd}` : '',
  memoryMd ? `\n## Agent Memory (MEMORY.md — max 3000 chars)\n${memoryMd}` : '',
].filter(Boolean).join('\n');

Memory instructions (added to system prompt append)

## Memory Management Instructions
- Save user preferences, habits, and personal details to USER.md (max 2000 chars)
- Save environment facts, project conventions, and lessons learned to MEMORY.md (max 3000 chars)
- When a memory file exceeds 80% of its character limit, consolidate related entries and remove stale/redundant ones before adding new content
- Use atomic writes: write to a .tmp file then rename to avoid corruption
- Mid-session: write to disk immediately but note that the system prompt snapshot is frozen at session start

Container runner mounts

Verify that groups/{name}/ is already mounted writable at /workspace/group/ — it should be from existing architecture. USER.md and MEMORY.md are just files in that directory.

Auto-creation

In src/container-runner.ts buildVolumeMounts() or before runContainerAgent(), ensure USER.md exists:

const userMdPath = path.join(groupDir, 'USER.md');
if (!fs.existsSync(userMdPath)) {
  fs.writeFileSync(userMdPath, '# User Profile\n\n_No preferences saved yet._\n');
}

Files affected

  • container/agent-runner/src/index.ts — read USER.md + MEMORY.md, append to system prompt
  • src/container-runner.ts — ensure USER.md exists before agent spawn

Dependencies

  • None — uses existing file mounts and system prompt injection

Effort Estimate

  • Size: S
  • Hours: 3
  • Parallel: true (independent of search tasks)

Definition of Done

  • USER.md created automatically for groups that don't have one
  • Both files injected into system prompt with character limit instructions
  • Existing MEMORY.md preserved intact
  • npm run build succeeds for both host and agent-runner
  • Manual test: agent saves user preference to USER.md, persists across sessions

Part of Epic: #907

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions