Writing Effective Specs
Questi contenuti non sono ancora disponibili nella tua lingua.
Specs are the core of Contextia’s knowledge layer. A spec is a behavioral contract that describes what a part of your system does, not how it is implemented. Good specs make AI agents productive. Bad specs make them guess.
This guide covers the principles and patterns for writing specs that work.
Anatomy of a spec
Section titled “Anatomy of a spec”Every spec is a Markdown file with YAML frontmatter in .contextia/system/specs/:
---id: SPEC-012title: Rate limitingdescription: Controls request throughput per client to protect backend services.status: approveddecisions: - DEC-008norms: - NORM-API-002paths: - src/middleware/rate_limiter.py - src/config/rate_limits.yaml---
## Objective
Limit API requests per client to prevent abuse and ensure fair resource allocation.
## Behaviors
WHEN a client sends requests below the configured thresholdTHEN all requests are processed normally with no rate-limit headers.
WHEN a client exceeds the per-minute request limitTHEN the server responds with HTTP 429 and a Retry-After header.
WHEN a client is rate-limited and waits for the retry windowTHEN subsequent requests within the threshold are processed normally.
## Notes
Rate limits are configured per-route in `rate_limits.yaml`. The default is 100 requests per minute per API key.Principle 1: One responsibility per spec
Section titled “Principle 1: One responsibility per spec”A spec should describe one cohesive behavior. If you find yourself writing “and also” between unrelated behaviors, split the spec.
Too broad:
SPEC-005: User management- Handles registration, login, password reset, profile updates, role assignment, and audit logging.Better:
SPEC-005: User registrationSPEC-006: User authenticationSPEC-007: Password reset flowSPEC-008: Role-based access controlPrinciple 2: Write testable behaviors
Section titled “Principle 2: Write testable behaviors”Every behavior in a spec should be verifiable. Use the WHEN/THEN format consistently. This format maps directly to test cases, which means your specs double as a test plan.
Vague (not testable):
The system should handle errors gracefully.Testable:
WHEN an upstream service returns a 5xx errorTHEN the system retries up to 3 times with exponential backoff.
WHEN all retries are exhaustedTHEN the system returns HTTP 502 to the client with an error correlation ID.Each WHEN/THEN pair answers three questions:
- What is the starting condition?
- What action triggers the behavior?
- What is the observable outcome?
If you cannot answer all three, the behavior is underspecified.
Principle 3: Use precise language
Section titled “Principle 3: Use precise language”Avoid words that require interpretation. Specs are read by AI agents that take language literally.
| Avoid | Use instead |
|---|---|
| ”should" | "must” or describe the behavior with WHEN/THEN |
| ”quickly" | "within 200ms” or “before the next frame" |
| "appropriate” | state the specific condition |
| ”etc.” | list all cases explicitly |
| ”handle errors” | describe what happens for each error type |
| ”securely” | reference the specific norm (e.g., NORM-SEC-003) |
Principle 4: Link decisions and norms
Section titled “Principle 4: Link decisions and norms”Specs do not exist in isolation. They depend on architectural decisions and must comply with project norms.
decisions: - DEC-008 # Token bucket algorithm chosen over sliding windownorms: - NORM-API-002 # All middleware must be statelessWhen an AI agent loads this spec via contextia context, it automatically gets the linked decision and norm as well. This means the agent understands not just what to build, but why certain choices were made and what constraints apply.
Principle 5: Scope paths accurately
Section titled “Principle 5: Scope paths accurately”The paths field in frontmatter tells Contextia which source files implement this spec:
paths: - src/middleware/rate_limiter.py - src/config/rate_limits.yamlBe specific. Broad paths like src/ dilute the signal. The paths field serves two purposes:
- Top-down navigation: an agent reading the spec knows where to look in the code.
- Integrity checking:
contextia checkverifies that annotated files and spec paths are consistent.
Use glob patterns when a spec covers multiple files with a common pattern:
paths: - src/middleware/rate_*.py - tests/middleware/test_rate_*.pyPrinciple 6: Keep the description concise
Section titled “Principle 6: Keep the description concise”The description field in frontmatter is a one-line summary. It appears in indexes and search results. Write it for scanning, not reading.
Too long:
description: This spec describes the rate limiting middleware that sits in front of all API endpoints and uses a token bucket algorithm to throttle requests on a per-client basis.Right length:
description: Controls request throughput per client to protect backend services.Save the detail for the body of the spec.
Granularity guidelines
Section titled “Granularity guidelines”How big should a spec be? Here are practical guidelines:
| Scope | Example | Typical spec count |
|---|---|---|
| Single endpoint | POST /auth/login | 1 spec |
| Feature area | Authentication (login, logout, refresh) | 2-4 specs |
| Subsystem | Payment processing | 5-10 specs |
| Full application | E-commerce platform | 20-50 specs |
Status lifecycle
Section titled “Status lifecycle”Specs have a status field that tracks their maturity:
draft --> approved --> implemented --> deprecated- draft: Under discussion, may change significantly.
- approved: Reviewed and accepted as a contract. Implementation can begin.
- implemented: Code exists that satisfies all behaviors. Annotations link back.
- deprecated: No longer active. Kept for historical reference.
AI agents check status before acting. An agent loading context for a task will flag if it is implementing against a draft spec that has not been approved.
Common mistakes
Section titled “Common mistakes”Describing implementation instead of behavior
Section titled “Describing implementation instead of behavior”## BadThe function calls `redis.incr()` to track request countsand compares against the limit stored in `config.rate_limits`.
## GoodWHEN a client exceeds the per-minute request limitTHEN the server responds with HTTP 429 and a Retry-After header.The spec says what happens, not how. The implementation can change from Redis to an in-memory counter without the spec changing.
Missing edge cases
Section titled “Missing edge cases”Every WHEN/THEN should have a corresponding “failure” or “boundary” case. If you describe what happens on success, also describe what happens on failure, timeout, and invalid input.
Orphan specs
Section titled “Orphan specs”A spec with no paths and no annotations pointing to it is an orphan. It describes behavior that either does not exist in code or is not linked. Run contextia check regularly to catch orphans.
Template for new specs
Section titled “Template for new specs”Use this as a starting point:
---id: SPEC-XXXtitle: [Short descriptive title]description: [One-line summary for indexes]status: draftdecisions: []norms: []paths: []---
## Objective
[One paragraph: what this behavior achieves and why it exists.]
## Behaviors
WHEN [condition]THEN [observable outcome].
WHEN [alternate condition]THEN [different outcome].
## Notes
[Optional: configuration, dependencies, known limitations.]Next steps
Section titled “Next steps”- Follow the Your First Project guide to practice writing specs in context.
- See the Frontmatter Schemas reference for the complete
SpecMetaschema. - Read about Agent Workflow to understand how agents consume your specs.