Skip to content

Instantly share code, notes, and snippets.

@0xfauzi
Created October 17, 2025 11:08
Show Gist options
  • Select an option

  • Save 0xfauzi/7c8f65572930a21efa62623557d83f6e to your computer and use it in GitHub Desktop.

Select an option

Save 0xfauzi/7c8f65572930a21efa62623557d83f6e to your computer and use it in GitHub Desktop.
Agents.md best practices

AGENTS.md Best Practices for AI Coding Assistants: Comprehensive Guide

AGENTS.md has emerged as the de facto open standard for guiding AI coding assistants, now adopted by over 20,000 repositories and formalized in August 2025 through collaboration between OpenAI, Google, Cursor, Factory, and Sourcegraph. This file acts as a "README for machines"—providing structured, technical context that helps AI assistants write better code from the start. For Python + AWS + Terraform projects, a well-crafted AGENTS.md dramatically reduces friction, ensuring generated code follows your conventions, uses the right tools, and adheres to security requirements.

What is AGENTS.md and why it matters

AGENTS.md is a dedicated Markdown file that complements, not replaces, README.md. While README targets human developers with project overviews and quick-start guides, AGENTS.md contains detailed technical instructions specifically for AI coding agents. Think of it as onboarding documentation for an AI team member: exact commands to run, code patterns to follow, testing strategies, deployment workflows, and project-specific gotchas.

The standard provides immediate benefits. AI assistants understand project requirements without a discovery phase, generated code adheres to team standards from the start, and code review cycles shrink. Most importantly, it works across the entire ecosystem—OpenAI Codex, GitHub Copilot, Cursor, Claude Code, Aider, and more—eliminating the need for multiple tool-specific configuration files.

Key adoption milestone: GitHub Copilot added native AGENTS.md support in August 2025, joining OpenAI Codex, Cursor, Google's Jules and Gemini, Factory, Amp, Windsurf, Zed, and RooCode in supporting the standard. Android Studio's Gemini integration includes a context drawer specifically for managing AGENTS.md files.

Current standards and structure (late 2024/2025)

The standard intentionally remains simple and schema-free—just plain Markdown with no required structure. This flexibility drove adoption, but effective implementations follow consistent patterns.

File naming and location

Use AGENTS.md (plural) as the primary filename. Place it at the repository root alongside README.md. For monorepos or complex projects, place additional AGENTS.md files in subdirectories—the nearest file in the directory tree takes precedence, creating a hierarchical configuration system. OpenAI's main repository uses this pattern with 88 AGENTS.md files across subcomponents.

For backward compatibility with legacy tools, create symlinks:

ln -s AGENTS.md CLAUDE.md
ln -s AGENTS.md .github/copilot-instructions.md
ln -s AGENTS.md .cursorrules

Essential sections in priority order

Research across successful implementations reveals these core sections:

Development environment setup comes first. List exact commands for dependency installation, environment configuration, package manager usage, and tool version requirements. Be concrete:

## Environment Setup
- Python 3.12+
- Install UV: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- Create environment: `uv venv && source .venv/bin/activate`
- Install dependencies: `uv sync`
- Configure AWS: `aws configure` (requires credentials)

Build and test commands second. Provide file-scoped commands preferentially—agents should be able to check single files without running full builds. This dramatically improves iteration speed:

## Commands

### File-scoped (preferred - fast feedback)
uv run pytest tests/test_handlers.py
uv run ruff check src/handlers.py
uv run mypy src/handlers.py

### Full suite (only when explicitly requested)
uv run pytest --cov=src
terraform fmt -recursive

Code style and conventions third. Be explicit about formatting, naming patterns, architectural decisions, library preferences, and version-specific requirements:

## Code Style
- Python: Follow PEP 8, use type hints (mypy strict mode)
- Formatting: Ruff (line length 88)
- FastAPI: Async patterns for all I/O, Mangum for Lambda integration
- PynamoDB: Always define table_name and region in Meta class
- Terraform: snake_case naming, tag all resources (environment, project, managed_by)
- Never use `@ts-ignore` or hardcode credentials

Testing instructions and requirements. Document test frameworks, locations, coverage expectations, and how to run different test types:

## Testing
- Framework: pytest with moto for AWS service mocking
- Unit tests: `uv run pytest tests/unit/`
- Integration: `uv run pytest tests/integration/` (requires Docker)
- Coverage requirement: 80%+ for new code
- Mocking pattern: Use `@mock_dynamodb` decorator for DynamoDB tests

Project structure and key files. Help agents navigate your codebase without exploring repeatedly:

