AGENTS-example.md
This is an EXAMPLE file. Use at your own risk. It is provided as a reference template for development standards and coding conventions. Adapt it to your project's needs before adopting. No guarantees are made about its completeness or suitability for any specific use case.
Language-agnostic. Framework-agnostic.
| Principle | Rule | Common Mistake |
|---|---|---|
| SRP — Single Responsibility | Each class/function/file has ONE reason to change. If you need "and" or "or" to describe it, split it. | Interpreting SRP as "one function per class." SRP means one axis of change. |
| OCP — Open/Closed | Add new behavior by writing new code, not modifying existing code. Use polymorphism or strategy patterns where change is expected. | Over-engineering with premature abstractions. Apply OCP where you have evidence of changing requirements. |
| LSP — Liskov Substitution | Subclasses must honor the contract of their parent. Prefer composition over inheritance when "is-a" is not strict. | Overriding a method to throw NotImplementedError or do nothing. |
| ISP — Interface Segregation | Define small, role-specific interfaces. Clients depend only on methods they use. | Creating one "service" interface with 15+ methods. |
| DIP — Dependency Inversion | Depend on abstractions at module boundaries, not concrete implementations. Domain logic must never import from infrastructure. | Confusing DIP with "just use dependency injection." DIP is about inverting the direction of source-code dependency. |
get, create, update, delete, validate, format, parse.is, has, can, should.id, url, api).data, result, obj, thing, temp, misc, utils.any, object, dynamic, Object.any just to make something compile.const, readonly, final, frozen, tuple, frozenset.| Layer | CAN | CANNOT |
|---|---|---|
| Handler/Controller | Receive input, delegate to service, return output | Contain business logic, call DB directly |
| Service/Orchestrator | Coordinate operations, apply business rules | Know about HTTP/transport, execute SQL directly |
| Repository/Data Access | Execute queries, map data | Make business decisions, call external APIs |
| Helper | Transform data, validate, format | Have side effects, do I/O, maintain state |
| External Client | Communicate with external services | Contain business logic, access database |
| Metric | Guideline |
|---|---|
| Lines of code (excluding imports, types, docs) | ~500 lines (up to ~530 OK; 600+ is a red flag) |
| Functions with DIFFERENT responsibilities | 5 functions max |
| Functions with SAME responsibility (same prefix) | 10 functions max |
| Main classes per file | 1 class |
| Small related classes (exceptions, DTOs, enums) | 5 classes (if all same type) |
Every file MUST have one reason to exist and one reason to change.
The Test: Can you describe this file's purpose in ONE sentence WITHOUT using "and" or "or"?
Functions MUST be grouped by responsibility category. Functions with DIFFERENT prefixes MUST NOT coexist in the same file.
| Responsibility | Function Prefixes | Separate File |
|---|---|---|
| Types/Models | Type definitions, interfaces, classes without logic | {feature}_types |
| Constants | MAX_*, DEFAULT_*, enums | {feature}_constants |
| Validation | validate*, check*, is_valid* | validation |
| Formatting | format*, build*, serialize*, to_* | formatting |
| Parsing | parse*, extract*, from_* | parsing |
| External calls | fetch*, send*, call*, request* | {service}_client |
| Data access | save*, load*, find*, delete*, query* | {feature}_repository |
| Orchestration | Main entry points, coordination | {feature}_service |
| Handlers | Endpoints, controllers, views | {feature}_handler |
_func) stay in the file that uses them.utils, helpers, misc, common, shared as standalone files.feature/
├── {feature}_service # Orchestration
├── {feature}_types # Type definitions
├── {feature}_constants # Constants and enums
├── helpers/
│ ├── validation # ONLY validation functions
│ ├── formatting # ONLY formatting functions
│ └── parsing # ONLY parsing functions
├── services/
│ └── {external}_client # ONLY external API communication
├── repositories/
│ └── {feature}_repository # ONLY data persistence
└── handlers/
└── {feature}_handler # ONLY request handling
Exception, Error, object). Use domain-relevant error types.# BAD
try:
result = do_something()
except:
pass
# GOOD
try:
result = do_something()
except ValidationError as e:
logger.warning("Validation failed", extra={"error": str(e), "field": e.field})
raise DomainError(f"Invalid input: {e.field}") from e
| Level | When to Use |
|---|---|
| ERROR | Something is broken and needs human attention |
| WARN | Degraded but self-recoverable |
| INFO | Significant business events |
| DEBUG | Diagnostic detail, off in production |
auth_id, user_id, internal_id.print() / console.log() with user data — these go to production logs.Test code is production code. It receives the same care, review, and quality standards.
should_[expected]_when_[condition].Happy path tests are the foundation — they validate the code works under normal conditions. Always start with these.
But happy path tests ALONE are not enough. You MUST also write adversarial tests that actively try to break the code and find defects:
None, "", [], {}, 0, -1Write tests based on REQUIREMENTS/SPEC, not on what the source code currently does. This is how you catch bugs where the code diverges from expected behavior.
When a test fails: first ask if the CODE is wrong, not the test. Do NOT silently change a failing assertion to match the current code without understanding WHY.
| Metric | Guideline |
|---|---|
| Lines per file | ~1000 lines guideline — above this, consider splitting, but not required if covering a single module |
| Tests per file | No hard limit — split only when covering unrelated behaviors |
| Setup (Arrange) | ~20 lines max per test (extract to helpers/factories if exceeded) |
Split test files based on LOGICAL SEPARATION, not arbitrary line counts. One file per module/service is perfectly fine, even at 800+ lines.
if/else, all catch blocks), not just line coverage.# Python
pytest tests/your_tests.py --cov=src/module_under_test --cov-report=term-missing --cov-branch -v
# JavaScript/TypeScript (Jest)
npx jest tests/your_tests.test.ts --coverage --collectCoverageFrom="src/module/**/*.{ts,tsx}"
# JavaScript/TypeScript (Vitest)
npx vitest run tests/your_tests.test.ts --coverage
| Pattern | Problem |
|---|---|
| The Liar | Test passes but doesn't verify the behavior it claims to test |
| The Mirror | Test reads the source code and asserts exactly what the code does — finds zero bugs |
| The Giant | 50+ lines of setup, multiple acts, dozens of assertions — should be 5+ separate tests |
| The Mockery | So many mocks that the test only tests the mock setup |
| The Inspector | Coupled to implementation details, breaks on any refactor |
| The Chain Gang | Tests depend on execution order or share mutable state |
| The Flaky | Sometimes passes, sometimes fails with no code changes |
| Level | Audience | Content |
|---|---|---|
| Context (L1) | Product / Stakeholders | System in its environment |
| Container (L2) | Both | Applications, databases, queues |
| Component (L3) | Engineering | Internal service details |
BEFORE delivering ANY code, verify ALL items.
any, object, dynamic)This guide applies to every line of code in the Langflow project. When in doubt, choose simplicity. When trade-offs arise, follow the priority order in Section 1. Build for correctness first. Optimize later. Test always.