Saltearse al contenido

Writing Effective Specs

Esta página aún no está disponible en tu idioma.

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.

Every spec is a Markdown file with YAML frontmatter in .contextia/system/specs/:

---
id: SPEC-012
title: Rate limiting
description: Controls request throughput per client to protect backend services.
status: approved
decisions:
- DEC-008
norms:
- NORM-API-002
paths:
- 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 threshold
THEN all requests are processed normally with no rate-limit headers.
WHEN a client exceeds the per-minute request limit
THEN the server responds with HTTP 429 and a Retry-After header.
WHEN a client is rate-limited and waits for the retry window
THEN 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.

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 registration
SPEC-006: User authentication
SPEC-007: Password reset flow
SPEC-008: Role-based access control

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 error
THEN the system retries up to 3 times with exponential backoff.
WHEN all retries are exhausted
THEN the system returns HTTP 502 to the client with an error correlation ID.

Each WHEN/THEN pair answers three questions:

  1. What is the starting condition?
  2. What action triggers the behavior?
  3. What is the observable outcome?

If you cannot answer all three, the behavior is underspecified.

Avoid words that require interpretation. Specs are read by AI agents that take language literally.

AvoidUse 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)

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 window
norms:
- NORM-API-002 # All middleware must be stateless

When 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.

The paths field in frontmatter tells Contextia which source files implement this spec:

paths:
- src/middleware/rate_limiter.py
- src/config/rate_limits.yaml

Be specific. Broad paths like src/ dilute the signal. The paths field serves two purposes:

  1. Top-down navigation: an agent reading the spec knows where to look in the code.
  2. Integrity checking: contextia check verifies 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_*.py

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.

How big should a spec be? Here are practical guidelines:

ScopeExampleTypical spec count
Single endpointPOST /auth/login1 spec
Feature areaAuthentication (login, logout, refresh)2-4 specs
SubsystemPayment processing5-10 specs
Full applicationE-commerce platform20-50 specs

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.

Describing implementation instead of behavior

Section titled “Describing implementation instead of behavior”
## Bad
The function calls `redis.incr()` to track request counts
and compares against the limit stored in `config.rate_limits`.
## Good
WHEN a client exceeds the per-minute request limit
THEN 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.

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.

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.

Use this as a starting point:

---
id: SPEC-XXX
title: [Short descriptive title]
description: [One-line summary for indexes]
status: draft
decisions: []
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.]