## Project Structure
- `src/handlers/` - Lambda function handlers (thin, delegate to services)
- `src/models/` - PynamoDB models (DynamoDB) and Pydantic models (API validation)
- `src/services/` - Business logic (keep handlers thin)
- `infrastructure/` - Terraform configs (modules pattern, workspaces for environments)
- Key file: `src/api/client.ts` - Use this typed client, not raw fetch calls

Safety and permission boundaries. Define what agents can do autonomously versus what requires approval:

## Permissions

### Allowed without prompting
- Read files, list directories
- Single file linting, type checking, formatting
- Unit tests on specific files

### Require approval first
- Package installations (`uv add`, `npm install`)
- Git operations (`git push`, `git commit`)
- File deletion
- Running full build or E2E test suites
- Terraform apply/destroy operations

Pull request and commit guidelines. Ensure generated code follows team workflows:

## PR Requirements
- Title format: [component] Brief description
- Always run `uv run pytest && uv run ruff check .` before committing
- Keep diffs small and focused
- Tests required for new features
- All CI checks must pass

Recommended file size and modularity

Keep AGENTS.md under 150 lines when possible. Agents load files into context windows, and excessive length wastes tokens while burying important information. For large projects, use nested AGENTS.md files in subdirectories or leverage file imports where supported:

# Root AGENTS.md
You are an experienced Python developer.

@./docs/python-guidelines.md

## Testing
@./docs/testing-patterns.md

Android Studio's Gemini and some other tools support this @./path/to/file.md syntax for modular documentation.

How AI tools parse and use AGENTS.md

Understanding how different tools consume AGENTS.md helps you write more effective instructions.

Context injection and parsing

OpenAI Codex automatically searches for AGENTS.md files whose scope includes modified files, prioritizing deeper nested instructions in a cascading configuration pattern. Contents are included in the system prompt, and Codex's /init command can generate an initial AGENTS.md by analyzing project structure.

GitHub Copilot processes AGENTS.md server-side as of August 2025. It automatically detects and appends contents to system prompts for Copilot Chat, Coding Agent, and Code Review features. Path-specific instructions use YAML frontmatter with glob patterns:

---
applyTo: "src/handlers/**/*.py"
---
Lambda handler instructions here

Cursor IDE supports both simple AGENTS.md (automatic detection at project root) and an advanced .cursor/rules/ system with .mdc files that include metadata for conditional loading:

---
description: "DynamoDB model standards"
globs: "src/models/**/*.py"
alwaysApply: false
---
PynamoDB patterns and conventions

Cursor offers four rule types: Always (always included), Auto Attached (included when matching files are referenced), Agent Requested (AI decides based on description), and Manual (only when explicitly mentioned).

Claude Code primarily uses CLAUDE.md but reads AGENTS.md via symlinks. Its /init command generates customized project instructions including code hooks for pre/post-edit validation. Claude Code supports hierarchical files where the most nested takes precedence, and integrates with a Skills system—organized folders with SKILL.md files for specialized capabilities.

Other tools: Aider requires configuration via .aider.conf.yml (read: AGENTS.md), Gemini CLI via .gemini/settings.json ({"contextFileName": "AGENTS.md"}), and most modern tools either support AGENTS.md natively or through simple configuration.

Context window management

Different tools have varying token limits: OpenAI Codex offers 128k-192k tokens, Claude Opus 4 provides 200k, GitHub Copilot varies by model (GPT-4o ~128k, Claude available), and Gemini reaches 1 million tokens (2 million for Pro).

Tools employ several context prioritization strategies. Proximity-based approaches favor closer or nested files. Relevance-based systems let the AI decide what's important from file descriptions. Explicit @-mentions force inclusion. Automatic trimming removes older context as limits approach. Hierarchical merging combines parent and child rules.

Search and retrieval methods

Most tools combine multiple search approaches. Text-based search (grep, ripgrep) provides fast exact matches. Semantic search with vector embeddings finds conceptually related code. AST-based search parses code structure for accurate, language-aware results. Effective AGENTS.md files account for these by providing clear, searchable keywords and explicit file paths.

Real-world examples and patterns

Pattern 1: Command-first structure

The most effective AGENTS.md files lead with commands rather than explanations. Setup commands first, testing second, deployment third, debugging last. From OpenAI's sample:

## Dev environment tips
- Use `pnpm dlx turbo run where <project_name>` to jump to a package
- Run `pnpm install --filter <project_name>` to add the package
- Check the name field inside package.json to confirm the right name

## Testing instructions
- Find the CI plan in .github/workflows folder
- Run `pnpm turbo run test --filter <project_name>`
- From package root: `pnpm test`
- Focus on one test: `pnpm vitest run -t "<test name>"`

## PR instructions
- Title format: [<project_name>] <Title>
- Always run `pnpm lint` and `pnpm test` before committing

Pattern 2: Good and bad examples

Strong implementations explicitly point to files demonstrating best patterns and warn against legacy code:

