Memory

How agents remember project-specific rules and context. The AGENTS.md system.

Evidence source: Amp Code v0.0.1769212917 (guidance discovery, settings cascade, skills)


The Problem

Every project is different:

  • Different test commands (npm test vs pytest vs cargo test)
  • Different conventions (tabs vs spaces, naming styles)
  • Different architectures (where things go)
  • Different forbidden patterns (don't touch X, always do Y)

Without memory, the agent must relearn these every session. Worse, it might violate project rules it doesn't know about.


The Solution: AGENTS.md

A file in the project that the agent reads at startup, containing project-specific instructions.

project/
├── AGENTS.md      # Project memory (or any of 7 variants)
├── src/
├── tests/
└── ...

Amp's Insight: Support multiple filename variants so projects can use whatever convention fits.


File Discovery

7 Supported Filenames

Amp looks for these files (in order):

Filename Notes
AGENTS.md Primary convention
Agents.md Case variant
agents.md Lowercase
AGENT.md Singular variant
Agent.md Case variant
agent.md Lowercase
CLAUDE.md Claude Code compatibility

Discovery Algorithm

GUIDANCE_FILENAMES = [
    "AGENTS.md", "Agents.md", "agents.md",
    "AGENT.md", "Agent.md", "agent.md",
    "CLAUDE.md"
]

def discover_guidance_files(working_dir: str, project_root: str) -> list[GuidanceFile]:
    """Find all guidance files from working dir to project root."""
    files = []

    # Walk up from working directory to project root
    current = working_dir
    while True:
        for filename in GUIDANCE_FILENAMES:
            path = os.path.join(current, filename)
            if os.path.exists(path):
                content = read_file(path)
                files.append(GuidanceFile(
                    path=path,
                    content=content,
                    scope=determine_scope(path, working_dir, project_root)
                ))
                break  # Only one per directory

        if current == project_root:
            break
        current = os.path.dirname(current)

    # Also check user config directories (~/.config/amp and ~/.config)
    user_guidance = find_user_guidance()
    if user_guidance:
        files.append(user_guidance)

    return files

Scope Priority

Guidance files have different scopes with clear priority:

Priority Scope Location Applies To
1 (highest) User ~/.config/amp/AGENTS.md All projects
2 Project Project root Entire project
3 Parent Parent directories Subtree below
4 Subtree Current directory Current subtree

Scope Resolution

def determine_scope(file_path: str, working_dir: str, project_root: str) -> str:
    """Determine the scope of a guidance file."""
    file_dir = os.path.dirname(file_path)

    # User-level (home directory)
    if file_dir.startswith(os.path.expanduser("~/.config/amp")):
        return "user"

    # Project root
    if file_dir == project_root:
        return "project"

    # Is working dir inside this directory?
    if working_dir.startswith(file_dir):
        return "parent"  # Applies to subtree

    return "subtree"  # Applies only to this subtree

Merging Guidance

When multiple files exist, they're concatenated with scope labels:

def build_guidance_prompt(files: list[GuidanceFile]) -> str:
    """Build guidance section for system prompt."""
    if not files:
        return ""

    parts = ["<discovered_guidance_files>"]

    for file in sorted(files, key=lambda f: SCOPE_PRIORITY[f.scope]):
        parts.append(f"<guidance_file path=\"{file.path}\" scope=\"{file.scope}\">")
        parts.append(file.content)
        parts.append("</guidance_file>")

    parts.append("</discovered_guidance_files>")

    return "\n".join(parts)

YAML Frontmatter

Guidance files can include YAML frontmatter for structured configuration:

---
description: Backend API service
alwaysApply: true
globs:
  - "src/**/*.ts"
  - "!src/**/*.test.ts"
---

# API Service Guidelines

Use Express.js patterns. All routes need OpenAPI docs.

Frontmatter Schema

@dataclass
class GuidanceFrontmatter:
    description: str | None = None      # Human-readable description
    alwaysApply: bool = False           # Apply even outside glob match
    globs: list[str] | None = None      # File patterns this applies to

Glob-Based Scoping

def should_apply_guidance(file: GuidanceFile, current_file: str) -> bool:
    """Check if guidance applies to the current file being edited."""
    frontmatter = parse_frontmatter(file.content)

    # Always apply if flag set
    if frontmatter.alwaysApply:
        return True

    # No globs = applies to everything
    if not frontmatter.globs:
        return True

    # Check glob patterns
    for pattern in frontmatter.globs:
        if pattern.startswith("!"):
            # Exclusion pattern
            if fnmatch(current_file, pattern[1:]):
                return False
        else:
            if fnmatch(current_file, pattern):
                return True

    return False

Cross-References with @-Mentions

Guidance files can reference other files using @-mentions:

# Project Guidelines

See @src/architecture.md for architecture decisions.
Follow patterns in @src/examples/good-component.tsx.

@-Mention Resolution

def resolve_at_mentions(content: str, base_dir: str) -> str:
    """Resolve @file references to actual content."""
    pattern = r'@([^\s]+)'

    def replace_mention(match):
        relative_path = match.group(1)
        full_path = os.path.join(base_dir, relative_path)

        if os.path.exists(full_path):
            file_content = read_file(full_path)
            return f"\n<referenced_file path=\"{relative_path}\">\n{file_content}\n</referenced_file>\n"
        else:
            return match.group(0)  # Leave as-is if not found

    return re.sub(pattern, replace_mention, content)

Guarded Files

Certain files should never be modified by the agent. Amp maintains a list of protected patterns.

15 Guarded Categories

Category Patterns Why
Environment .env, .env.* Secrets
Credentials *credentials*, *secrets* Security
Keys *.pem, *.key, *.p12 Cryptographic material
Auth tokens *token*, *oauth* Authentication
Config secrets *password*, *apikey* Credentials
SSH .ssh/* SSH keys
AWS .aws/* Cloud credentials
GPG .gnupg/* Encryption keys
Kubernetes kubeconfig Cluster access
Docker docker-compose.override.yml Local overrides
IDE .idea/*, .vscode/* User preferences
Git internals .git/* Repository internals
Node modules node_modules/* Dependencies
Lock files package-lock.json, yarn.lock Should use package manager
Build outputs dist/*, build/* Generated files

Implementation

GUARDED_PATTERNS = [
    # Environment and secrets
    ".env", ".env.*", ".env.local", ".env.*.local",
    "*credentials*", "*secrets*", "*password*", "*apikey*", "*token*",

    # Cryptographic
    "*.pem", "*.key", "*.p12", "*.pfx", "*.jks",

    # Cloud and config
    ".ssh/*", ".aws/*", ".gnupg/*", "kubeconfig",

    # IDE and tools
    ".idea/*", ".vscode/settings.json",

    # Dependencies and generated
    "node_modules/*", "dist/*", "build/*",
    "package-lock.json", "yarn.lock", "pnpm-lock.yaml"
]

def is_guarded_file(path: str) -> bool:
    """Check if file is protected from modification."""
    for pattern in GUARDED_PATTERNS:
        if fnmatch(path, pattern):
            return True
    return False

def check_edit_permission(path: str) -> tuple[bool, str | None]:
    """Check if edit is allowed."""
    if is_guarded_file(path):
        return False, f"Cannot edit guarded file: {path}"
    return True, None

Settings Cascade

Settings come from multiple sources with clear precedence:

6 Priority Levels (low → high)

Priority Source Example
1 (lowest) Package.json defaults package.json amp config
2 Admin/managed settings /etc/ampcode/managed-settings.json
3 Global user settings ~/.config/amp/settings.json
4 Workspace settings .amp/settings.json
5 Environment variables AMP_MODEL=...
6 (highest) Dynamic runtime settings In-memory overrides

Settings Resolution

def resolve_settings() -> Settings:
    """Resolve settings from all sources."""
    settings = Settings()  # Package.json defaults (lowest)

    # Layer 2: Admin/managed settings
    admin = load_json("/etc/ampcode/managed-settings.json")
    settings.merge(admin)

    # Layer 3: Global user settings
    user = load_json(os.path.expanduser("~/.config/amp/settings.json"))
    settings.merge(user)

    # Layer 4: Workspace settings (.amp/settings.json or .amp/settings.jsonc)
    workspace = load_workspace_settings()
    settings.merge(workspace)

    # Layer 5: Environment variables
    env_settings = parse_env_settings()
    settings.merge(env_settings)

    # Layer 6: Dynamic runtime settings (highest priority)
    runtime = load_runtime_settings()
    settings.merge(runtime)

    return settings

Environment Variables

Amp recognizes 25+ environment variables:

Core Variables

Variable Purpose Example
AMP_API_KEY Anthropic API key sk-ant-...
AMP_MODEL Default model claude-sonnet-4-20250514
AMP_MODE Operating mode smart, rush, free
AMP_MAX_TOKENS Max output tokens 8192

Provider Keys

Variable Provider
ANTHROPIC_API_KEY Anthropic
OPENAI_API_KEY OpenAI
GOOGLE_API_KEY Google AI
VERTEX_API_KEY Google Vertex

Feature Flags

Variable Purpose
AMP_DISABLE_TELEMETRY Disable analytics
AMP_DISABLE_COURSE_CORRECTION Skip course correction
AMP_DEBUG Enable debug logging

Skills Discovery

Skills extend agent capabilities through markdown files.

12 Discovery Locations (priority order)

Priority Path Type
1 .agents/skills/ Local project skill
2 Parent dirs .agents/skills/ Inherited project skill
3 ~/.config/agents/skills/ Global agents skill
4 ~/.config/amp/skills/ Global amp skill
5 .claude/skills/ Local claude skill
6 Parent dirs .claude/skills/ Inherited claude skill
7 ~/.claude/skills/ Global claude skill
8 ~/.claude/plugins/cache/ Plugin cache skill
9 $AMP_TOOLBOX directories Environment toolbox
10 ~/.config/amp/tools/ Default toolbox
11 amp.skills.path setting Custom path skill
12 Built-in skills Always available
def discover_skills(workspace_roots: list[str], settings: dict) -> list[Skill]:
    """Find all available skills in priority order."""
    skills = []

    # Build search roots from workspace roots + parents
    search_roots = build_search_roots(workspace_roots)

    # 1-2: .agents/skills in roots and parents
    for root in search_roots:
        skills.extend(load_skills(os.path.join(root, ".agents", "skills")))

    # 3-4: Global ~/.config paths
    skills.extend(load_skills(os.path.expanduser("~/.config/agents/skills")))
    skills.extend(load_skills(os.path.expanduser("~/.config/amp/skills")))

    # 5-6: .claude/skills in roots and parents
    for root in search_roots:
        skills.extend(load_skills(os.path.join(root, ".claude", "skills")))

    # 7-8: Global Claude paths
    skills.extend(load_skills(os.path.expanduser("~/.claude/skills")))
    skills.extend(load_skills(os.path.expanduser("~/.claude/plugins/cache")))

    # 9: AMP_TOOLBOX env dirs (colon-separated)
    for path in parse_path_list(os.environ.get("AMP_TOOLBOX", "")):
        skills.extend(load_skills(path))

    # 10: Default toolbox
    skills.extend(load_skills(os.path.expanduser("~/.config/amp/tools")))

    # 11: Custom settings path (amp.skills.path)
    for path in parse_path_list(settings.get("amp.skills.path", "")):
        skills.extend(load_skills(path))

    # 12: Built-in skills (always last)
    skills.extend(load_builtin_skills())

    return skills

Skill File Format

---
name: run-tests
description: Run the project test suite
triggers:
  - "run tests"
  - "test this"
  - "/test"
---

# Run Tests

Execute the test suite for this project.

## Steps

1. Detect test framework (jest, vitest, pytest, etc.)
2. Run appropriate command
3. Report results

## Commands by Framework

- Jest: `npm test`
- Vitest: `npx vitest`
- Pytest: `pytest`
- Cargo: `cargo test`

Skill Discovery

def discover_skills(project_root: str) -> list[Skill]:
    """Find all available skills."""
    skills = []

    for search_path in SKILL_SEARCH_PATHS:
        # Expand user home
        path = os.path.expanduser(search_path)

        # Make relative paths absolute
        if not os.path.isabs(path):
            path = os.path.join(project_root, path)

        if not os.path.exists(path):
            continue

        # Find all .md files
        for file in glob.glob(os.path.join(path, "**/*.md"), recursive=True):
            skill = parse_skill_file(file)
            if skill:
                skills.append(skill)

    return skills

Memory File Best Practices

Structure

# Project Name

Brief description of what this project is.

## Quick Reference

- Test: `npm test`
- Build: `npm run build`
- Lint: `npm run lint`

## Architecture

- `/src/api` - REST endpoints
- `/src/services` - Business logic
- `/src/db` - Database access

## Conventions

- Use TypeScript strict mode
- Prefer named exports
- Use Zod for validation

## Do NOT

- Modify files in `/generated/`
- Use `any` type
- Skip tests for new functions

## Important Files

- `src/config.ts` - All configuration
- `src/types.ts` - Shared types

Key Principles

Principle Why
Concise Agent reads this every session
Actionable Commands, not explanations
Specific Exact file paths, exact commands
Negative rules "Do NOT" sections prevent mistakes

Session Memory

Within a session, track context without AGENTS.md:

@dataclass
class SessionContext:
    """Track important context during a session."""
    files_read: set[str] = field(default_factory=set)
    files_modified: set[str] = field(default_factory=set)
    commands_run: list[str] = field(default_factory=list)
    errors_encountered: list[str] = field(default_factory=list)
    decisions_made: list[str] = field(default_factory=list)

    def record_read(self, path: str):
        self.files_read.add(path)

    def record_modify(self, path: str):
        self.files_modified.add(path)

    def record_command(self, cmd: str, exit_code: int):
        self.commands_run.append(f"{cmd} -> {exit_code}")

    def get_summary(self) -> str:
        return f"""
Session context:
- Files read: {len(self.files_read)}
- Files modified: {list(self.files_modified)}
- Commands run: {len(self.commands_run)}
- Errors: {len(self.errors_encountered)}
"""

Implementation Checklist

Building memory? Ensure:

  • File Discovery

    • Support all 7 filename variants (AGENTS/AGENT/CLAUDE)
    • Walk directory tree upward
    • Check user config directories (~/.config/amp and ~/.config)
    • Parse YAML frontmatter
  • Scope Handling

    • User > Project > Parent > Subtree priority
    • Glob-based filtering
    • @-mention resolution
  • Guarded Files

    • Block edits to secrets
    • Block edits to credentials
    • Block edits to lock files
  • Settings Cascade

    • Package.json defaults → managed → user → workspace → env → runtime
    • Environment variable parsing (AMP_*)
    • JSON/JSONC settings files
  • Skills

    • Search all 12 locations
    • Parse skill frontmatter
    • Match triggers to invocations

What's Next

We have all the pieces. Let's put them together.

10-minimal-agent.md - The complete working agent