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 testvspytestvscargo 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