## Code Examples

### ✅ Good Patterns
- PynamoDB models: `src/models/user.py` (hash/range keys, batch operations)
- FastAPI handlers: `src/handlers/api.py` (async, Mangum integration)
- Terraform modules: `infrastructure/modules/lambda/` (reusable, well-documented)

### ❌ Avoid These
- `src/legacy/old_handler.py` - Synchronous patterns (deprecated)
- `infrastructure/monolith.tf` - Pre-modules structure (being migrated)
- Hardcoded colors, credentials, or region names anywhere

Pattern 3: Hierarchical organization for monorepos

Large codebases use nested AGENTS.md files to provide context-specific guidance:

project/
├── AGENTS.md                    # Organization-wide standards
├── backend/
│   └── AGENTS.md               # Python/FastAPI specific
├── infrastructure/
│   └── AGENTS.md               # Terraform specific
└── frontend/
    └── AGENTS.md               # React specific

Each file focuses on its domain. The root covers general practices, while subdirectory files provide technology-specific details.

Python + AWS + Terraform best practices

For serverless Python applications with AWS and infrastructure-as-code, AGENTS.md requires special considerations.

Technology stack documentation

Be explicit about versions, package managers, and tools:

# Technology Stack
- **Runtime**: Python 3.12 (AWS Lambda)
- **Package Manager**: UV (replaces pip/poetry)
- **API Framework**: FastAPI with Mangum adapter
- **AWS Services**: Lambda, DynamoDB, S3, API Gateway
- **IaC**: Terraform 1.5+ with S3 backend + DynamoDB locking
- **Database ORM**: PynamoDB for DynamoDB
- **Validation**: Pydantic v2 models
- **Testing**: pytest with moto (AWS service mocking)

UV package manager specifics

UV is 10-100x faster than pip and replaces multiple tools. Document its complete workflow:

## UV Package Manager

### Initial Setup
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv
source .venv/bin/activate  # Linux/Mac

### Dependency Management
uv sync                     # Install from lock file
uv add boto3                # Add runtime dependency
uv add --group dev pytest   # Add dev dependency
uv add "fastapi>=0.110,<1"  # Add with version constraint
uv lock                     # Update lock file
uv run <command>            # Run command in venv

### Important
- Commit `uv.lock` for reproducibility
- Use `uv sync` for consistent environments
- Always specify version constraints for AWS SDKs

PynamoDB and DynamoDB patterns

PynamoDB requires specific configuration. Document common patterns:

## PynamoDB Models

### Basic Structure
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute, NumberAttribute

class UserModel(Model):
    class Meta:
        table_name = 'users'
        region = 'us-east-1'  # CRITICAL: Must match table region
    
    user_id = UnicodeAttribute(hash_key=True)
    email = UnicodeAttribute()
    created_at = NumberAttribute()
    metadata = UnicodeAttribute(null=True)  # Use null=True for optional

### Best Practices
- Always define table_name and region in Meta class
- Use batch_write() and batch_get() for multiple items (efficiency)
- Mark optional fields with null=True (saves storage costs)
- Use conditional operations for concurrency control
- Test with moto: `@mock_dynamodb` decorator

Pydantic models for validation

Pydantic serves dual purposes—API request/response validation and data modeling:

## Pydantic Models

from pydantic import BaseModel, Field, EmailStr

class UserRequest(BaseModel):
    email: EmailStr
    name: str = Field(min_length=1, max_length=100)
    age: int | None = Field(None, gt=0, lt=150)
    
    class Config:
        json_schema_extra = {
            "example": {
                "email": "user@example.com",
                "name": "John Doe",
                "age": 30
            }
        }

### Usage
- Request/response validation in FastAPI
- Field() for constraints (min_length, gt, lt, regex)
- Type unions (|) for optional fields
- Nested models for complex structures
- Examples auto-generate OpenAPI documentation

FastAPI + Lambda integration

Document the Mangum adapter pattern for FastAPI in Lambda:

## Lambda Handlers

from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: str):
    # Separate handler from business logic
    return UserService().get_user(user_id)

# Lambda handler
handler = Mangum(app)

### Best Practices
- Keep handlers thin, move logic to service classes
- Initialize AWS clients outside handler for reuse (Lambda container reuse)
- Use async patterns for all I/O operations
- Handle cold starts: provisioned concurrency or SnapStart for critical endpoints
- Environment variables for configuration (never hardcode)

Terraform documentation

Infrastructure-as-code requires comprehensive command documentation:

## Terraform Commands

### Initialization and Validation
cd infrastructure
terraform init
terraform validate
terraform fmt -recursive  # Always run before committing

### Planning and Applying
terraform plan -var-file=environments/dev.tfvars -out=tfplan
terraform apply tfplan

### Workspace Management (Multi-Environment)
terraform workspace list
terraform workspace select dev
terraform workspace new staging

### State Management
terraform state list
terraform state show aws_lambda_function.api
terraform show

### Destruction
terraform destroy -var-file=environments/dev.tfvars

## Terraform Best Practices
- Store state in S3 with DynamoDB locking
- Use workspaces for environments (dev/staging/prod)
- Tag all resources: environment, project, managed_by, cost_center
- Use modules for reusable components
- Document all variables with description and type
- Enable versioning on S3 state bucket
- Always run `terraform fmt` before committing
- Use `terraform plan` before every apply

Testing with moto

AWS service mocking is critical for unit tests:

## Testing with Moto

### Basic Pattern
import pytest
from moto import mock_dynamodb

@mock_dynamodb
def test_user_creation():
    # Moto intercepts boto3 calls
    UserModel.create_table(read_capacity_units=1, write_capacity_units=1)
    user = UserModel(user_id='123', email='test@example.com')
    user.save()
    
    # Retrieve and assert
    retrieved = UserModel.get('123')
    assert retrieved.email == 'test@example.com'

### Test Structure
tests/
├── unit/              # Mocked AWS services
│   ├── test_models.py
│   └── test_handlers.py
├── integration/       # Real AWS (LocalStack or deployed)
│   └── test_api.py
└── conftest.py        # Shared fixtures

### Commands
uv run pytest tests/unit/              # Fast, mocked
uv run pytest tests/integration/       # Slower, requires setup
uv run pytest --cov=src --cov-report=html

Deployment workflows

Document both local and CI/CD deployment:

## Deployment

### Local Development
uv run uvicorn src.main:app --reload  # FastAPI locally
sam local invoke MyFunction -e events/test.json  # Test Lambda locally
sam local start-api  # Local API Gateway

### Infrastructure Deployment
cd infrastructure
terraform workspace select prod
terraform plan -var-file=environments/prod.tfvars
terraform apply -var-file=environments/prod.tfvars

### CI/CD Pipeline (GitHub Actions)
- Workflow: `.github/workflows/deploy.yml`
- Triggered on: merge to main
- Stages: Test → Security Scan → Terraform Plan → Manual Approval → Terraform Apply
- Deployment gates:
  - All tests pass (pytest, mypy, ruff)
  - Security scan clean (Snyk, Bandit)
  - Code coverage ≥80%
  - Manual approval for production

### Rollback
terraform workspace select prod
terraform apply -var-file=environments/prod.tfvars -target=aws_lambda_function.api -var="lambda_version=previous"

Multi-tool project organization

For projects combining service code and infrastructure:

Option 1: Single comprehensive AGENTS.md

# Project: Serverless API

[Overview section]
[Python/FastAPI section]
[PynamoDB section]
[Testing section]
[Terraform section]
[Deployment section]

Option 2: Nested AGENTS.md files (preferred for complex projects)

project/
├── AGENTS.md                    # Overview + integration points
├── src/
│   └── AGENTS.md               # Python, FastAPI, PynamoDB, testing
├── infrastructure/
│   └── AGENTS.md               # Terraform, state management, modules
└── tests/
    └── AGENTS.md               # Testing strategies, mocking patterns

Start with a single file. Split into subdirectories when any section exceeds 200 lines.

Security and secrets handling

Security is paramount in AGENTS.md. One leaked credential can compromise entire systems.

Never include in AGENTS.md

Absolutely forbidden:

  • API keys, tokens, passwords, or any credentials
  • Database connection strings with passwords
  • AWS access keys or secret keys
  • Private encryption keys or certificates
  • OAuth secrets or JWT signing keys
  • Production IP addresses or internal URLs
  • Customer data or personally identifiable information
  • Proprietary algorithms or trade secrets
  • Security vulnerability details
  • Detailed firewall or security group configurations

Research shows GitHub identified 39 million leaked secrets in 2024, with projects using AI assistants showing a 40% increase in secrets exposure. AI assistants may also inadvertently include leaked secrets in generated code.

What to include instead

Document where secrets live and how to access them, never the secrets themselves:

## Secrets Management

### Secret Storage Locations
- **Production**: AWS Secrets Manager (`prod/*` namespace)
- **Staging**: AWS Secrets Manager (`staging/*` namespace)
- **Development**: Local `.env` file (gitignored, copy from `.env.example`)
- **CI/CD**: GitHub Actions Secrets

### Accessing Secrets in Code
# Python example
import boto3

secrets_client = boto3.client('secretsmanager', region_name='us-east-1')
secret = secrets_client.get_secret_value(SecretId='prod/api-key')

# NEVER: api_key = "sk_live_abc123..."

### Required Environment Variables (Structure Only)
- `DATABASE_URL`: PostgreSQL connection (format: postgresql://user:pass@host:port/db)
- `API_KEY`: External API auth (retrieve from secrets manager)
- `JWT_SECRET`: Token signing (minimum 32 characters, from secrets manager)

### Security Rules
- NEVER commit `.env` files
- NEVER hardcode credentials
- NEVER log secret values
- NEVER pass secrets as command-line arguments
- ALWAYS use secrets manager for production
- ALWAYS run secret scanning before commits: `uv run bandit -r src/`

Environment variables are not secure

While commonly used, environment variables have significant limitations:

  • Accessible to all spawned child processes
  • Readable via /proc or ps commands by any process with same user
  • Often inadvertently logged in error messages or crash reports
  • Inherited by every subprocess automatically
  • No built-in auditing

The CNCF Cloud Native Security Whitepaper states: "Secrets should be injected at runtime within workloads through non-persistent mechanisms that are immune to leaks via logs, audit, or system dumps (i.e., in-memory shared volumes instead of environment variables)."

Better alternatives: AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Kubernetes secrets as tmpfs volumes, or encrypted secrets files with proper permissions.

Security scanning tools

Document required security tooling:

## Security Requirements

### Pre-Commit Checks
- Secret scanning: `uv run detect-secrets scan --all-files`
- Dependency vulnerabilities: `uv run pip-audit`
- Code security: `uv run bandit -r src/`
- Terraform security: `tfsec infrastructure/`

### CI/CD Security Gates
- GitHub push protection (blocks commits with secrets)
- Snyk scanning (dependency vulnerabilities)
- Semgrep (code patterns and security rules)
- All scans must pass before merge allowed

### AWS Security
- Use IAM roles for Lambda (principle of least privilege)
- Enable CloudTrail for audit logging
- Encrypt DynamoDB tables at rest
- Use VPC for sensitive Lambda functions
- Enable S3 bucket encryption and versioning
- Rotate IAM access keys quarterly

CI/CD and deployment documentation

Effective AGENTS.md files make CI/CD transparent to AI assistants.

Document the complete pipeline

## CI/CD Pipeline

### GitHub Actions Workflows
- **CI**: `.github/workflows/ci.yml` (runs on all PRs)
  - Stages: Lint → Type Check → Unit Tests → Security Scan → Build
  - Must pass before merge allowed
- **Deploy**: `.github/workflows/deploy.yml` (runs on main branch)
  - Stages: Integration Tests → Terraform Plan → Manual Approval → Terraform Apply
  - Separate jobs for staging and production

### Local CI Simulation
# Run all CI checks locally before pushing
uv run ruff check src/
uv run mypy src/
uv run pytest tests/unit/
uv run bandit -r src/
terraform fmt -check -recursive

### Deployment Environments
- **Development**: Auto-deploy on push to `develop` branch
- **Staging**: Auto-deploy on merge to `main` branch
- **Production**: Manual approval required after staging validation

Testing instructions and prerequisites

Comprehensive testing documentation prevents common issues:

## Testing Instructions

### Prerequisites
- Python 3.12+
- Docker (for integration tests using LocalStack)
- AWS CLI configured (for integration tests against real AWS)
- PostgreSQL 15+ running on port 5432 (for API integration tests)

### Test Database Setup
docker-compose up -d postgres
uv run alembic upgrade head
uv run python scripts/seed_test_data.py

### Running Tests

#### File-Scoped (Preferred - Fast Feedback)
uv run pytest tests/unit/test_handlers.py        # Single file
uv run pytest tests/unit/test_handlers.py::test_create_user  # Single test
uv run pytest -k "test_user"                     # Pattern match

#### Full Test Suite
uv run pytest tests/unit/                        # All unit tests (fast)
uv run pytest tests/integration/                 # Integration tests (slower)
uv run pytest                                    # Everything

#### With Coverage
uv run pytest --cov=src --cov-report=html --cov-report=term
open htmlcov/index.html

### Test Requirements
- All new features require unit tests
- API endpoints require integration tests
- Coverage must be ≥80% for new code
- Use moto for mocking AWS services in unit tests
- Use LocalStack or real AWS for integration tests

Deployment gates and requirements

Define what must be true before deployment:

## Deployment Requirements

### Merge Requirements
- All CI checks pass (lint, type check, tests, security scan)
- Code coverage ≥80%
- No high or critical severity vulnerabilities
- At least one approved code review
- Branch protection rules enforced

### Production Deployment Gates
1. Successful staging deployment and smoke tests
2. Manual approval from team lead
3. Deployment window (weekdays 10am-4pm EST, no Fridays)
4. Rollback plan documented in deployment PR

### Post-Deployment Validation
- Health check endpoint returns 200: `curl https://api.example.com/health`
- CloudWatch logs show no errors: `aws logs tail /aws/lambda/api --follow`
- Key metrics within normal ranges (check Datadog dashboard)
- Smoke tests pass: `pytest tests/smoke/`

Common mistakes and anti-patterns

Learning from real-world failures prevents repeated mistakes.

Critical mistake 1: Including sensitive information

❌ Wrong:

DATABASE_URL=postgresql://admin:MyP@ssw0rd@prod-db.company.com/maindb
API_KEY=sk_live_abc123xyz789
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE

✅ Correct:

## Configuration
- `DATABASE_URL`: From AWS Secrets Manager (`prod/database`)
- `API_KEY`: From environment variables (set in Lambda config)
- AWS credentials: Use IAM roles (never access keys in Lambda)

Critical mistake 2: Overly verbose documentation

Massive 1000+ line files waste tokens and bury important information. Link to detailed documentation instead:

❌ Wrong: Pasting entire API specifications, comprehensive architecture diagrams, complete tutorials.

✅ Correct:

## Architecture
Serverless API: FastAPI → Lambda → API Gateway → DynamoDB

For detailed architecture: [docs/architecture.md](docs/architecture.md)
For API reference: [docs/api-reference.md](docs/api-reference.md)

Critical mistake 3: Vague or ambiguous instructions

❌ Wrong:

- Run the tests
- Build it properly
- Deploy carefully

✅ Correct:

## Testing
- Unit tests: `uv run pytest tests/unit/` (fast, mocked AWS)
- Integration: `uv run pytest tests/integration/` (requires Docker)
- All tests: `uv run pytest` (runs both suites)
- CI must pass before merge

## Deployment
- Staging: Auto-deploys on merge to main
- Production: Run `terraform apply` after manual approval in GitHub Actions

Critical mistake 4: Forcing full builds

AI agents running complete test suites for every small change wastes time:

❌ Wrong:

## Testing
Always run: `npm run build && npm test && npm run e2e`

✅ Correct:

## Testing

### File-Scoped (Preferred - Fast)
uv run pytest tests/test_handlers.py
uv run ruff check src/handlers.py
uv run mypy src/handlers.py

### Full Suite (Only When Explicitly Requested)
uv run pytest --cov=src
uv run ruff check .
terraform fmt -recursive

Critical mistake 5: Unclear permission boundaries

❌ Wrong: No guidance on what AI can do autonomously versus what requires approval.

✅ Correct:

## Permissions

### Allowed Without Prompting
- Read any source file
- Run linters, formatters, type checkers on single files
- Run unit tests on specific test files

### Require Approval First
- Installing packages (`uv add`, `npm install`)
- Git operations (`git push`, `git commit`)
- Deleting files or directories
- Running full build or E2E tests
- Terraform apply or destroy
- Modifying CI/CD configuration

Critical mistake 6: No good/bad examples

AI agents may copy legacy or anti-pattern code without explicit guidance:

✅ Correct:

## Code Examples

### ✅ Good Patterns
- PynamoDB model: `src/models/user.py` (proper Meta class, batch operations)
- FastAPI endpoint: `src/handlers/projects.py` (async, thin handlers)
- Terraform module: `infrastructure/modules/lambda/` (reusable, documented)

### ❌ Avoid These
- `src/legacy/sync_handler.py` - Synchronous patterns (deprecated)
- `src/utils/raw_boto3.py` - Direct boto3 without error handling
- Hardcoded credentials, regions, or table names anywhere

Critical mistake 7: Letting it become outdated

AGENTS.md must evolve with the codebase:

  • Review in pull requests when processes change
  • Treat as code, not static documentation
  • Audit quarterly for accuracy
  • Update immediately when commands or structure change

Critical mistake 8: Not handling monorepos properly

Single AGENTS.md for large monorepos creates confusion:

✅ Correct approach:

monorepo/
├── AGENTS.md                    # Org-wide standards, general patterns
├── python-api/
│   └── AGENTS.md               # Python/FastAPI/PynamoDB specific
├── infrastructure/
│   └── AGENTS.md               # Terraform/AWS specific
└── typescript-frontend/
    └── AGENTS.md               # React/TypeScript specific

Nearest file takes precedence, allowing context-specific guidance.

Template structures and recommended sections

Complete template for Python + AWS + Terraform

# Project Name

Brief description of serverless application, purpose, and architecture.

# Technology Stack
- **Runtime**: Python 3.12 (AWS Lambda)
- **Package Manager**: UV
- **API Framework**: FastAPI with Mangum
- **AWS Services**: Lambda, DynamoDB (PynamoDB), S3, API Gateway
- **IaC**: Terraform 1.5+ (S3 backend, DynamoDB locking)
- **Testing**: pytest with moto
- **Validation**: Pydantic v2

# Architecture
- `src/handlers/` - Lambda handlers (thin, async)
- `src/models/` - PynamoDB (DynamoDB) and Pydantic (API) models
- `src/services/` - Business logic
- `infrastructure/` - Terraform (modules pattern, workspaces)
- `tests/` - Unit (moto) and integration tests

# Environment Setup

## Prerequisites
- Python 3.12+
- UV: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- AWS CLI configured: `aws configure`
- Terraform 1.5+: `brew install terraform`
- Docker (for integration tests)

## Initial Setup
uv venv && source .venv/bin/activate
uv sync
cp .env.example .env  # Populate from team vault

## AWS Configuration
aws configure  # Set credentials and region
export AWS_DEFAULT_REGION=us-east-1

# Commands

## File-Scoped (Preferred - Fast)
uv run pytest tests/unit/test_handlers.py
uv run ruff check src/handlers.py
uv run mypy src/handlers.py

## Full Suite (Only When Requested)
uv run pytest --cov=src
uv run ruff check .
terraform fmt -recursive

## Terraform
cd infrastructure
terraform init
terraform workspace select dev
terraform plan -var-file=environments/dev.tfvars
terraform apply -var-file=environments/dev.tfvars

# Code Patterns

## PynamoDB Models
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute

class UserModel(Model):
    class Meta:
        table_name = 'users'
        region = 'us-east-1'  # Must match table region
    
    user_id = UnicodeAttribute(hash_key=True)
    email = UnicodeAttribute()

- Always define table_name and region in Meta
- Use batch_write() for multiple items
- Mark optional fields: null=True

## Pydantic Models
from pydantic import BaseModel, Field

class UserRequest(BaseModel):
    email: str = Field(..., pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
    age: int | None = Field(None, gt=0, lt=150)

- Use for API validation
- Field() for constraints
- Type unions (|) for optional

## FastAPI + Lambda
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: str):
    return UserService().get_user(user_id)

handler = Mangum(app)

- Keep handlers thin
- Async for all I/O
- Initialize AWS clients outside handler

# Testing

## Structure
tests/
├── unit/              # Mocked AWS (moto)
├── integration/       # Real AWS or LocalStack
└── conftest.py        # Shared fixtures

## Running Tests
uv run pytest tests/unit/              # Fast, mocked
uv run pytest tests/integration/       # Requires Docker/AWS
uv run pytest --cov=src --cov-report=html

## Requirements
- 80%+ coverage for new code
- Unit tests for all business logic
- Integration tests for API endpoints
- Use `@mock_dynamodb` for DynamoDB mocking

# Terraform

## Commands
terraform init
terraform validate
terraform fmt -recursive  # Before committing
terraform plan -var-file=environments/dev.tfvars -out=tfplan
terraform apply tfplan

## Best Practices
- Store state in S3 with DynamoDB locking
- Use workspaces for environments
- Tag all resources: environment, project, managed_by
- Always run fmt before committing
- Document all variables

# Security

## Secrets Management
- Production: AWS Secrets Manager (`prod/*`)
- Development: `.env` (gitignored)
- CI/CD: GitHub Actions Secrets
- Never commit credentials or `.env` files

## Security Scanning
uv run bandit -r src/              # Code security
uv run pip-audit                   # Dependency vulnerabilities
tfsec infrastructure/              # Terraform security

## AWS Security
- Use IAM roles (principle of least privilege)
- Enable CloudTrail for audit logging
- Encrypt DynamoDB at rest
- VPC for sensitive functions

# Deployment

## Local Development
uv run uvicorn src.main:app --reload
sam local invoke MyFunction -e events/test.json

## Infrastructure
cd infrastructure
terraform workspace select prod
terraform plan -var-file=environments/prod.tfvars
terraform apply -var-file=environments/prod.tfvars

## CI/CD
- Workflow: `.github/workflows/deploy.yml`
- Staging: Auto-deploy on merge to main
- Production: Manual approval required
- Gates: Tests pass, security scan clean, code review approved

# Code Style
- Python: PEP 8, type hints (mypy strict)
- Formatting: Ruff (line length 88)
- Imports: Group stdlib, third-party, local
- Docstrings: Google style
- Never hardcode credentials, regions, or table names

# Good Examples
- PynamoDB: `src/models/user.py`
- FastAPI: `src/handlers/projects.py`
- Terraform: `infrastructure/modules/lambda/`

# Avoid
- `src/legacy/sync_handler.py` - Synchronous patterns
- Hardcoded anything
- Direct boto3 without error handling

# Permissions

## Allowed Without Prompting
- Read files
- Lint/test single files
- Type checking

## Require Approval
- `uv add` (package installations)
- `git push` (git operations)
- File deletion
- `terraform apply/destroy`
- Full test suites

# Troubleshooting

## Lambda Timeout
- Increase timeout in Terraform (max 15 minutes)
- Enable provisioned concurrency for critical endpoints

## PynamoDB Can't Find Table
- Check region in Meta class matches table region
- Verify table exists: `aws dynamodb list-tables`

## Terraform State Lock
- Force unlock: `terraform force-unlock <LOCK_ID>`

# Additional Resources
- UV: https://docs.astral.sh/uv/
- PynamoDB: https://pynamodb.readthedocs.io/
- FastAPI: https://fastapi.tiangolo.com/
- Terraform AWS: https://registry.terraform.io/providers/hashicorp/aws/

Minimal template for simple projects

For smaller projects, start minimal and expand as needed:

# Project Name

Brief description.

# Setup
uv venv && source .venv/bin/activate
uv sync

# Commands
uv run pytest tests/
uv run ruff check src/

# Code Style
- Python: PEP 8, type hints
- Formatting: Ruff
- Tests required for new features

# Testing
uv run pytest tests/unit/test_file.py  # Single file
uv run pytest  # All tests

# Security
- Never commit .env files
- Store secrets in environment variables
- Run `uv run bandit -r src/` before committing

Final recommendations and best practices

Start small and iterate

Begin with 20-30 lines covering the most critical information: setup commands, testing, and code style. Add sections based on what AI assistants repeatedly get wrong. The best AGENTS.md files evolve from real usage.

Use concrete, executable commands

Every command should be copy-pasteable and work exactly as written. Avoid "run the usual tests" in favor of "uv run pytest tests/unit/test_handlers.py". Provide file paths, flags, and expected output where helpful.

Think like onboarding

What would you tell a new teammate who needs to make their first code change? Setup, make a change, test it, submit it. That's your AGENTS.md outline.

Maintain as living documentation

Update AGENTS.md in pull requests when processes change. Review during code reviews. Keep synchronized with README.md. Audit quarterly for accuracy. Outdated instructions are worse than no instructions.

Balance automation and safety

Define clear permission boundaries. File-scoped operations (linting single files, running specific tests) can happen automatically. Risky operations (package installations, git pushes, infrastructure changes) require approval. This balance maintains productivity while preventing accidents.

Provide escape hatches

Include guidance like "When stuck, ask a clarifying question" or "If tests fail repeatedly, ask for human review." AI assistants should know when to ask for help.

Use hierarchical organization for scale

Start with a single AGENTS.md. When it exceeds 150-200 lines, split into subdirectories with nested files. Root file covers organization-wide standards, subdirectory files provide context-specific details. The nearest file takes precedence.

Link instead of duplicating

Reference existing documentation rather than copying it. "For API details, see [docs/api.md]" saves tokens and prevents drift between AGENTS.md and actual documentation.

Test your AGENTS.md

Ask an AI assistant to perform common tasks using only your AGENTS.md. Can it set up the environment? Run tests? Deploy code? Iterate based on where it gets stuck or makes mistakes.

Consider tool compatibility

While AGENTS.md works across most modern AI coding tools, some advanced features vary. File imports (@./file.md) work in some tools but not others. Glob patterns in frontmatter work in GitHub Copilot and Cursor but not universally. Stick to standard Markdown for maximum compatibility.

Security as a first principle

Treat AGENTS.md as potentially public. Never include actual secrets. Assume it could be used to train models. Reference secret storage systems, never secrets themselves. Run secret scanning before committing. Enable GitHub push protection.

The adoption of AGENTS.md represents a fundamental shift in how we document software projects. As AI assistants become standard development tools, having machine-readable, actionable guidance becomes as important as human-readable documentation. A well-crafted AGENTS.md file is an investment that pays dividends every time an AI assistant generates code that works correctly the first time, follows your conventions, and passes all checks without manual intervention.

For Python + AWS + Terraform projects specifically, the combination of UV package management documentation, PynamoDB patterns, Pydantic validation examples, FastAPI + Lambda integration guidance, comprehensive Terraform commands, and moto-based testing strategies creates a complete blueprint for AI-assisted serverless development. The result is faster development cycles, fewer code review iterations, and more consistent codebases—exactly what AGENTS.md was designed to deliver.

@Ar9av

Ar9av commented May 22, 2026

Copy link
Copy Markdown

great writeup. one thing i've noticed though is that AGENTS.md guidance alone gets like 25-40% compliance from agents, but the same rules enforced as runtime hooks hit closer to 95%. we built immunity-agent to close that gap, it hooks directly into Claude Code, Cursor, Codex etc and blocks dangerous calls before they land rather than hoping the agent reads its instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment