Methodology v1.0 — open standard
Requirements · Domain · Tests

Requirements
that live
alongside code.

Atomic Spec — a methodology for collaboration between PM, Developer Agent, and Tester Agent. One source of truth. Full decision history. Every requirement's status — visible without a meeting.

1
source of truth
for the entire team
3
file states
draft → active → deprecated
0
additional
tools needed
every decision's
history in git
The problem we solve

Requirements live everywhere.
Which means nowhere.

Most teams today
Requirements in Confluence, tasks in Jira, discussions in Slack — constant context fragmentation
A new developer agent doesn't know why a particular decision was made
The tester agent doesn't know what exactly changed in the sprint — tests everything or nothing
PM changes a requirement — the developer agent finds out at code review, the tester agent — never
The manager can't see real status: what's implemented, what's unverified, what's outdated
With Atomic Spec
Everything in git: requirements, decisions, tests, history — one repository, one timeline
git log spec.md — full history: who, when, why, at whose initiative
git diff release/v1..release/v2 — exact list of what changed in scenarios
Amendment file captures mid-sprint conflicts — no one gets caught off guard
File tree is a live dashboard: draft / active / deprecated visible without opening a file
What it looks like in practice

The file tree is
the system state

📁 specs/auth/
├── _index.md ← domain dashboard
├── domain.spec.md v3.1 · Identity aggregate
├── 📁 _draft/ ← open questions, not ready for work
│ └── AUTH-MERGE-010_google-github-linking.spec.md
├── 📁 _deprecated/ ← history, never deleted
│ └── AUTH-REG-001_email-password-registration.spec.md
└── 📁 registration/
├── AUTH-REG-010_phone-otp-registration.spec.md ✓ active
├── AUTH-REG-020_google-oauth-registration.spec.md ✓ active
└── AUTH-REG-020a~param_otp-timeout.spec.md amendment
How it works

Three rules. The entire methodology.

01
Atom = one file

Each requirement is a separate .spec.md. Contains intent, domain rules, Gherkin scenarios, and platform contract. Adding a requirement = creating a file.

02
Position = status

File in the folder root — verified and current. In _draft/ — has open questions. In _deprecated/ — history. Status is visible without opening the file.

03
Git = decision journal

Who changed it, when, why, at whose initiative — in the commit message. Diff between release tags — exact list of changes for each role.

Get started

Choose a role —
get the full guide

Detailed instructions, file examples, git commands, and workflows — tailored for each role.

📋 PM / Analyst Agent ⚙️ Developer Agent 🧪 Tester Agent 🎯 Manager
Methodology for Living Requirements
📋 PM · Analyst Agent

You always know
the current state
of requirements

Not "I think we agreed on everything," but a precise answer: what's verified, what's in progress, what needs to be resolved right now.

Full guide → ← Back
Pain points we solve
😰
"We discussed this in Slack"
A decision was made, but a month later nobody remembers why. The developer agent does it differently because it "makes more sense."
🔄
Mid-sprint requirement change
You refined a requirement — part of the team found out immediately, part — at the demo. Amendment file captures the conflict explicitly.
Unresolved questions
Open Questions live in _draft/ and are visible to everyone. They won't go into implementation until resolved. They won't get lost in chat.
Your workflow

How the analyst agent works with atoms

Step 1
Create atom in _draft/
Step 2
Close Open Questions
Step 3
PR → verification
Step 4
Merge → folder root
Step 5
Deprecated on replacement
specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md
--- id: AUTH-REG-020 type: use-case title: "Registration via Google OAuth" parent: AUTH # Open questions — reason for being in _draft/ open-questions: - id: OQ-1 question: "Is merge required when emails match?" status: open ← file stays in _draft/ while status: open implementation: status: none verification: status: none blocks-release: true --- ## § Intent A guest authenticates via Google. If no account exists — one is created. If one exists — login is performed. One flow, two outcomes. ## § Domain Rules DR-O-1 Email from Google is considered verified → role User (not UnverifiedUser) DR-O-2 If email matches a Phone account → see AUTH-MERGE-001 ## § Acceptance Criteria Scenario: New user via Google Given no account with the given googleId When guest completes OAuth flow Then a User with role User is created And UserRegistered is emitted
Key capabilities
  • Decision Log in every atom
    Every decision is recorded with date, participants, and alternatives. Six months later you know why, not just what.
  • Change Types with rules
    ParameterChange, RuleChange, ModelChange — each type knows who must verify and whether it blocks the sprint.
  • Deprecated does not mean deleted
    History is always preserved. You can trace the evolution from email/password → phone/OTP → OAuth.
  • Amendment on sprint conflict
    A change arrived while development is underway? An amendment file is created with an explicit decision: pull out or make it the next PR.
Full analyst agent guide →
⚙️ Developer Agent

You know exactly
what and why
needs to be done

Not "I think we agreed," but an exact delta: what changed in the requirements while you were working, and who decided it.

Full guide → ← Back
What you get
🎯
What to do right now
Open PR = your task. Diff PR vs main = exactly what to implement. No "go ask someone."
📖
Why it's done this way
git log spec.md — full decision history. Decision Log explains why the alternative was not chosen.
Requirement change
Amendment file in _ready/ signals: the requirement changed while you were working. Explicit decision in the file.
Key git commands for the developer agent

Working with branches as the source of truth

terminal
# What I need to implement in this sprint git log --oneline main..HEAD -- specs/ # Did the requirement change while I was working git diff main feat/AUTH-REG-020 -- specs/auth/ # Why was this decision made git log --follow -p specs/auth/registration/AUTH-REG-020.spec.md # What changed compared to the previous release git diff release/Sprint-13..release/Sprint-14 -- specs/ # All unresolved amendments grep -r "amendment-status: pending" specs/ -l
Developer agent rule Before starting implementation: git diff main HEAD -- specs/ — make sure the requirement in your branch is up to date. If the analyst changed something in main while you were working — you'll see the diff.
Tests inside the spec

Platform contract — right in the requirements file

AUTH-REG-020.spec.md → § Platform: Web API
## § Platform: Web API contract: method: POST path: /v1/auth/oauth/callback body: provider: string # "google" | "github" code: string # OAuth authorization code responses: 201: { userId, email, provider, isNewUser: true } 200: { userId, email, provider, isNewUser: false } 409: { code: ACCOUNT_MERGE_REQUIRED } ## § Platform Tests // @spec AUTH-REG-020 | platform: web-api describe('POST /v1/auth/oauth/callback', () => { it('201 → new user created with role User', async () => { const res = await api.post('/v1/auth/oauth/callback', { provider: 'google', code: 'valid-code' }) expect(res.status).toBe(201) expect(res.body.isNewUser).toBe(true) expect(events).toContainEvent('UserRegistered') }) })
Full developer agent guide →
🧪 Tester Agent · QA

You know exactly
what changed
in this release

Not "test everything," but an exact diff of scenarios between tags. What's new, what changed, what blocks the release.

Full guide → ← Back
Three key questions answered
📋
What to test in the release
git diff release/v1..release/v2 -- specs/ — exact list of files and scenario delta. Nothing extra.
🚦
What blocks the release
blocks-release: true + verification.status: none — the list of exactly those files. Everything else is secondary.
🔍
What exactly changed
Diff of the § Acceptance Criteria section between releases shows not the whole file, but only behavior changes.
Workflow example

What changed in Sprint 14

terminal — tester agent works with release tags
# 1. What was added or changed in this sprint git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-only # Output: specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md ← new specs/auth/otp/AUTH-OTP-002_rate-limit-exceeded.spec.md ← modified # 2. What exactly changed in OTP scenarios git diff release/Sprint-13..release/Sprint-14 \ -- specs/auth/otp/AUTH-OTP-002.spec.md # Output: - When a repeat request arrives less than 20 seconds later + When a repeat request arrives less than 60 seconds later # 3. What blocks the release grep -r "blocks-release: true" specs/ -l | \ xargs grep -l "verification.status: none" # Output — test urgently: specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md
Full tester agent guide →
🎯 Project & Product Office Manager

Full picture
without meetings
or status reports

The state of every domain, open decisions, sprint risks — at any moment, straight from the repository.

Full guide → ← Back
Metrics you can see
24
active requirements in the system
3
open decisions blocking progress
2
requirements blocking the release
6
deprecated — change history

All numbers from a single command. Real state, not what's written in a report.

What the manager sees
🗺️
Domain evolution
Domain git tags show the history of architectural decisions. domain/AUTH/v1.0 → v3.1 — three Breaking, one Additive.
⚠️
Sprint risks
HIGH-risk use-cases are in separate PRs. Amendment with sprint-locked — an explicit conflict between a requirement and current development.
📊
Release readiness
List of files with blocks-release: true and verification: none — a precise answer: can we ship?
Full manager guide →

The Analyst Role in Atomic Spec

The analyst is the atom owner. They create requirements, resolve open questions, and verify domain model changes. All analyst work is done in .spec.md files and PRs.

Key Rule If a requirement exists, it must be in an atom. If a requirement changes, an amendment is created or the atom is updated with a changelog entry in the commit message.

Atom Lifecycle

Each atom progresses through positions in the file tree:

_draft/
Creation and Working with Open Questions
File stays in _draft/ as long as any OQ has status: open. Cannot be taken into development.
PR review
Stakeholder Verification
PR moves the file from _draft/ to root. Stakeholders approve the PR — that is the verification.
root
Verified, Active
File in root = ready for sprint. Developer and tester work with it.
_deprecated/
History Forever
File is never deleted. deprecated-by references the replacement. Reason is recorded in the commit.

Atom Format — Full Template

specs/[domain]/[section]/DOMAIN-TYPE-NNN_slug.spec.md
--- id: AUTH-REG-020 type: use-case # use-case | scenario | domain | amendment title: "Google OAuth Registration" parent: AUTH children: - AUTH-REG-021_google-provider-error - AUTH-REG-022_email-conflict # Domain references see-also: [AUTH-MERGE-001, AUTH-OTP-001] emits: [UserRegistered, UserLoggedIn] # Statuses on three axes (analyst manages the first) open-questions: - id: OQ-1 question: "Is merge required when emails match?" status: resolved resolution: "Merge is prohibited without explicit user confirmation" resolved-by: "Maria K. / Product" implementation: status: none # none | in-progress | done verification: status: none # none | in-progress | passed | failed blocks-release: true --- ## § Intent A guest authenticates via Google OAuth. ## § Domain Rules DR-O-1 Email from Google is considered verified → role User (not UnverifiedUser) DR-O-2 providerUserId is unique per provider DR-O-3 If email matches an existing account → AUTH-MERGE-001 ## § Acceptance Criteria Scenario: Successful registration of a new user Given no account exists with the given googleId When guest completes OAuth flow with Google Then a User is created with the User role And an Identity { provider: google } is created And UserRegistered is emitted ## § Decision Log D-001 date: 2024-05-10 decision: "Email from OAuth is considered verified automatically" rationale: "Google guarantees email verification on their side" alternatives: ["Require re-verification"] decided-by: "Maria K. + Ivan P."

Change Types and Rules

TypeWhenWho VerifiesBlocks Sprint
ParameterChangeValue change: timeout, limit, size1 stakeholderno
RuleChangeNew, changed, or removed domain ruleProduct + Techpossibly
FlowChangeNew scenario step, branch changeProductpossibly
ModelChangeNew aggregate, field, eventProduct + Tech + Architectyes
BoundaryChangeAggregate transfer, domain splitCTO + Architect + Productyes, RFC

Amendments When a Requirement Changed During Sprint

If a requirement changed while development is in progress, do not modify the original atom — create an amendment:

AUTH-OTP-002a~param_retry-timeout.spec.md
--- id: AUTH-OTP-002a type: amendment change: ParameterChange title: "OTP retry timeout: 20s → 60s" amends: AUTH-OTP-002_rate-limit-exceeded parameter: name: retry_timeout_seconds was: 20 becomes: 60 conflict: status: sprint-locked # sprint-locked | pulled | merged locked-sprint: Sprint-14 resolution: "Finish Sprint-14 with the old value, amendment → Sprint-15" ---
Three Scenarios 1. Atom is still in _draft/ → simply update the file in place.
2. In _in-progress/, already pulled → return to _ready/, update.
3. In development / already done → create an amendment file alongside.

Git Conventions for the Analyst

commit message convention
# Commit message format [AUTH-REG-020][Additive] Google OAuth Registration Requested-by: Maria K. / Product Reason: OAuth initiative Q2 2024 Decision: D-001 (email from Google = verified automatically) Stakeholders: Maria K. (Product), Ivan P. (CTO) Breaking: false Affects: AUTH-MERGE-001 # PR title [AUTH-REG-020] feat: Google OAuth registration # PR description includes verification checklist ## Verified by - [x] Maria K. / Product - [x] Ivan P. / CTO ## Open Questions resolved - OQ-1: merge prohibited without confirmation ✓

Working with Open Questions

Open Questions are the only reason to be in _draft/. As long as there is at least one status: open, the file does not move to root.

OQ StatusMeaningAction
openQuestion is open, no decision madeFile remains in _draft/
blockedDepends on another OQFile remains in _draft/
resolvedDecision made and recordedIf all resolved → PR to root

Common Work Scenarios

New Authorization Method

  1. Create AUTH-REG-030_github-oauth-registration.spec.md in _draft/
  2. Record open questions as OQ-N
  3. Conduct a session with the team — close OQ
  4. PR: move to root, stakeholders approve
  5. If GitHub-specific (no email) — separate leaf atom

Domain Model Change

  1. Create AUTH~MODEL-003~model_identity-aggregate.spec.md in _draft/
  2. Record § Model Delta (aggregate diff)
  3. Specify affects-atoms — all affected use-cases
  4. Get 3 verifiers: Product + Tech Lead + Architect
  5. Separate PR before the sprint that uses the new model begins

The Developer Role in Atomic Spec

The developer is the atom consumer. They read requirements, implement the platform contract, update implementation.status, and flag conflicts between requirements and implementation.

Golden Rule Before starting any task: git diff main HEAD -- specs/. Make sure the requirement in your branch is up to date. The analyst may have changed something in main while you were working.

Daily Work

At the start of each workday — three commands:

terminal
# 1. What changed in specs while I was away git log main --since="yesterday" -- specs/ --oneline # 2. Is there an amendment to my task grep -r "amends: AUTH-REG-020" specs/ -l # 3. Requirements delta in my branch vs main git diff main feat/AUTH-REG-020 -- specs/

How to Read an Atom

The developer reads sections top to bottom, stopping at the needed level of detail:

SectionWhat It Gives the DeveloperRequired
§ IntentUnderstand why this feature existsyes
§ Domain RulesInvariants and business rules — must not be violatedyes
§ Acceptance CriteriaGherkin — what must work (test foundation)yes
§ Domain Model TouchWhich aggregates are created/modifiedyes
§ Platform: Web APIExact API contract — endpoint, body, responsesyes
§ Platform TestsReady-made test cases for implementationtake as-is
§ Decision LogWhy it's done this way — decision contextwhen questions arise

Branch and PR Workflow

developer git workflow
# 1. Start task — branch by atom ID git checkout -b feat/AUTH-REG-020_google-oauth-registration # 2. Update implementation.status in the atom # implementation.status: none → in-progress git commit -m "[AUTH-REG-020] wip: started Google OAuth implementation" # 3. On completion — update status # implementation.status: in-progress → done git commit -m "[AUTH-REG-020] done: Google OAuth registration implemented PR: #341 Implements: AUTH-REG-020, AUTH-REG-021" # 4. PR title includes atom ID # [AUTH-REG-020] feat: Google OAuth registration

Platform Section — Contract Source

The § Platform: Web API section is the official contract. No need to ask the analyst "which endpoint", no need to check Swagger. Everything is in the file.

§ Platform: Web API
contract: method: POST path: /v1/auth/oauth/callback body: provider: string # "google" | "github" code: string # Authorization code from provider state: string # PKCE state for verification responses: 201: { userId, email?, provider, token, isNewUser: true } 200: { userId, email?, provider, token, isNewUser: false } 409: { code: ACCOUNT_MERGE_REQUIRED, mergeToken } 502: { code: PROVIDER_ERROR, provider } # Mobile platform — separate section § Platform: Mobile ui-flow: OAuthWebView → LoadingScreen → HomeScreen deep-link: myapp://auth/callback

When a Requirement Changed While You Are Working

The analyst created an amendment file alongside your atom. What to do:

  1. Read conflict.status in the amendment
  2. If sprint-locked → finish the current task as-is, amendment goes to the next PR
  3. If pulled → task returned, implementation needs updating
  4. Update amendment-status after your decision
Never stay silent about an amendment If the analyst created an amendment to your active task — that is an explicit signal. Discuss it in the PR comments or at the next standup. Record the decision in conflict.resolution.

Full Git Command List

git cheatsheet — developer
# What should I do in this sprint git log --oneline main..HEAD -- specs/ # Has the requirement changed in main while I was working git diff main feat/AUTH-REG-020 -- specs/ # History of a specific atom (who, when, why changed) git log --follow -p specs/auth/registration/AUTH-REG-020.spec.md # All active amendments (that need implementation) grep -r "amendment-status: pending" specs/ -l # Check that all my specs have implementation: done grep -r "implementation.status: in-progress" specs/ -l # Domain history — all Breaking changes git log --tags --simplify-by-decoration --pretty="%D %s" | grep domain/AUTH

Common Scenarios

Analyst did not describe an edge case

Create a leaf atom in _draft/ yourself, open an OQ, ask the analyst to close it. Do not invent behavior on your own — record the question.

Requirement contradicts another requirement

Add a see-also link, create an OQ in both atoms, block the PR until resolved. The conflict must be visible to the analyst.

Domain Model Change (ModelChange)

Wait until the analyst merges the ~model_ atom into main. Start implementation only after. ModelChange blocks the sprint — this is normal.

The Tester Role in Atomic Spec

The tester is the atom verifier. They check that the implementation matches the scenarios in § Acceptance Criteria, update verification.status, and block the release on mismatch.

The Tester's Key Question Not "what should I test" but "what changed in scenarios between this and the previous release." The answer is git diff release/v1..release/v2 -- specs/.

Preparing for Release Testing

terminal — starting Sprint-14 testing
# 1. List of changed specs in this release git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status # Output: A specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md A specs/auth/registration/AUTH-REG-021_google-provider-error.spec.md M specs/auth/otp/AUTH-OTP-002_rate-limit-exceeded.spec.md # 2. What exactly changed in Acceptance Criteria git diff release/Sprint-13..release/Sprint-14 \ -- specs/auth/otp/AUTH-OTP-002.spec.md # 3. Full list of what blocks the release grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" # 4. What has already passed verification grep -rl "verification.status: passed" specs/

Reading the Scenario Delta

The diff of the § Acceptance Criteria section is the list of what needs re-verification:

git diff — changes in OTP scenario
## § Acceptance Criteria Scenario: Repeated OTP request before timeout Given user requested OTP - When repeated request comes in less than 20 seconds + When repeated request comes in less than 60 seconds Then system rejects the request with code RATE_LIMITED And returns time until next attempt # New scenario added: + Scenario: OTP entry attempt limit exceeded + Given user entered incorrect OTP 3 times + Then account is locked for 15 minutes + And AccountTemporarilyBlocked is emitted

The tester sees: the old test with 20s needs to be updated to 60s. A new scenario with account locking was added.

Blocking Tests Matrix

blocks-releaseverificationAction
truenone🔴 URGENT — cannot release without this
truein-progress🟡 In progress — track
truepassed✅ Done — not blocking
falsenone⬜ Can be done after release
truefailed🔴 STOP — release blocked

Updating Verification Status

AUTH-REG-020.spec.md — updating after testing
verification: status: passed # none → in-progress → passed/failed blocks-release: true verified-by: "Kate M. / QA" verified-at: 2024-06-20 run-id: "Sprint-14-regression-001" # Commit message when updating [AUTH-REG-020][verification] passed — Google OAuth registration Verified-by: Kate M. / QA Sprint: Sprint-14 Run: Sprint-14-regression-001

Tester Git Commands

git cheatsheet — tester
# What's new in the release git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status # Only scenario delta (§ Acceptance Criteria) git diff release/Sprint-13..release/Sprint-14 -- specs/ | \ grep -A 20 "Acceptance Criteria" # Is anything blocking the release right now grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none\|verification.status: failed" # What has already been tested in this sprint git log release/Sprint-13..HEAD --grep="verification" --oneline # Amendments that affect scenarios find specs/ -name "*~*" -not -path "*_deprecated*"

Common Scenarios

Implementation does not match the scenario

Update verification.status: failed, add a comment with details, create a bug report referencing the atom (spec: AUTH-REG-020). If blocks-release: true — the release is automatically blocked at the next check.

Scenario is incomplete or incorrect

Open an OQ in the atom (the analyst gets the signal), do not close verification as passed. System behavior must match the scenario — not the other way around.

Amendment changed a value in a scenario

The old test (20s) needs updating to the new value (60s). The amendment file contains the exact was / becomes — use it as instructions for updating the test.

The Manager Role in Atomic Spec

The manager is the system state reader. Atoms provide a complete picture without the need to gather statuses from the team: what is implemented, what is untested, which decisions are blocked, and what risks the current sprint carries.

Key Principle The file tree and git domain tags are the single source of truth about the system state. If something is not in .spec.md files, it does not exist.

Status Dashboard — Five Commands

terminal — morning status monitoring
# How many active requirements in the system find specs/ -name "*.spec.md" \ ! -path "*/_draft/*" \ ! -path "*/_deprecated/*" | wc -l # Unresolved decisions — blocking progress grep -r "status: open" specs/ --include="*.spec.md" | \ grep "open-questions" | wc -l # What blocks the next release grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" 2>/dev/null # Implemented but not tested grep -rl "implementation.status: done" specs/ | \ xargs grep -l "verification.status: none" 2>/dev/null # Pending amendments — sprint conflicts find specs/ -name "*~*" ! -path "*/_deprecated/*" | \ xargs grep -l "amendment-status: pending" 2>/dev/null

Current Sprint Risks

Three signals that require management attention:

SignalMeaningAction
sprint-locked in amendmentRequirement changed mid-sprintConfirm decision: pull or amendment to next sprint
HIGH risk in use-caseHigh-risk feature in current sprintDedicate a separate PR, additional review
ModelChange not mergedArchitectural change blocks sprintPrioritize domain change review

Domain Evolution — Decision History

AUTH domain history
# Domain version history git log --tags --simplify-by-decoration --pretty="%ci %D %s" | grep "domain/AUTH" # Output — complete architectural decision history: 2024-02-01 domain/AUTH/v2.0 Breaking: email/password → phone/OTP 2024-05-01 domain/AUTH/v3.0 Breaking: Identity aggregate introduced 2024-08-01 domain/AUTH/v3.1 Additive: GitHub OAuth provider # What changed in the domain between versions git diff domain/AUTH/v2.0..domain/AUTH/v3.0 -- specs/auth/domain.spec.md # Versioning tags # vX.0 — ModelChange or BoundaryChange (Breaking) # vX.Y — RuleChange or FlowChange (Additive) # No PATCH — in requirements there are no "fixes" without semantic change

Release Readiness

Before each release — four checks:

  1. No blocking untested requirements
    blocks-release: true + verification: none → list is empty
  2. No failed verifications
    verification.status: failed → 0
  3. No pending amendments to implemented features
    amendment-status: pending in Done tasks → 0
  4. No open OQ in active atoms
    OQ status: open outside _draft/ → anomaly, requires attention

Team Health Metrics

MetricHow to CalculateTarget
Ratio draft/activeFiles in _draft / total active< 20% — no long-standing uncertainties
Amendment frequencyAmendment files per sprint0-1 — requirements are stable before sprint
Verification lagDays from implementation: done to verification: passed< 5 days
OQ resolution timeAverage time from open to resolved< 3 days — no stuck decisions
Breaking per quarterModelChange + BoundaryChange tags< 3 — architecture is stable

Manager Git Commands

git cheatsheet — manager
# Full picture: active / draft / deprecated echo "Active:" && find specs/ -name "*.spec.md" ! -path "*/_*" | wc -l echo "Draft:" && find specs/ -name "*.spec.md" -path "*/_draft/*" | wc -l echo "Depr.:" && find specs/ -name "*.spec.md" -path "*/_deprecated/*" | wc -l # What changed in the system in the last 2 weeks git log --since="2 weeks ago" -- specs/ --oneline # Check release readiness (4 checks) echo "=== RELEASE READINESS ===" && \ echo "Blocking untested:" && \ grep -rl "blocks-release: true" specs/ | xargs grep -l "verification.status: none" 2>/dev/null && \ echo "Failed verification:" && \ grep -rl "verification.status: failed" specs/ 2>/dev/null && \ echo "Pending amendments:" && \ find specs/ -name "*~*" ! -path "*/_deprecated/*" | xargs grep -l "amendment-status: pending" 2>/dev/null && \ echo "Open OQ outside draft:" && \ grep -r "status: open" specs/ --include="*.spec.md" ! -path "*/_draft/*" -l 2>/dev/null && \ echo "=== ALL CLEAR if no output above ===" # Breaking changes history for the quarter git log --since="3 months ago" --grep="Breaking: true" --oneline -- specs/

What is Atomic Spec

Atomic Spec is a collaborative requirements methodology that combines four approaches into a single knowledge management system in a git repository.

ApproachWhat We Take
Domain-Driven DesignDomain model (aggregates, events, bounded context) as the source of the team's ubiquitous language
Test-Driven DevelopmentAcceptance criteria (Gherkin) inside the requirement itself — test and requirement are inseparable
Use Case DrivenUse cases as the atomic unit of functionality
Requirements-DrivenRequirements as living artifacts with history, not frozen documents

The central idea: one file — one unit of knowledge. Atoms form a hierarchy, reference each other, and contain everything — from business intent to test cases and platform contracts.

The Key Test Can you reconstruct the full domain history — who, when, why made each decision — solely from git log on *.spec.md files? Yes → the methodology is applied correctly.

Why It's Needed

Five problems that Atomic Spec solves:

ProblemHow It Solves It
Requirements scattered across Confluence, Jira, SlackEverything in one .spec.md file next to the code
Unclear why a decision was made§ Decision Log and git log — complete history
Tester doesn't know what changedgit diff release/v1..release/v2 — exact scenario delta
PM changed a requirement during sprint — team doesn't knowAmendment file with explicit conflict.status
Manager can't see the real statusFile tree = live dashboard without meetings

Atom Hierarchy

hierarchy
System system.spec.md ← system-wide boundaries └── Domain domain.spec.md ← bounded context, model, events └── Use Case AUTH-REG-010_slug.spec.md ← functional scenario └── Scenario AUTH-REG-011_slug.spec.md ← atomic test case (leaf)
LevelFileCost of ChangeRFC
Systemsystem.spec.mdCritical — affects all domainsrequired
Domaindomain.spec.mdHigh — 3+ verifiersrecommended
Use CaseDOMAIN-TYPE-NNN_slug.spec.mdMedium — 2 verifiersno
ScenarioDOMAIN-TYPE-NNN_slug.spec.mdLow — 1 verifierno

Atom Anatomy

File = YAML frontmatter + Markdown sections. Sections go from abstract to concrete. Each role reads their own sections — no one is forced to read everything.

atom structure
--- YAML frontmatter --- ## § Intent ← PM/Analyst: business intent, why it's needed ## § Domain Rules ← PM/Analyst: DR-N — business rules and invariants ## § Acceptance Criteria ← Tester: Gherkin, technology-neutral ## § Domain Model Touch ← Dev + Analyst: aggregates, fields, events ## § Constraints ← PM/Dev: non-functional (PERF, SEC, IDMP) ## § Platform: Web API ← Dev: endpoint, body, responses — contract ## § Platform Tests ← Tester: ready-made test cases for the platform ## § Open Questions ← All: OQ-N — unresolved questions (reason for draft) ## § Decision Log ← All: D-N — decisions made with alternatives

File Naming

file name anatomy
AUTH-REG-010_phone-otp-registration.spec.md ^^^^ ^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^ │ │ │ slug: action-noun (reads as a description) │ │ ordinal number — step 10 (step 10, next 020, gap reserved) │ type: REG=registration, LOG=login, OTP, MERGE, PAY, ORDER... domain — all uppercase, 2-6 letters # Examples of correct slugs email-already-taken ← noun + adjective phone-otp-registration ← topic + action google-oauth-login ← provider + method + action payment-3ds-verification ← technology + action # Amendments — suffix after id AUTH-OTP-002a~param_retry-timeout.spec.md ← ParameterChange AUTH-REG-010b~rule_attempt-limit.spec.md ← RuleChange AUTH-REG-010c~flow_add-resend-step.spec.md ← FlowChange AUTH~MODEL-003~model_identity-aggregate.spec.md ← ModelChange ~boundary_extract-otp-domain.rfc.md ← BoundaryChange RFC

Folders and Statuses

Three positions define the state of a requirement. A file is always in exactly one of them.

folder structure
feature/ _draft/ ← has open OQ / not verified cannot be taken into development file moves to root when all OQ = resolved _deprecated/ ← decommissioned NEVER delete — this is decision history deprecated-by references the replacement file.spec.md ← verified, current, ready for development single source of truth for the team
QuestionAnswer Without Opening the File
Can it be taken into work?File in the folder root → yes
Are there open questions?File in _draft/ → no, stop
Is the requirement outdated?File in _deprecated/ → history
Currently in development?Open PR in git
Implemented?implementation.status: done + merged PR
Tested?verification.status: passed

Each directory contains _index.md — a table of all files with quick status. This is the directory's live dashboard.

Frontmatter — Full Specification

Frontmatter contains only what cannot be learned from git: structural links between atoms, open questions, and statuses on three axes (semantic, implementation, testing).

full frontmatter
--- # ── Identification ────────────────────────────────────── id: AUTH-REG-010 type: use-case # use-case | scenario | domain | amendment title: "Phone/OTP Registration" # ── Hierarchy — git does not know these links ─────────────── parent: AUTH children: - AUTH-REG-011_phone-already-taken - AUTH-REG-012_otp-expired - AUTH-REG-013_otp-invalid - AUTH-REG-014_registration-success # ── Domain references ───────────────────────────────────── supersedes: AUTH-REG-001_email-password-registration see-also: [AUTH-OTP-001, AUTH-MERGE-001] emits: [UserRegistered] consumes: [OTPCode] # ── Open questions — the only reason to be in _draft/ open-questions: - id: OQ-1 question: "Block the account or only the session after 3 errors?" status: resolved # open | resolved | blocked resolution: "Block the account for 15 minutes" resolved-by: "Maria K. / Product, 2024-03-15" # ── Axis 1: semantic status (analyst) ───────────────── status: active # draft | active | deprecated # ── Axis 2: implementation status (developer) ───────── implementation: status: done # none | in-progress | done sprint: Sprint-12 pr: github.com/org/repo/pull/289 # ── Axis 3: testing status (tester) ─────────────── verification: status: passed # none | in-progress | passed | failed blocks-release: true verified-by: "Kate M. / QA, 2024-04-01" ---
What is NOT stored in frontmatter Who changed, when changed, why changed, who requested — all of this is in git log and PR description. No need to duplicate it in the file.

Change Types

Each requirement change is classified before implementation. The type determines: who verifies, whether it blocks the sprint, whether an RFC is needed, and the radius of affected atoms.

TypeWhat ChangesExampleVerifiersBlocks Sprint
ParameterChangeConstant valueTimeout 20s → 60s1 stakeholderno
RuleChangeDomain rule DRAdd attempt limitProduct + Techpossibly
FlowChangeScenario step, branchAdd resend stepProductpossibly
ModelChangeAggregate, field, eventNew Identity aggregateProduct + Tech + Archyes
BoundaryChangeDomain boundariesExtract OTP domainCTO + Arch + Productyes + RFC
PlatformChangeAPI contractNew field in responseTech Leadno
Key difference from semver Semver answers: "can I upgrade without changing my code?" In requirements the question is different: "what do I need to reconsider after this change?" For the Platform level semver works perfectly. For the domain level — you need a Breaking / Additive classification, without PATCH (in requirements there are no "fixes" without semantic change).

Amendments

When a requirement changes while the atom is already in development, the change is not made to the original file. A separate amendment file is created with an explicit conflict indication.

AUTH-OTP-002a~param_retry-timeout.spec.md
--- id: AUTH-OTP-002a type: amendment change: ParameterChange title: "OTP retry timeout: 20s → 60s" amends: AUTH-OTP-002_rate-limit-exceeded parameter: name: retry_timeout_seconds was: 20 becomes: 60 conflict: status: sprint-locked # sprint-locked | pulled | merged locked-sprint: Sprint-14 resolution: "Finish Sprint-14 with the old value. Fix → Sprint-15" decided-by: "Alex S. / Dev + Maria K. / Product, 2024-06-10" amendment-status: pending # pending | merged ---

Three Scenarios when a change conflicts with the sprint:

SituationAction
Atom is still in _draft/ or _ready/Update the file in place, record in changelog commit
In development, already pulledReturn to _ready/, update, reconsider sprint
In development or already doneCreate an amendment file alongside, conflict.status: sprint-locked

Domain Versioning

Different system levels are versioned differently because they have different consumers and different compatibility semantics.

LevelSchemeLogic
DomainvMAJOR.MINOR git tagMAJOR = ModelChange/BoundaryChange. MINOR = RuleChange/Additive. No PATCH.
Use Case / ScenarioRevision r1, r2, r3Linear history. The consumer looks at the diff, not "am I compatible."
Platform (API)Honest semverConsumer is code. Compatibility is technical. Semver works perfectly.
AmendmentNo versionThis is an event, not an artifact. Lives in git as a commit.
tag examples
# AUTH domain — architectural decision history git tag domain/AUTH/v1.0 -m "Initial: email/password auth" git tag domain/AUTH/v2.0 -m "Breaking: email→phone/OTP. D-001." git tag domain/AUTH/v3.0 -m "Breaking: Identity aggregate. D-002." git tag domain/AUTH/v3.1 -m "Additive: GitHub OAuth provider" # Platform API — honest semver git tag platform/web-api/v1.0.0 -m "Initial API" git tag platform/web-api/v1.1.0 -m "Added provider field to response" git tag platform/web-api/v2.0.0 -m "Breaking: changed auth callback contract" # Viewing domain history git log --tags --simplify-by-decoration --pretty="%ci %D %s" | grep "domain/AUTH"

Git Conventions

Git is the decision journal. Commit message carries everything not stored in the file: who requested, why, and what it affects.

commit and PR conventions
# Commit message — full format [AUTH-REG-020][Additive] Google OAuth Registration Requested-by: Maria K. / Product Reason: OAuth initiative Q2 2024 Decision: D-001 (email from Google = verified automatically) Stakeholders: Maria K. (Product), Ivan P. (CTO) Breaking: false Affects: AUTH-MERGE-001, AUTH-LOG-020 # Branch naming feat/AUTH-REG-020_google-oauth-registration ← new feature amend/AUTH-OTP-002a_retry-timeout ← amendment rfc/AUTH-boundary-extract-otp ← RFC domain/AUTH-model-identity-aggregate ← ModelChange # PR title [AUTH-REG-020][Additive] feat: Google OAuth registration # PR description — required blocks ## Spec change ## Verified by (stakeholder checklist) ## Open Questions resolved ## Breaking changes ## Affects

Three Perspectives on One Repository

The same files provide different views for different roles. The key tool is git diff between the relevant tags or branches.

RoleViewTool
AnalystCurrent requirements + open questionsmain branch + files in _draft/
DeveloperRequirements delta in their branchgit diff main feat/...
TesterWhat changed in release scenariosgit diff release/v1..release/v2
ManagerFull picture + risks_index.md + grep queries

Principles of Making Changes

1. An atom is not deleted — it becomes deprecated. The file moves to _deprecated/ with deprecated-by and reason specified. A deleted atom breaks history.

2. Leaves are added, branches change minimally. New scenario = new file. Use-case change = diff with changelog in commit. The higher the level, the more conservative.

3. Atom scope is not expanded — a new one is created. Expanding scope = hidden contract change. Everyone referencing the atom silently gets different semantics.

4. Domain Rules — append only, never rewrite. DR changes are recorded with an explicit note: [updated: 2024-03, reason: security policy v3].

5. Open questions block implementation, not documentation. An atom with open OQ exists and is visible. The team knows the question exists, and does not make assumptions.

6. No domain change without an explicit decision. ModelChange and BoundaryChange require § Decision Log with alternatives and rationale.

7. PR boundary = domain risk boundary. Do not mix in one PR: domain changes + new scenarios, high-risk + low-risk, domain change + platform change.

Domain Evolution — Live Example

Example: the authentication system went through 4 iterations. Here is how the tree changed and what it meant for the team.

AUTH domain evolution — summary table
Iter 0 Iter 1 Iter 2 Iter 3 email/pass phone/OTP +Google +GitHub ───────────────────────────────────────────────────────────────────── domain.spec v1.0 v2.0 Breaking v3.0 Break v3.1 Add AUTH-REG-001 active deprecated AUTH-REG-010 active active active AUTH-REG-020 active active AUTH-REG-030 active AUTH-MERGE-001 _draft→ active ───────────────────────────────────────────────────────────────────── Total atoms 7 14 21 30 Active 7 8 15 24 Deprecated 0 6 6 6 # Conclusion: adding GitHub (iter 3) cost 3x less than Google (iter 2) # because the Identity aggregate in v3.0 is an investment in extensibility
Learning Cases

Case: Payment Systems Integrator

Context: the product integrates multiple payment providers — Stripe (international cards), Tinkoff (RU cards), SBP (QR code). Team: 1 PM, 3 developers, 1 QA. Two-week sprints.

Key challenge: each provider has its own rules, webhook formats, and refund logic. Adding a new provider must not break existing ones. The domain model must be provider-agnostic.

Domain Model

specs/payment/domain.spec.md v2.1
## § Domain Model Aggregate: Payment id: UUID orderId: UUID → Order amount: Money { value: Decimal, currency: ISO4217 } provider: "stripe" | "tinkoff" | "sbp" status: pending | authorized | captured | failed | refunded providerRef: string ← external ID in the provider's system metadata: map ← provider-specific data Aggregate: Refund id: UUID paymentId: UUID → Payment amount: Money reason: string status: pending | processed | failed Events: PaymentInitiated { paymentId, orderId, provider, amount } PaymentAuthorized { paymentId, providerRef } PaymentCaptured { paymentId } PaymentFailed { paymentId, reason, providerCode } RefundRequested { refundId, paymentId, amount } RefundProcessed { refundId } ## § Decision Log D-001: "Money as Value Object, not a number" rationale: "Eliminates currency and rounding errors" D-002: "providerRef — string, not typed" rationale: "Each provider has its own ID format"

File Tree

📁 specs/payment/
├── domain.spec.md v2.1 · Payment + Refund aggregates
├── _index.md
├── 📁 initiation/
│ ├── PAY-INI-010_stripe-card-payment.spec.md
│ ├── PAY-INI-011_stripe-3ds-required.spec.md
│ ├── PAY-INI-012_stripe-card-declined.spec.md
│ ├── PAY-INI-020_tinkoff-card-payment.spec.md
│ ├── PAY-INI-030_sbp-qr-payment.spec.md new
│ ├── PAY-INI-031_sbp-qr-expired.spec.md
│ └── 📁 _deprecated/
│ └── PAY-INI-001_direct-card-storage.spec.md PCI-DSS violation, removed
├── 📁 capture/
│ ├── PAY-CAP-010_capture-authorized-payment.spec.md
│ └── PAY-CAP-011_capture-after-timeout.spec.md
├── 📁 refund/
│ ├── PAY-REF-010_full-refund.spec.md
│ ├── PAY-REF-011_partial-refund.spec.md
│ └── 📁 _draft/
│ └── PAY-REF-012_refund-after-sbp.spec.md OQ-1: Does SBP support partial refunds?
└── 📁 webhook/
├── PAY-WHK-010_stripe-webhook-handler.spec.md
└── PAY-WHK-020_tinkoff-webhook-handler.spec.md

Atom Example: SBP QR Payment

PAY-INI-030_sbp-qr-payment.spec.md
--- id: PAY-INI-030 type: use-case title: "Initiate payment via SBP QR code" parent: PAYMENT children: - PAY-INI-031_sbp-qr-expired - PAY-INI-032_sbp-bank-unavailable see-also: [PAY-REF-012_refund-after-sbp] emits: [PaymentInitiated, PaymentCaptured, PaymentFailed] implementation: { status: done, sprint: Sprint-08, pr: "#412" } verification: { status: passed, blocks-release: true } --- ## § Intent The buyer pays for an order via SBP — scanning a QR code with their banking app. Payment is confirmed via a webhook from NSPK. The QR code is valid for 15 minutes. ## § Domain Rules DR-1 QR code is generated on the SBP side via NSPK API DR-2 QR code is valid for 15 minutes. After expiry → Payment status: failed DR-3 Confirmation arrives asynchronously via webhook DR-4 Duplicate confirmation for one payment → idempotently ignored (IDMP) DR-5 SBP does not support partial capture — full amount only ## § Acceptance Criteria Scenario: Successful payment via SBP Given an order for 1500 RUB awaiting payment When the buyer selects SBP payment Then the system obtains a QR code from NSPK API And the QR code is displayed to the buyer When the buyer scans the QR and confirms in their bank Then the system receives a PaymentConfirmed webhook from NSPK And Payment.status = captured And PaymentCaptured { paymentId, amount: 1500 RUB } is emitted Scenario: QR expired without payment [→ PAY-INI-031] Scenario: Buyer's bank unavailable [→ PAY-INI-032] ## § Platform: Web API initiate: POST /v1/payments/sbp/initiate body: { orderId: UUID, amount: Money } 201: { paymentId: UUID, qrUrl: string, expiresAt: timestamp } 422: { code: AMOUNT_TOO_SMALL } ← SBP minimum: 1 RUB webhook: POST /v1/webhooks/sbp ← described in PAY-WHK-030

Sprint Amendment: QR Timeout Change

In Sprint-08, SBP returned a QR timeout of 10 minutes, although the NSPK documentation stated 15. After clarification, it turned out to be 10 minutes. The amendment arrived while the developer was already working on the feature.

PAY-INI-030a~param_sbp-qr-ttl.spec.md
--- id: PAY-INI-030a type: amendment change: ParameterChange title: "SBP QR TTL: 15 min → 10 min" amends: PAY-INI-030_sbp-qr-payment parameter: name: qr_ttl_minutes was: 15 becomes: 10 source: "NSPK technical documentation v3.2, section 4.1" conflict: status: pulled ← managed to pull from sprint in time resolution: "Update PAY-INI-030 before implementation begins" decided-by: "Alex S. / Dev, 2024-06-03" amendment-status: merged ← amendment applied to the main atom ---

Evolution: Adding SBP to Stripe + Tinkoff

The first two providers stored card data in Payment. When adding SBP, the model needed to be extended — SBP works differently. Solution: ProviderSession as a separate aggregate.

PAY~MODEL-002~model_provider-session.spec.md
--- id: PAY~MODEL-002 type: model-change change: ModelChange title: "ProviderSession aggregate for asynchronous providers" affects-domain: PAYMENT affects-atoms: - PAY-INI-010 ← Stripe: add sessionId - PAY-INI-020 ← Tinkoff: add sessionId decision: id: D-003 statement: "ProviderSession separate from Payment" rationale: "SBP has its own lifecycle (QR → poll → confirm) independent of Payment" alternatives: ["Add qrUrl field to Payment (rejected: God Object)"] --- ## § Model Delta + Aggregate: ProviderSession + id: UUID + paymentId: UUID → Payment + provider: "stripe" | "tinkoff" | "sbp" + sessionData: map ← qrUrl, 3dsRedirect, etc + expiresAt: timestamp? + status: active | expired | completed

Three Perspectives on a Single Sprint

RoleLooks AtSees
PM_draft/refund/OQ-1 open: SBP refund unknown → do not take into sprint
Devgit diff main feat/sbpPAY~MODEL-002 added ProviderSession — need a new repository
QAgit diff release/v1..release/v2 -- specs/New scenarios PAY-INI-030, 031, 032 — all blocks-release: true

Case: Blog Platform

Context: a media platform for authors. Publications, drafts, categories, comments, SEO metadata. Started as a simple blog — grew into an editorial platform with roles over 6 months.

Key insight: the system seems simple, but has non-trivial evolution. Started with "publication = article", then drafts appeared, then revisions, then collaborative editing. Each step broke previous assumptions.

Domain Model v3.0

specs/blog/domain.spec.md v3.0
## § Domain Model Aggregate: Post id: UUID authorId: UUID → User slug: string (unique, URL-safe) status: draft | review | published | archived publishedAt: timestamp? seo: SEOMeta { title, description, ogImage } Aggregate: Revision ← added in v2.0 id: UUID postId: UUID → Post content: string (Markdown) authorId: UUID → User createdAt: timestamp isCurrent: boolean Aggregate: Comment id: UUID postId: UUID → Post authorId: UUID → User content: string status: pending | approved | rejected Events: PostPublished { postId, slug, authorId } PostArchived { postId } RevisionSaved { revisionId, postId } CommentApproved { commentId, postId } ## § Changelog v1.0: Post with content:string directly v2.0: Breaking — content moved to Revision (edit history) v3.0: Additive — SEOMeta as Value Object in Post

File Tree

📁 specs/blog/
├── domain.spec.md v3.0
├── 📁 publishing/
│ ├── BLOG-PUB-010_create-draft.spec.md
│ ├── BLOG-PUB-011_save-revision.spec.md
│ ├── BLOG-PUB-020_publish-post.spec.md
│ ├── BLOG-PUB-021_slug-already-taken.spec.md
│ ├── BLOG-PUB-030_archive-post.spec.md
│ └── 📁 _deprecated/
│ └── BLOG-PUB-001_publish-with-content.spec.md v1.0 era, content in Post
├── 📁 seo/
│ ├── BLOG-SEO-010_set-seo-meta.spec.md
│ └── BLOG-SEO-011_auto-generate-seo.spec.md
├── 📁 comments/
│ ├── BLOG-CMT-010_submit-comment.spec.md
│ ├── BLOG-CMT-011_moderate-comment.spec.md
│ └── 📁 _draft/
│ └── BLOG-CMT-020_nested-comments.spec.md OQ-1: nesting depth?
└── 📁 feed/
├── BLOG-FEED-010_get-published-posts.spec.md
└── BLOG-FEED-011_filter-by-category.spec.md

Atom Example: Publishing a Post

BLOG-PUB-020_publish-post.spec.md
--- id: BLOG-PUB-020 type: use-case title: "Publish post" parent: BLOG emits: [PostPublished] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent The author transitions a post from draft to published status. After publishing, the post is accessible via a public URL (slug). ## § Domain Rules DR-1 Publishing is only allowed from draft or review status DR-2 Slug is generated from the title and must be unique DR-3 Post must have at least one Revision with non-empty content DR-4 SEO metadata is optional but recommended (warning) DR-5 publishedAt is set at the moment of transition to published ## § Acceptance Criteria Scenario: Successful publication Given a post in draft status And the post has a Revision with non-empty content When the author clicks "Publish" Then Post.status = published And Post.publishedAt = now() And the post is accessible via GET /posts/{slug} And PostPublished is emitted Scenario: Slug already taken [→ BLOG-PUB-021] Scenario: No content [→ BLOG-PUB-022]

Evolution: from a Simple Blog to an Editorial Platform

VersionWhat ChangedTypeWhat Broke
v1.0Post with content: string directlyInitial
v2.0Content moved to Revision (edit history)ModelChangeAll use-cases that worked with Post.content
v2.1Added review status to PostRuleChangeDR-1 in BLOG-PUB-020 updated
v3.0SEOMeta as Value ObjectAdditiveNothing — new fields are optional
Key Lesson of the Case Moving content to Revision (v2.0) is a ModelChange that invalidated all v1.0 code. In Atomic Spec this is visible in advance: all atoms with Post.content in § Domain Model Touch are listed in the affects-atoms of the ModelChange file. The team knew the scope before development began.

Amendment: Made SEO-title Required

After v3.0, the analyst made SEO-title required for publishing. The developer was already working on the publishing feature.

BLOG-PUB-020b~rule_seo-title-required.spec.md
--- id: BLOG-PUB-020b type: amendment change: RuleChange title: "SEO title required for publishing" amends: BLOG-PUB-020_publish-post rule: action: modify dr-id: DR-4 was: "SEO metadata is optional (warning)" becomes: "SEO title is required for publishing. Without it — error SEO_TITLE_REQUIRED" impact: [BLOG-PUB-020, BLOG-SEO-010] conflict: status: sprint-locked resolution: "Sprint-05: publish without SEO title. Sprint-06: add validation" ---

Case: AI Chatbot

Context: the product is an AI assistant for customer support. Chats with history, model switching (GPT-4 / Claude / Llama), context window, memory, tools. Team of 4 people. Requirements change every week — this is the main challenge for the methodology.

Key complexity: AI products have vague requirements by default. "The bot should respond smartly" is not a DR. Atomic Spec forces you to formalize what seems obvious: maximum context size, behavior on model error, what counts as a "good" response.

Domain model

specs/chat/domain.spec.md v2.0
## § Domain Model Aggregate: Conversation id: UUID userId: UUID → User model: "gpt-4" | "claude-3" | "llama-3" status: active | archived systemPrompt: string? ← custom instructions tokenCount: integer ← current context size Aggregate: Message id: UUID conversationId: UUID → Conversation role: user | assistant | system content: string tokens: integer model: string? ← model that generated (only role=assistant) latencyMs: integer? ValueObject: ContextWindow maxTokens: integer ← depends on the model strategy: truncate-oldest | summarize Events: MessageSent { messageId, conversationId, tokens } ResponseGenerated { messageId, model, latencyMs, tokens } ContextTruncated { conversationId, removedMessages } ModelSwitched { conversationId, from, to } ## § Decision Log D-001: "tokenCount in Conversation, not in Memory" rationale: "Needed for real-time check before sending" D-002: "Truncate strategy by default, not summarize" rationale: "Summarize adds latency and costs tokens" alternatives: ["summarize (rejected: expensive)", "hard error (rejected: UX)"]

File Tree

📁 specs/chat/
├── domain.spec.md v2.0
├── 📁 messaging/
│ ├── CHAT-MSG-010_send-user-message.spec.md
│ ├── CHAT-MSG-011_stream-response.spec.md
│ ├── CHAT-MSG-012_model-error-fallback.spec.md
│ └── CHAT-MSG-013_context-truncation.spec.md
├── 📁 models/
│ ├── CHAT-MOD-010_switch-model.spec.md
│ ├── CHAT-MOD-011_model-unavailable.spec.md
│ └── 📁 _draft/
│ └── CHAT-MOD-020_auto-model-routing.spec.md OQ-1, OQ-2 open
├── 📁 history/
│ ├── CHAT-HST-010_load-conversation.spec.md
│ └── CHAT-HST-011_archive-conversation.spec.md
└── 📁 tools/
├── CHAT-TOOL-010_web-search-tool.spec.md
└── 📁 _draft/
└── CHAT-TOOL-020_code-execution-tool.spec.md OQ-3: sandbox requirements

Atom Example: Sending a Message

CHAT-MSG-010_send-user-message.spec.md
--- id: CHAT-MSG-010 type: use-case title: "User sends a message" emits: [MessageSent, ResponseGenerated] see-also: [CHAT-MSG-013_context-truncation] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent The user sends a text message in an active chat. The system passes the conversation context to the LLM and streams the response. ## § Domain Rules DR-1 Maximum length of a single message: 4000 characters DR-2 Before sending: verify that tokenCount + newTokens <= model.maxTokens DR-3 If context is overflowed → perform truncation (CHAT-MSG-013), then send DR-4 Response is streamed in chunks as generated — do not wait for full response DR-5 Timeout waiting for first chunk: 10 seconds. Then → fallback (CHAT-MSG-012) DR-6 Latency of each response is recorded in Message.latencyMs ## § Acceptance Criteria Scenario: Successful send and response received Given an active chat with model gpt-4 And tokenCount is within the limit When the user sends a message "How are you?" Then a Message { role: user } is created And the system starts streaming the response within 10 seconds And a Message { role: assistant, model: "gpt-4" } is created And ResponseGenerated with latencyMs is emitted Scenario: Message too long Given the user enters text longer than 4000 characters Then MESSAGE_TOO_LONG error before sending ## § Constraints PERF First response chunk: < 2s at p95 SEC Content passes through moderation before being sent to the LLM ## § Platform: Web API POST /v1/conversations/{id}/messages body: { content: string } response: SSE stream event: chunk { delta: string } event: done { messageId: UUID, tokens: int, latencyMs: int } event: error { code: string }

How to Handle Rapid Changes

AI products have 2-3 requirement changes per week. Atomic Spec has a special practice for such products:

SituationHow to Handle
Model timeout changed~param amendment — one field, one verifier, one PR
Added a new model to enumAdditive in domain.spec.md — one line in the model
Truncation logic changed~rule amendment to CHAT-MSG-013 — product + tech verification
New tool typeNew atom in tools/ — created in _draft/ while OQs are open
LLM provider switchModelChange — requires RFC, blocks the sprint
Fast Products Rule The faster requirements change — the more important change typing becomes. ParameterChange (timeout) and ModelChange (new aggregate) must be handled differently. Atomic Spec makes this difference visible and mandatory.

LLM Model Switch: ModelChange Example

The team decided to add Llama-3 support as a cheaper alternative. However, Llama has a different API, a different context limit, and no function calling. This is not just "adding a line to an enum".

CHAT~MODEL-002~model_llama-provider.spec.md
--- id: CHAT~MODEL-002 type: model-change change: ModelChange title: "Llama-3 support as a provider without function calling" affects-domain: CHAT affects-atoms: - CHAT-MSG-010 ← tools unavailable for Llama - CHAT-TOOL-010 ← web-search tool only for GPT/Claude - CHAT-MOD-010 ← model switching: new constraints decision: id: D-004 statement: "ModelCapabilities as ValueObject — not all models are equal" rationale: "Llama does not support tools. Cannot hardcode this in every use-case" ## § Model Delta + ValueObject: ModelCapabilities + supportsTools: boolean + supportsStreaming: boolean + maxContextTokens: integer + costPerToken: Decimal Aggregate: Conversation - model: "gpt-4" | "claude-3" | "llama-3" + model: ModelId ← separate type with capabilities + capabilities: ModelCapabilities ← cached at the moment of chat creation

Case: Online Store

Context: an e-commerce platform with multiple teams. Product catalog, cart, orders, delivery, returns. Each domain has its own team (2-3 people). This makes cross-team interaction a key challenge.

Main complexity: multiple domains interact through events. A change in one domain can unexpectedly break another. Atomic Spec makes these dependencies explicit through emits / consumes in frontmatter.

System Domains

system.spec.md — domain boundaries
## § Domains CATALOG: Products, categories, availability, prices Team: Team A (2 dev) Publishes: ProductUpdated, StockChanged CART: Cart, discount application, availability check Team: Team B (2 dev) Consumes: StockChanged, PriceUpdated Publishes: CartCheckedOut ORDER: Orders, statuses, history Team: Team B (same, 2 dev) Consumes: CartCheckedOut, PaymentCaptured Publishes: OrderConfirmed, OrderCancelled DELIVERY: Delivery, tracking, couriers Team: Team C (1 dev + integration) Consumes: OrderConfirmed Publishes: DeliveryStarted, DeliveryCompleted PAYMENT: Payments (see case 1) Consumes: OrderConfirmed Publishes: PaymentCaptured, RefundProcessed ## § Event Map (critical dependencies) CATALOG → (StockChanged) → CART CART → (CartCheckedOut) → ORDER ORDER → (OrderConfirmed) → DELIVERY, PAYMENT PAYMENT → (PaymentCaptured) → ORDER

File Tree

📁 specs/
├── system.spec.md v1.2 · cross-team dependencies
├── 📁 catalog/
│ ├── domain.spec.md v1.1
│ ├── CAT-PRD-010_add-product.spec.md
│ ├── CAT-PRD-011_update-price.spec.md
│ └── CAT-STK-010_update-stock.spec.md
├── 📁 cart/
│ ├── domain.spec.md
│ ├── CART-010_add-item.spec.md
│ ├── CART-011_remove-item.spec.md
│ ├── CART-020_apply-promo-code.spec.md
│ ├── CART-030_checkout.spec.md
│ └── CART-031_item-out-of-stock-on-checkout.spec.md
├── 📁 order/
│ ├── domain.spec.md
│ ├── ORD-010_confirm-order.spec.md
│ ├── ORD-011_cancel-order.spec.md
│ └── 📁 _draft/
│ └── ORD-020_partial-cancel.spec.md OQ-1: partial item rejection?
└── 📁 delivery/
├── domain.spec.md
├── DEL-010_create-delivery.spec.md
└── DEL-011_track-delivery.spec.md

Atom: Checkout

CART-030_checkout.spec.md
--- id: CART-030 type: use-case title: "Checkout" parent: CART emits: [CartCheckedOut] consumes: [StockChanged] ← listening for availability changes see-also: [ORD-010_confirm-order, CART-031_item-out-of-stock] --- ## § Intent The buyer completes the cart and proceeds to payment. The system reserves items and creates an order in the ORDER domain. ## § Domain Rules DR-1 Before checkout: verify current availability of each item DR-2 If an item is unavailable → error specifying the affected items DR-3 Price is locked at checkout time (does not change when the catalog changes) DR-4 Reservation via StockReservation — not subtraction from stock DR-5 Reservation lasts 30 minutes. Then — released automatically DR-6 Minimum order amount: 100 RUB (configurable parameter) ## § Acceptance Criteria Scenario: Successful checkout Given a cart with 2 items, both in stock When the buyer clicks "Place order" Then the price of each item is locked at the current level And a reservation is created for 30 minutes And CartCheckedOut { cartId, items, totalPrice } is emitted And the ORDER domain creates an Order (via event) Scenario: Item went out of stock during checkout [→ CART-031] Scenario: Amount below minimum [→ CART-032] ## § Decision Log D-001: "Reservation, not subtraction" rationale: "Allows releasing if payment failed without double deduction" alternatives: ["Subtract immediately (rejected: cannot easily roll back)"]

BoundaryChange: Extracting Inventory into a Separate Domain

After team growth, inventory management was extracted into a separate INVENTORY domain. This is a BoundaryChange — the most expensive type of change, requiring an RFC.

~boundary_extract-inventory-domain.rfc.md
--- id: RFC-002 type: boundary-change title: "Extracting inventory management into the INVENTORY domain" status: accepted # proposed | accepted | rejected moves: from-domain: CATALOG to-domain: INVENTORY ← new domain, new Team D atoms: - CAT-STK-010_update-stock - CAT-STK-011_reserve-stock - CAT-STK-012_release-reservation impact: - CART-030 ← consumes StockChanged — source changes - ORD-010 ← depends on reservation cross-team: true votes: - { stakeholder: "CTO", vote: accept, date: 2024-09-01 } - { stakeholder: "Arch", vote: accept, date: 2024-09-02 } - { stakeholder: "Product", vote: accept, date: 2024-09-03 } migration-plan: Sprint-20: create INVENTORY domain, copy atoms Sprint-21: switch CART/ORDER to new events Sprint-22: deprecate CAT-STK-* atoms, final smoke test ---

Multi-Team Perspectives

When multiple teams work on the same repository, each team works with its own subtree but sees dependencies through system.spec.md.

TeamOwn SubtreeWatches EventsRisk
Team A (Catalog)specs/catalog/Publishes StockChangedChanging the event format breaks Team B
Team B (Cart + Order)specs/cart/, specs/order/Consumes StockChanged, publishes CartCheckedOutDepends on two other teams
Team C (Delivery)specs/delivery/Consumes OrderConfirmedAny change to ORD-010 requires verification
Cross-Team Rule Changing emits in any atom is a Breaking change for all teams that have this event in consumes. Before any event change: find all atoms with this event in consumes and notify their teams.
grep: find all event consumers
# Who consumes StockChanged — notify before changing grep -r "StockChanged" specs/ --include="*.spec.md" -l # Output: specs/cart/CART-030_checkout.spec.md specs/cart/CART-031_item-out-of-stock.spec.md # → Notify Team B before any change to StockChanged
Learning Cases

Methodology
in real-world examples

Four different systems — one approach. See what an atom tree looks like, how a domain evolves, and how a team works with real requirements.

How to read a case

Each case shows the complete path

01
Domain model
Aggregates, events, invariants — where a system begins
02
Atom tree
Real file structure with use-cases and scenarios
03
Evolution
How a system changes from iteration to iteration
Learning Cases

Overview: Payment Systems Integrator

The system accepts payments through multiple providers: Stripe (international cards), YooKassa (Russian cards), SBP (instant transfers). This is a classic case where the atom structure from the OAuth providers example applies to the payment domain — one abstract Provider, multiple concrete implementations.

Key characteristics of this system for documentation:

  • Idempotency — a repeat request must not create a second payment
  • Webhooks — the provider asynchronously reports status changes
  • Partial failures — the payment started, the provider crashed mid-process
  • Reconciliation — matching with the provider, detecting discrepancies

Domain model

specs/payments/domain.spec.md — v2.1
## § Domain Model Aggregate: Payment id: UUID orderId: UUID → Order amount: Money { value, currency } provider: "stripe" | "yukassa" | "sbp" status: pending | processing | succeeded | failed | refunded idempotencyKey: string ← unique per provider providerPaymentId: string? ← received from provider metadata: map ← provider-specific data Aggregate: PaymentMethod id: UUID userId: UUID → User provider: string token: string ← provider token, not raw card data type: "card" | "wallet" | "sbp" last4: string? Aggregate: Webhook id: UUID provider: string eventType: string payload: JSON processed: boolean paymentId: UUID? ← link established during processing ## § Domain Events PaymentInitiated { paymentId, orderId, amount, provider } PaymentSucceeded { paymentId, orderId, providerPaymentId } PaymentFailed { paymentId, orderId, reason, retryable } PaymentRefunded { paymentId, amount, reason } WebhookReceived { webhookId, provider, eventType } ReconciliationGap { paymentId, localStatus, providerStatus } ## § Invariants INV-1 Payment.amount does not change after creation INV-2 idempotencyKey is unique per provider — duplicate = 409 INV-3 succeeded → refunded — the only allowed backward transition INV-4 Webhook is processed exactly once (exactly-once)

Atom tree

📁 specs/payments/
├── _index.md
├── domain.spec.md v2.1
├── 📁 _draft/
│ └── PAY-REC-001_reconciliation-gap-resolution.spec.md OQ-1 open
├── 📁 _deprecated/
│ └── PAY-CHG-001_direct-card-charge.spec.md replaced by tokenization
├── 📁 charge/
│ ├── PAY-CHG-010_initiate-payment.spec.md
│ ├── PAY-CHG-011_duplicate-idempotency-key.spec.md
│ ├── PAY-CHG-012_provider-unavailable.spec.md
│ ├── PAY-CHG-013_insufficient-funds.spec.md
│ ├── PAY-CHG-020_stripe-charge.spec.md
│ ├── PAY-CHG-030_yukassa-charge.spec.md
│ └── PAY-CHG-040_sbp-charge.spec.md
├── 📁 webhook/
│ ├── PAY-WH-010_webhook-received.spec.md
│ ├── PAY-WH-011_duplicate-webhook.spec.md
│ ├── PAY-WH-012_unknown-payment-webhook.spec.md
│ └── PAY-WH-013_webhook-signature-invalid.spec.md
├── 📁 refund/
│ ├── PAY-REF-010_initiate-refund.spec.md
│ └── PAY-REF-011_refund-already-refunded.spec.md
└── 📁 reconciliation/
└── PAY-REC-010_daily-reconciliation.spec.md

Key Atom: Payment Initiation

PAY-CHG-010_initiate-payment.spec.md
--- id: PAY-CHG-010 type: use-case title: "Initiate payment through provider" parent: PAYMENTS children: - PAY-CHG-011_duplicate-idempotency-key - PAY-CHG-012_provider-unavailable - PAY-CHG-013_insufficient-funds emits: [PaymentInitiated, PaymentSucceeded, PaymentFailed] see-also: [PAY-WH-010] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent The system initiates a payment through the selected provider. Success or failure arrives either synchronously or via webhook. ## § Domain Rules DR-1 idempotencyKey = SHA256(orderId + amount + currency + provider) DR-2 A repeated request with the same idempotencyKey → 200 with the existing payment, not a new one DR-3 Provider is selected by currency: RUB → YooKassa/SBP, others → Stripe DR-4 Provider response timeout: 30 seconds DR-5 After timeout → status: failed, retryable: true, PaymentFailed is emitted ## § Acceptance Criteria Scenario: Successful payment via Stripe Given order ORDER-123 for 50 USD And Stripe provider is selected When the system initiates a payment Then a Payment { status: processing } is created And PaymentInitiated is emitted And Stripe returns paymentIntentId When webhook payment_intent.succeeded arrives Then Payment.status → succeeded And PaymentSucceeded is emitted ## § Platform: Web API contract: method: POST path: /v1/payments body: orderId: UUID amount: { value: number, currency: string } provider: string? # if not specified — selected automatically methodId: UUID? # saved PaymentMethod responses: 201: { paymentId, status: "processing", providerData } 200: { paymentId, status, ... } # idempotency hit 402: { code: INSUFFICIENT_FUNDS } 503: { code: PROVIDER_UNAVAILABLE, retryAfter }

Atom: Webhook Processing

PAY-WH-010_webhook-received.spec.md
## § Domain Rules DR-W-1 Webhook is verified via provider HMAC signature before processing DR-W-2 A webhook is processed exactly once (exactly-once via Webhook.processed) DR-W-3 A repeated webhook with the same eventId → 200, processing is not triggered DR-W-4 Unknown paymentId in webhook → Webhook is saved, status: orphaned DR-W-5 Processing is asynchronous — HTTP 200 is returned immediately, processing happens in a queue ## § Acceptance Criteria Scenario: Stripe reports a successful payment Given Payment { id: P-1, status: processing } When webhook { type: payment_intent.succeeded, paymentIntentId: ... } arrives Then HTTP 200 is returned immediately And asynchronously: Payment.status → succeeded And PaymentSucceeded is emitted Scenario: Duplicate webhook → PAY-WH-011 Scenario: Invalid signature → PAY-WH-013

Idempotency as a Domain Rule

Idempotency is not a technical detail but a business rule. It lives in domain rules of a use-case, not in the platform section. This allows a tester to verify it independently from the implementation.

PAY-CHG-011_duplicate-idempotency-key.spec.md
--- id: PAY-CHG-011 type: scenario parent: PAY-CHG-010 dr-covers: [DR-2] --- ## § Acceptance Criteria Scenario: Repeated request with the same idempotencyKey Given a Payment { idempotencyKey: "key-abc", status: processing } exists When a new request with the same orderId + amount + currency + provider arrives Then HTTP 200 (not 201) And the existing paymentId is returned And a new Payment is NOT created And the provider is NOT called again ## § Platform Tests // @spec PAY-CHG-011 it('200 idempotency — does not create a second payment', async () => { const first = await api.post('/v1/payments', payload) const second = await api.post('/v1/payments', payload) expect(second.status).toBe(200) expect(second.body.paymentId).toBe(first.body.paymentId) expect(await db.payments.count({ orderId })).toBe(1) })

Iteration 1 → 2 → 3: Adding Providers

IterationWhat we addDomain ChangesNew atoms
v1.0 — StripeBasic payment USD/EURdomain v1.0 — base model7 atoms
v2.0 — YooKassaRUB cards, two-stage confirmationdomain v2.0 — Capture added+5 atoms
v2.1 — SBPQR code, push notification, 24h timeoutdomain v2.1 — Additive, QR flow+4 atoms
Same pattern as with OAuth First provider — we build the model. Second — we refactor toward abstraction (Payment.provider). Third — a pure enum extension. The investment in abstraction at v2.0 makes v2.1 nearly free.

Mid-Sprint Fix: Changing Timeout

PAY-CHG-010a~param_provider-timeout.spec.md
--- id: PAY-CHG-010a type: amendment change: ParameterChange title: "Provider timeout: 30s → 10s" amends: PAY-CHG-010_initiate-payment parameter: name: provider_timeout_seconds was: 30 becomes: 10 requested-by: stakeholder: "DevOps / SRE" reason: "30s holds DB connection, cascade timeout during 23:00 traffic spike" conflict: status: sprint-locked locked-sprint: "Sprint-8" resolution: "Sprint-8 finishes with 30s. Amendment → Sprint-9, separate PR" ---

Overview: Blog Platform

Seems like a simple system — until you start digging into the details. Drafts, publishing, editing published content, versions, comment moderation, SEO metadata, tags — each of these features contains non-obvious domain decisions. This case shows how to grow from MVP to a full platform without rewriting atoms.

Key questions that will arise:

  • Draft and publication — one aggregate or two?
  • Editing published content — creates a new version or modifies the existing one?
  • A deleted post with comments — what happens to the comments?
  • Tag deleted — do posts with it remain?

Domain model (result of all iterations)

specs/blog/domain.spec.md — v3.0
## § Domain Model Aggregate: Post id: UUID authorId: UUID → User slug: string (unique) ← URL identifier status: draft | published | archived currentRevisionId: UUID → PostRevision tagIds: UUID[] publishedAt: timestamp? Aggregate: PostRevision ← added in v3.0 id: UUID postId: UUID → Post title: string body: string (markdown) seoMeta: { title?, description?, ogImage? } createdAt: timestamp createdBy: UUID → User Aggregate: Comment id: UUID postId: UUID → Post authorId: UUID → User parentId: UUID? → Comment ← for nesting body: string status: pending | approved | rejected Aggregate: Tag id: UUID slug: string (unique) name: string ## § Invariants INV-1 Post.slug does not change after publication (SEO) INV-2 Editing published content → creates a PostRevision, does not modify the current one INV-3 Deleting Post → Comment.status = archived (not deleted) INV-4 Tag can only be deleted if postIds is empty (no linked posts) ## § Decision Log D-001 Draft = Post { status: draft }, not a separate aggregate Rationale: one aggregate — one history, simpler status transition management D-002 Editing → new RevisionList, not Post.body mutation Rationale: change history, rollback capability, audit

Atom tree — final state

📁 specs/blog/
├── domain.spec.md v3.0
├── 📁 _deprecated/
│ └── BLG-PST-001_simple-publish.spec.md MVP, replaced by BLG-PST-010
├── 📁 posts/
│ ├── BLG-PST-010_create-draft.spec.md
│ ├── BLG-PST-011_publish-post.spec.md
│ ├── BLG-PST-012_edit-published-post.spec.md creates a revision
│ ├── BLG-PST-013_archive-post.spec.md
│ ├── BLG-PST-014_slug-already-taken.spec.md
│ └── BLG-PST-015_rollback-to-revision.spec.md
├── 📁 comments/
│ ├── BLG-CMT-010_submit-comment.spec.md
│ ├── BLG-CMT-011_moderate-comment.spec.md
│ └── BLG-CMT-012_comment-on-deleted-post.spec.md
├── 📁 tags/
│ ├── BLG-TAG-010_create-tag.spec.md
│ └── BLG-TAG-011_delete-tag-with-posts.spec.md
└── 📁 seo/
├── BLG-SEO-010_post-seo-metadata.spec.md
└── BLG-SEO-011_sitemap-generation.spec.md

Iteration 1 — MVP: Just Publish

In the MVP there are no drafts, no versions, no tags. Just write and publish.

Iteration 1 — domain v1.0, 3 atoms
domain.spec.md v1.0 Aggregate: Post id, authorId, title, body, slug, status: draft|published BLG-PST-001_simple-publish.spec.md ← this later becomes deprecated BLG-PST-002_duplicate-slug.spec.md BLG-PST-003_publish-success.spec.md Decision Log: D-001 No separate Draft aggregate — Post.status = draft is sufficient for MVP Rationale: YAGNI, we will add versioning when editing is needed

Iteration 2 — Comments

Adding comments. The first non-obvious question arises: what happens to comments when a post is deleted?

BLG-CMT-012_comment-on-deleted-post.spec.md
--- id: BLG-CMT-012 type: scenario parent: BLG-CMT-010 --- ## § Open Questions OQ-1 [resolved] Delete comments in cascade or archive them? Resolution: archive — losing user content is unacceptable Resolved-by: Product, 2024-03-10 ## § Acceptance Criteria Scenario: Attempt to comment on a deleted/archived post Given Post { status: archived } When a user submits a new comment Then HTTP 422 { code: POST_NOT_AVAILABLE } And existing comments are NOT deleted (status: archived) And a new Comment is NOT created ## § Decision Log D-003 Cascading deletion of comments upon post archival is prohibited Rationale: users spent time on content, deleting without consent is unethical Decided-by: Product + Legal, 2024-03-10

Iteration 3 — Tags and SEO

Tags — a simple feature with a non-obvious edge case: what if you delete a tag that has posts?

Important domain decision INV-4 states: Tag can only be deleted if there are no linked posts. This is not a technical constraint — it is a business decision. It must live in domain.spec.md, not in validation code.

Iteration 4 — Edit Versioning

This is a Breaking change — the PostRevision aggregate is added. The Post model changes.

domain v2.0domain v3.0
Post.bodystring — stores text directlyremoved — text in PostRevision
Post.titlestringremoved — in PostRevision
PostRevisionnonew aggregate
Editingmutates Postcreates PostRevision
ModelChange — a separate PR before the sprint starts BLG~MODEL-001~model_post-revision-aggregate.spec.md must be merged and verified before the developer starts implementing BLG-PST-012 and BLG-PST-015. Otherwise — a conflict mid-sprint.

System Roles and Their Overlaps

RoleCanCannot
Authorcreate, edit own posts, publishmoderate others' comments, delete tags
Editoreverything Author can + edit any postsdelete users, manage tags
Admineverything
Readerread, commentcreate posts

Each role boundary is a separate Domain Rule in the corresponding use-case. Not in middleware code, not in a README — in an atom.

Overview: AI Chatbot

The most unconventional case for Atomic Spec: the system behavior is non-deterministic. The same input can produce different output. How to write requirements and tests for such a system?

The answer: we document not the expected exact response, but the expected behavior — intent classification, presence of a tool call, response structure, boundaries of the acceptable. Acceptance Criteria work with response properties, not with its exact content.

Challenge: Non-determinism in Requirements

How NOT to write a scenario for AI
✗ BAD — tests exact text, fragile Scenario: User asks about weather Given message "what is the weather in Moscow?" Then response contains "Moscow" and temperature in °C And response text: "It is currently 15 degrees in Moscow" ← FRAGILE ✓ GOOD — tests behavior and structure Scenario: User asks about weather Given message contains intent WEATHER_QUERY with location=Moscow When the bot processes the message Then tool is called: get_weather { city: "Moscow" } And response contains a numeric temperature value And response does NOT contain a disclaimer about lack of real-time data And response tone: informative, not alarmist

Domain model

specs/chatbot/domain.spec.md — v2.0
## § Domain Model Aggregate: Conversation id: UUID userId: UUID → User status: active | archived | escalated model: string ← "claude-3", "gpt-4", etc. systemPromptId: UUID → SystemPrompt tokenCount: number ← sum of all messages Aggregate: Message id: UUID conversationId: UUID → Conversation role: "user" | "assistant" | "tool" content: string | ContentBlock[] toolCalls: ToolCall[]? intent: string? ← classified upon saving tokens: number Aggregate: SystemPrompt id: UUID version: semver ← prompts are versioned like code content: string scope: "global" | "persona" | "tool-specific" ## § Invariants INV-1 Conversation.tokenCount does not exceed model.contextWindow INV-2 When 80% of context is reached — a Summary is created and old messages are archived INV-3 Escalated conversation does not receive new AI responses — only human agent INV-4 Tool call without a result is not sent in the next LLM request ## § Domain Events MessageReceived { conversationId, messageId, intent } BotResponded { conversationId, messageId, toolCallsCount } ConversationEscalated { conversationId, reason, triggeredBy } ContextCompressed { conversationId, summaryCreated, messagesArchived }

Atom tree

📁 specs/chatbot/
├── domain.spec.md v2.0
├── 📁 _draft/
│ └── BOT-ESC-020_auto-escalation-rules.spec.md OQ-2 open
├── 📁 conversation/
│ ├── BOT-CNV-010_start-conversation.spec.md
│ ├── BOT-CNV-011_send-message.spec.md
│ ├── BOT-CNV-012_context-window-overflow.spec.md
│ └── BOT-CNV-013_archive-conversation.spec.md
├── 📁 tools/
│ ├── BOT-TOOL-010_weather-tool-call.spec.md
│ ├── BOT-TOOL-011_tool-call-failure.spec.md
│ └── BOT-TOOL-012_tool-call-timeout.spec.md
├── 📁 fallback/
│ ├── BOT-FLB-010_llm-unavailable.spec.md
│ ├── BOT-FLB-011_content-policy-violation.spec.md
│ └── BOT-FLB-012_rate-limit-exceeded.spec.md
└── 📁 escalation/
├── BOT-ESC-010_manual-escalation.spec.md
└── BOT-ESC-011_escalation-to-human.spec.md

Key Atom: Sending a Message

BOT-CNV-011_send-message.spec.md
## § Domain Rules DR-1 The user message is saved before sending to the LLM DR-2 The full history is passed if tokenCount < 80% contextWindow DR-3 When tokenCount > 80% — compression first (BOT-CNV-012), then request DR-4 The assistant response is saved regardless of tool calls presence DR-5 If LLM is unavailable — the user receives a fallback message (BOT-FLB-010) ## § Acceptance Criteria Scenario: Regular text response without tool calls Given an active conversation with 5 messages And tokenCount < 80% contextWindow When the user sends a text message Then LLM receives the full history + the new message And the assistant response is saved in Message { role: assistant } And BotResponded { toolCallsCount: 0 } is emitted And response contains non-empty text And response does NOT contain raw JSON or technical artifacts Scenario: Request requires a tool call Given user intent is classified as TOOL_REQUIRED When LLM returns tool_call { name, arguments } Then tool is called with the provided arguments And the result is added to history as Message { role: tool } And LLM is called again with the tool result And the final response to the user is text, not raw tool result

How to Test Non-deterministic Behavior

Three levels of testing an AI system — each answers its own question:

LevelWhat we testHow we capture it in an atom
StructuralCorrect call sequence: save → LLM → save responseStandard Gherkin with Then-steps for action order
BehavioralPresence/absence of tool call, intent type, response structureThen: response contains/does not contain X, tool was/was not called
QualitativeTone, accuracy, alignment with system prompt§ Quality Criteria — separate section, not Gherkin
§ Quality Criteria — special section for AI atoms
## § Quality Criteria # This section describes expectations that cannot be expressed in a deterministic test # Evaluated via LLM-as-judge or manual expert review on a sample QC-1 The response to a weather question should be informative, not alarmist QC-2 The bot should not apologize unnecessarily (no more than once per conversation) QC-3 When data is missing — honestly state this, do not hallucinate QC-4 Response length matches question complexity (short question → short answer) # Evaluation method evaluation-method: llm-judge judge-prompt: prompts/quality-judge-v2.md pass-threshold: 0.85 # 85% of messages in the sample must pass evaluation sample-size: 100

Fallback Scenarios — Critically Important

BOT-FLB-010_llm-unavailable.spec.md
## § Domain Rules DR-F-1 LLM unavailable → user receives a human-friendly message within 3s DR-F-2 The user message is saved even when LLM is unavailable DR-F-3 After LLM recovery — the saved message can be answered manually DR-F-4 If LLM is unavailable > 5 minutes → Conversation.status = escalated ## § Acceptance Criteria Scenario: LLM returned 503 Given LLM API returns 503 When the user sends a message Then the message is saved in the database And the user receives a fallback response within 3 seconds And the fallback response does not reveal technical error details And the fallback response contains information that the request was saved

Evolution: From Simple Chatbot to Agent

IterationWhat we addDomain Changes
v1.0 — Simple Q&AQ&A without memoryConversation without history
v1.1 — HistoryPrevious message contextAdditive: Message history
v2.0 — Tool callsWeather lookup, knowledge basesBreaking: Message.toolCalls
v2.1 — EscalationHandoff to a live operatorAdditive: escalated status
v3.0 — AgentMulti-step tasks, planningBreaking: Task aggregate

Overview: Online Store

The most complex case in terms of domain count and their interaction. Online Store consists of several independent Bounded Contexts that interact through events, not direct calls. Atomic Spec here solves the most important problem: who owns which requirement and how a change in one domain affects another.

Bounded Contexts — Domain Structure

specs/ — top-level structure
📁 specs/ ├── 📁 catalog/ ← product catalog, prices, stock ├── 📁 cart/ ← cart, temporary state ├── 📁 orders/ ← orders, their lifecycle ├── 📁 payments/ ← (see Payment Integrator case) ├── 📁 delivery/ ← logistics, delivery statuses ├── 📁 notifications/ ← email/sms/push on events └── 📁 users/ ← accounts, addresses, preferences # Each domain is a separate team or component # Interaction — only through domain events # orders/ does not call payments/ directly # orders/ emits OrderConfirmed → payments/ reacts

Domain: Catalog

specs/catalog/domain.spec.md
Aggregate: Product id: UUID sku: string (unique) status: active | discontinued | out-of-stock price: Money stock: number ← reserved when added to cart Aggregate: PriceHistory ← price change history for audit and "was/now" display ## § Invariants INV-1 Product.stock does not go negative — reservation is prohibited when stock=0 INV-2 Price is locked in Cart.item at the time of addition — does not change when Product.price changes INV-3 discontinued → cannot be added to cart, but existing orders remain valid ## § Events emitted (consumed by other domains) ProductOutOfStock → cart/ marks item as unavailable PriceChanged → notifications/ notifies wishlist users StockReserved { productId, quantity, cartId } StockReleased { productId, quantity, reason: "cart_expired|order_cancelled" }

Domain: Cart

specs/cart/domain.spec.md + key atoms
Aggregate: Cart id: UUID userId: UUID? → User ← null for anonymous users items: CartItem[] expiresAt: timestamp ← cart lives for 30 days status: active | checked-out | expired Entity: CartItem productId: UUID → Product quantity: number priceSnapshot: Money ← price at the time of addition (INV-2) status: available | unavailable | price-changed ## § Key use-cases CART-ADD-010 Add product to cart CART-ADD-011 Product went out of stock at the moment of adding ← race condition CART-ADD-012 Product already in cart — increase quantity CART-CHK-010 Begin checkout CART-CHK-011 Product became unavailable during checkout ← important edge case CART-MRG-010 Merge anonymous cart after login
Critical edge case: product went out of stock during checkout CART-CHK-011 — the user started checkout, and while they were entering the address, another buyer purchased the last item. Without an explicit atom, this case is discovered in production. Atomic Spec forces you to capture it before implementation.

Domain: Order

specs/orders/domain.spec.md
Aggregate: Order id: UUID userId: UUID → User items: OrderItem[] ← snapshot from Cart, not references status: pending → confirmed → paid → shipped → delivered | cancelled totalAmount: Money deliveryAddress: Address ← snapshot, not a reference to User.address ## § Invariants INV-1 Order.items = snapshot from Cart — changing Product does not affect the order INV-2 Order.deliveryAddress = snapshot — changing User.address has no effect INV-3 Cancelling an order in shipped status is only possible via return (Return flow) INV-4 cancelled → StockReleased is emitted for each OrderItem ## § Events OrderCreated → payments/ initiates payment OrderConfirmed → delivery/ creates a delivery task → notifications/ sends confirmation OrderCancelled → payments/ initiates refund → catalog/ releases the reservation

Saga: Full Order Journey

A Saga is a System-level atom that describes cross-service interaction. It is not a use-case of a single domain — it is an orchestration of several.

specs/system/SHOP-SAGA-001_order-checkout-flow.spec.md
--- id: SHOP-SAGA-001 type: saga ← new type for cross-service flows title: "Full order checkout and fulfillment cycle" spans: [cart, orders, payments, catalog, delivery, notifications] --- ## § Happy Path Step 1 cart/ User checks out the cart → CartCheckedOut { cartId, items, userId } Step 2 orders/ Order { status: pending } is created → OrderCreated { orderId, amount } Step 3 payments/ Payment is initiated → PaymentSucceeded { paymentId, orderId } Step 4 orders/ Order.status → confirmed → OrderConfirmed { orderId } Step 5 delivery/ A delivery task is created → DeliveryScheduled { deliveryId, orderId } Step 6 notifications/ Email + push to the user ## § Compensation (on failure) If PaymentFailed: orders/ → Order.status = cancelled catalog/ → StockReleased for each item cart/ → Cart.status = active (can try again) If DeliveryFailed (no courier): delivery/ → OrderConfirmed remains, retry in 1h orders/ → status does not change, only delivery retry ## § See Also PAY-CHG-010 payment initiation ORD-CRT-010 order creation DLV-SCH-010 delivery scheduling

Evolution from MVP to Full System

IterationWhat we buildDomainsKey decisions
MVP v1.0Catalog + Order + Payment (Stripe)catalog, orders, paymentsNo cart — direct transition to order
v1.1Cart+cartPrice snapshot in CartItem — INV-2
v2.0Delivery+deliveryBreaking: OrderConfirmed now requires delivery
v2.1Notifications+notificationsAdditive: subscribing to events from other domains
v3.0Returnsorders expandedBreaking: new Return aggregate, compensation Saga
The main lesson of this case The Snapshot pattern (Order.items, Order.deliveryAddress, CartItem.priceSnapshot) — these are domain invariants, not technical details. They must be explicitly captured in domain.spec.md. Without this, a developer uses references instead of snapshots, and six months later prices in old orders start changing.

Why Atomic Spec Is Ideal for Working with AI

AI agents (Claude Code, Cursor, Copilot Workspace and others) work effectively when context is structured, unambiguous and predictable. Atomic Spec solves three key problems of agent-driven development:

Agent ProblemHow Atomic Spec Solves It
"How do I know exactly what to implement?"The atom contains § Platform Contract — a precise API contract with no room for interpretation
"Does my change break anything?"emits/consumes explicitly show dependencies between domains
"Did I understand the business rule correctly?"§ Domain Rules — formalized DR-N, not prose
"Are the tests already written?"§ Platform Tests — ready-made test cases, the agent just runs them
"How do I update the task status?"Frontmatter with explicit implementation.status fields
Key Principle An agent must never guess at requirements. If information is missing from the atom, the agent creates an Open Question rather than making a decision on its own. Atomic Spec makes the boundary of knowledge explicit.

How an Agent Reads Atoms

The agent works with atoms following a strict protocol. The reading order follows the atom sections — from abstract to concrete:

agent atom reading protocol
# Step 1 — read frontmatter # → check status (draft? → STOP, do not implement) # → check open-questions (any open? → STOP, create OQ-issue) # → read parent, children, see-also — understand context # → read emits/consumes — understand dependencies # Step 2 — read § Domain Rules # → each DR-N is a constraint that MUST NOT be violated # → DR with [updated] tag — read new value, not old one # Step 3 — read § Acceptance Criteria # → Gherkin scenarios = behavior specification # → references [→ ATOM-ID] → read child atoms # Step 4 — read § Platform: {target} # → select target platform (web-api / mobile / ...) # → contract is the source of truth for method signatures # Step 5 — read § Platform Tests # → ready-made test cases — copy and adapt # → @spec comment is required in every test # Step 6 — update frontmatter # → implementation.status: none → in-progress # → commit separately

Context the Agent Receives Before a Task

Before the agent starts implementation, it receives a minimal sufficient context. Too much — the agent loses focus; too little — it starts guessing.

minimal context for the agent
# 1. Target atom cat specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md # 2. Parent domain cat specs/auth/domain.spec.md # 3. Child atoms (scenarios) cat specs/auth/registration/AUTH-REG-021_google-provider-error.spec.md # 4. Related atoms (see-also) cat specs/auth/identity/AUTH-MERGE-001_oauth-phone-account-linking.spec.md # 5. Check for amendments find specs/ -name "AUTH-REG-020*~*" ! -path "*/_deprecated/*" # 6. Atom change history git log --follow specs/auth/registration/AUTH-REG-020.spec.md
Minimal Context Rule The agent needs: target atom + domain.spec.md + all children + all see-also + amendments. Not needed: deprecated atoms, atoms from other domains without explicit links, git history of other files.

Agent Creates a New Atom

When the agent receives a task "add feature X" without an existing atom, it must first create the atom rather than write code. The order is strict:

agent algorithm for creating an atom
# 1. Determine position in the hierarchy # → which domain? read domain.spec.md # → which type (use-case / scenario)? # → which parent? # → does a similar atom already exist? find specs/ -name "*.spec.md" | xargs grep -l "google oauth" -i # 2. Generate ID # → domain + type + next free number (step 10) # → verify ID is not taken ls specs/auth/registration/ | grep AUTH-REG | sort # 3. Create file in _draft/ # → ALWAYS start in _draft/ — not in root # → fill frontmatter completely # → record all unknowns as OQ-N # 4. Fill sections sequentially # § Intent → § Domain Rules → § Acceptance Criteria # → DO NOT write code until atom is ready # 5. Commit atom separately from code git add specs/auth/registration/_draft/AUTH-REG-020.spec.md git commit -m "[AUTH-REG-020][draft] Google OAuth registration spec" # 6. If OQ are open → STOP, escalate to human # 7. If no OQ → implement

Agent Discovered a Requirement Has Changed

During implementation the agent may discover that a requirement in the atom contradicts another atom, or that system behavior implies an undocumented rule. The correct algorithm:

agent algorithm for requirement conflicts
# Situation: agent is working on AUTH-REG-020, discovered that # domain.spec.md does not describe what to do with a phone-user # WRONG — guess on your own: # // Probably need to create a new account # user = createUser(googleId) # CORRECT — record the uncertainty: # 1. Add OQ to the atom open-questions: - id: OQ-1 question: "If the email from Google matches a phone account — create a new account or offer merge?" status: open raised-by: "AI agent / AUTH-REG-020 implementation" raised-at: "2024-06-10" # 2. Move atom to _draft/ git mv specs/auth/registration/AUTH-REG-020.spec.md \ specs/auth/registration/_draft/AUTH-REG-020.spec.md # 3. Commit and escalate to human git commit -m "[AUTH-REG-020] OQ-1: email conflict behaviour undefined Blocked: needs product decision before implementation can continue" # 4. DO NOT write code for this logic branch until OQ is closed

Agent on Code Review

The agent can automatically verify code compliance with atoms. This is one of the most valuable scenarios: no need to wait for a human for basic checks.

agent code review checklist per atom
# For each changed file in PR — find the related atom git diff --name-only HEAD~1 | xargs grep -l "@spec" 2>/dev/null # Checks the agent performs automatically: # ✓ 1. All DR-N implemented? # → read § Domain Rules, verify each DR in code # ✓ 2. Platform contract matches? # → HTTP statuses, body fields, error codes — everything matches § Platform? # ✓ 3. All events emitted? # → emits: [UserRegistered] → does code have emit('UserRegistered', ...)? # ✓ 4. @spec comment in tests? grep -r "@spec" src/__tests__/ | grep -v "AUTH-REG-020" # → test without @spec = test without traceability = violation # ✓ 5. implementation.status updated? grep "implementation.status: in-progress" specs/auth/registration/AUTH-REG-020.spec.md # → must be in-progress or done # ✓ 6. Any unclosed amendments? find specs/ -name "AUTH-REG-020*~*" | xargs grep -l "amendment-status: pending"

Useful Agent Queries to the Tree

agent grep arsenal
# What should I implement (active, impl: none) grep -rl "implementation.status: none" specs/ \ ! -path "*/_draft/*" ! -path "*/_deprecated/*" # Are there amendments to my task find specs/ -name "AUTH-REG-020*~*" ! -path "*/_deprecated/*" # Who consumes the event I am changing grep -r "consumes.*UserRegistered" specs/ -l # All open OQ — what blocks work grep -r "status: open" specs/ --include="*.spec.md" -B2 | grep "question:" # All atoms I must update after ModelChange grep -r "affects-atoms" specs/ -A 10 | grep "AUTH-" # What blocks release right now grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" # Any name conflicts (duplicate slug) find specs/ -name "*.spec.md" | sed 's/.*\///' | sort | uniq -d # All atoms without @spec in tests — traceability gap diff \ <(find specs/ -name "*.spec.md" ! -path "*/_*" -exec grep -h "^id:" {} \; | awk '{print $2}' | sort) \ <(grep -r "@spec" src/__tests__/ | grep -oP 'AUTH-\w+-\d+' | sort -u)

Rules the Agent Never Violates

absolute agent rules
FORBIDDEN Implement an atom with draft status or open OQ FORBIDDEN Delete atoms — only deprecate FORBIDDEN Create a file in the folder root bypassing _draft/ FORBIDDEN Change Domain Rules without creating a RuleChange amendment FORBIDDEN Expand atom scope — create a new one instead FORBIDDEN Make architectural decisions independently — use OQ only FORBIDDEN Write code if the platform contract is ambiguous MANDATORY Read domain.spec.md before any implementation in the domain MANDATORY Check for amendments (~param, ~rule, ~flow) to the atom MANDATORY Update implementation.status at start and finish MANDATORY Add @spec comment to every test MANDATORY Verify consumes/emits when changing events MANDATORY Create OQ for any uncertainty and stop

Antipatterns in Agent-Driven Development

AntipatternSymptomCorrect Approach
Spec-blind codingAgent writes code without reading the atomProtocol: read all related atoms first
Silent assumptionAgent fills requirement gaps with its own logicCreate OQ, stop, escalate to human
Draft skipAgent creates atom directly in folder rootAlways via _draft/, moving is a separate PR
Mega commitAtom + code + tests in one commitThree separate commits: spec / implementation / tests
Status driftAgent implemented but did not update implementation.statusStatus Update is part of definition of done
Context overloadAgent reads all of specs/ before a taskOnly target atom + domain + children + see-also
Rule inventionAgent adds a DR that is not in the atomOnly DR explicitly described by the Analyst Agent. Everything else is OQ

Agent System Prompt

A compact prompt for an AI agent working with an Atomic Spec repository. Contains everything necessary — nothing superfluous.

AGENT_SYSTEM_PROMPT.md — copy in full
# Atomic Spec Agent — System Prompt You are working with a repository that uses the Atomic Spec methodology. Each requirement is a .spec.md file. You read atoms before implementation. You never guess at requirements — only record uncertainty. ## Atom Structure Frontmatter: id, type, parent, children, emits, consumes, open-questions, implementation.status, verification.status, see-also § Intent — why this feature exists (read first) § Domain Rules — DR-N: business invariants (must not violate) § Acceptance Criteria — Gherkin scenarios (behavior specification) § Domain Model Touch — aggregates and events (source of types) § Platform: Web API — endpoint/body/responses contract § Platform Tests — ready-made test cases (adapt, do not rewrite) § Open Questions — OQ-N: unresolved questions § Decision Log — D-N: decisions made with alternatives ## File Tree _draft/ — has open OQ → CANNOT implement _deprecated/ — history → read only, do not touch folder root → verified, active → implement XXXa~param_ — ParameterChange amendment (single value) XXXb~rule_ — RuleChange amendment (new/changed DR) XXXc~flow_ — FlowChange amendment (scenario step) ~model_ — ModelChange (aggregate/field/event) ## Task Implementation Algorithm 1. Find atom: grep -r "id: ATOM-ID" specs/ 2. Check status — if _draft/ or open-questions.status=open → STOP 3. Read: atom + domain.spec.md + children + see-also 4. Check amendments: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" 5. Update implementation.status: none → in-progress (separate commit) 6. Implement following § Platform Contract. Do not deviate from the contract. 7. Write tests. Each test: // @spec ATOM-ID 8. Update implementation.status: in-progress → done 9. Three separate commits: [ATOM-ID][spec] / [ATOM-ID][impl] / [ATOM-ID][tests] ## Creating a New Atom (if none exists) 1. Verify atom does not exist: find specs/ -name "*slug*" 2. Determine: domain + type + ID (next x10 after last) 3. Create in _draft/ with full frontmatter 4. All unknowns → OQ-N with status: open 5. If OQ exist → commit atom, STOP, escalate to human 6. If no OQ → PR moving to root (stakeholder verification) ## When Uncertainty Is Discovered DO NOT: make a decision independently DO NOT: skip and continue DO: add OQ to atom with raised-by: "agent" and raised-at: date DO: move atom to _draft/ if it was in root DO: create a commit explaining the block DO: tell the human exactly what is undefined ## Change Types — when to create what Constant value changed → ~param amendment DR changed/added → ~rule amendment Scenario step changed → ~flow amendment Domain model changed → ~model (ModelChange, needs human) Domain boundaries changed → .rfc (BoundaryChange, human only) Only API contract changed → update § Platform, do not create amendment ## Pre-commit Checks □ All DR-N from § Domain Rules implemented? □ HTTP statuses/fields match § Platform Contract? □ All events from emits: emitted in code? □ Every test has // @spec ATOM-ID? □ implementation.status updated? □ No pending amendments to this atom? □ consumes events — their sources have not changed in this PR? ## Absolute Prohibitions ✗ Implement an atom from _draft/ or with open OQ ✗ Delete .spec.md files (only deprecate) ✗ Create file in root bypassing _draft/ ✗ Change Domain Rules without ~rule amendment ✗ Expand scope of existing atom ✗ Make decisions for the Analyst Agent or product ✗ Mix spec + impl + tests in one commit ## Commit message format [ATOM-ID][type] brief description type: spec | impl | tests | amend | model | fix If amendment: [ATOM-IDa][amend] ParameterChange: timeout 20s→60s If OQ: [ATOM-ID][blocked] OQ-1: email conflict behavior
How to Use the Prompt Copy the content in full into the agent system prompt (Claude Code, Cursor, Copilot Workspace). Supplement with your project context: technology stack, domains, team conventions. The prompt is intentionally written in machine-readable format — no unnecessary words, with explicit sections.

What to Add for Your Project

Add your project specifics to the base prompt as one block:

prompt supplement — project specifics
## Project Context Stack: Node.js / TypeScript / PostgreSQL / Redis Tests: Jest, files in src/__tests__/ Domains: AUTH, PAYMENT, ORDER, CATALOG, DELIVERY Teams: Team A (catalog), Team B (cart+order), Team C (delivery) Platforms: web-api (primary), mobile (iOS/Android), admin-panel Events bus: RabbitMQ, exchange: domain.events ## Current Sprint Sprint-14. Goal: Google OAuth + SBP payments. My tasks: AUTH-REG-020, AUTH-LOG-020, PAY-INI-030 Blockers: AUTH-MERGE-001 in _draft/ (OQ-1 open)

Atomic Spec + AI Agents

AI agents are a new type of team member. They write code, create tests, suggest refactoring. But they have a fundamental problem: no memory between sessions and no context about team decisions.

Atomic Spec solves this elegantly: the repository of .spec.md files is the agent's external memory. Everything needed about history, decisions, and current state is in files that the agent can read at any time.

Key Idea The agent does not store context in memory — it reads it from .spec.md files before each task. Spec files are the interface between humans and agents.

Problems When Working with Agents Without a System

ProblemWhat HappensHow Often
Agent does not know "why"Rewrites logic that seems illogical but was accepted for business reasons recorded in Decision LogEvery session
Agent does not know current statusWrites code for a requirement that is already deprecated or still in draftOften
Agent does not see dependenciesChanges domain model not knowing it breaks other use-casesDuring refactoring
Agent invents behaviorWith incomplete context, hallucinates "reasonable" behavior instead of following the specOn edge cases
No change versioningAgent changes spec and code together without explicit classification of what changedConstantly

How Atomic Spec Solves Agent Problems

what the agent does at the start of each task
# 1. Read domain model — understand ubiquitous language cat specs/payment/domain.spec.md # 2. Find the task atom — read Intent, DR, Acceptance Criteria cat specs/payment/initiation/PAY-INI-030_sbp-qr-payment.spec.md # 3. Check status — is it draft, are there pending amendments grep -r "amends: PAY-INI-030" specs/ -l grep "status:" specs/payment/initiation/PAY-INI-030.spec.md | head -5 # 4. Understand dependencies — what emits and what consumes grep -r "consumes.*PaymentInitiated\|see-also.*PAY-INI-030" specs/ -l # 5. Check Decision Log — why it is this way grep -A 5 "Decision Log" specs/payment/initiation/PAY-INI-030.spec.md

Agent Role in the Team

The agent operates in one of four roles depending on the task. The role determines which files to read and what to create.

Agent RoleTaskReadsCreates / Modifies
ImplementerImplement feature per spec§ Platform, § Domain RulesCode, updates implementation.status
Spec WriterCreate / supplement atomsdomain.spec.md, neighboring atomsNew .spec.md in _draft/
Test WriterWrite tests§ Acceptance Criteria, § Platform TestsTest files, updates verification.status
ReviewerReview PR with spec changesEntire affected subdomainComments, OQ in atoms when problems found
Important Limitation The agent does not verify atoms — that is the role of human stakeholders. The agent can create an atom in _draft/ and propose closing OQ, but moving the file to root (verification) is done only by a human via PR.

Workflows with the Agent

Workflow 1 — Agent Implements a Feature

agent implements PAY-INI-030
# Human assigns task: "Implement PAY-INI-030_sbp-qr-payment" # Agent performs (in order): 1. Reads domain.spec.md → understands Money, Payment, ProviderSession 2. Reads PAY-INI-030.spec.md → Intent, DR-1..DR-5, Platform contract 3. Checks for pending amendments: grep "amends: PAY-INI-030" specs/ 4. Reads see-also atoms: PAY-REF-012 (draft — refund not implemented) 5. Writes code strictly per § Platform: Web API contract 6. Writes tests from § Platform Tests section 7. Updates implementation.status: in-progress in PAY-INI-030.spec.md 8. Creates PR with commit message per convention: [PAY-INI-030][Additive] feat: SBP QR payment # If agent finds a contradiction in the spec — does NOT guess: WRONG: "I decided the timeout should be 5 minutes, it makes more sense" CORRECT: add OQ to atom: "OQ-2: DR-2 says 15 min, NSPK API returns 10 min. Clarify?"

Workflow 2 — Agent Writes Spec from Description

agent creates a new atom
# Human describes feature in free form: "Need the ability to do partial refund via SBP. Refund specific items from the order, not the full amount." # Agent performs: 1. Reads domain.spec.md → sees Refund aggregate, current fields 2. Reads PAY-REF-011_partial-refund.spec.md → how partial refund works for cards 3. Reads PAY-INI-030_sbp-qr-payment.spec.md → DR-5: "SBP does not support partial capture" # Agent creates atom with EXPLICIT open questions: specs/payment/refund/_draft/PAY-REF-012_refund-after-sbp.spec.md open-questions: OQ-1: "PAY-INI-030 DR-5 prohibits partial capture. Does SBP support partial refund via a separate API? Requires clarification in NSPK documentation." OQ-2: "If SBP does not support it — offer full refund only or block UI?" # File remains in _draft/ — agent does NOT move it to root # PR is created only with the _draft/ file for Analyst Agent review

Workflow 3 — Agent Discovered a Requirement Change

agent creates an amendment
# Situation: agent is working on a task and discovers that # the actual provider API behavior differs from the spec # Step 1: determine change type "DR-2 says QR lives 15 minutes. NSPK API returns expires_in=600 (10 min)" # → this is a ParameterChange # Step 2: check atom status grep "implementation.status" specs/payment/initiation/PAY-INI-030.spec.md # → in-progress — atom is being developed # Step 3: create amendment, DO NOT change the main atom touch specs/payment/initiation/PAY-INI-030a~param_sbp-qr-ttl.spec.md # fill in: parameter.was=15, parameter.becomes=10 # conflict.status: sprint-locked (if already in sprint) # Step 4: notify in PR description "⚠️ Discrepancy found between spec and provider reality. PAY-INI-030a~param created. Requires Analyst Agent decision."

Agent Rules — Complete Set

rules — violating any is critical
── ALWAYS DO ────────────────────────────────────────────────────────── ✓ READ domain.spec.md before any task in the domain ✓ READ atom fully: Intent → DR → Acceptance Criteria → Platform ✓ CHECK for pending amendments: grep "amends: ATOM-ID" specs/ ✓ FOLLOW § Platform contract literally — endpoint, error codes, fields ✓ CREATE atoms only in _draft/ — verification is human-only ✓ RECORD contradictions as OQ — do not guess independently ✓ UPDATE implementation.status at start and end of work ✓ WRITE commit message per convention: [ID][ChangeType] description ✓ CREATE amendment when discrepancy between spec and reality is found ✓ CHECK dependencies via emits/consumes before changing domain ── NEVER DO ─────────────────────────────────────────────────────────── ✗ DO NOT DELETE atoms — only deprecate ✗ DO NOT MOVE files from _draft/ to root independently ✗ DO NOT MODIFY atoms in _deprecated/ ✗ DO NOT GUESS behavior if the spec has no answer — create OQ ✗ DO NOT EXPAND atom scope — create a new one instead ✗ DO NOT CHANGE existing DR directly — only via amendment ✗ DO NOT START implementing an atom from _draft/ without explicit permission ✗ DO NOT IGNORE pending amendments to the atom being implemented

Agent System Prompt

A ready-made prompt for insertion into system prompt or .cursorrules / AGENTS.md. Contains all methodology nuances in compact form.

AGENTS.md / .cursorrules / system prompt
# Atomic Spec — Instructions for an AI Agent ## Role and Context You are working in a repository using the Atomic Spec methodology. All requirements are stored in `specs/` as `.spec.md` files. Files are the single source of truth about the system and its history. ## Before Each Task — Must Read 1. `specs/[domain]/domain.spec.md` — domain model, aggregates, events, Decision Log 2. Target atom fully: `§ Intent` → `§ Domain Rules` → `§ Acceptance Criteria` → `§ Platform` 3. Check pending amendments: `grep -r "amends: [ATOM-ID]" specs/ -l` 4. Check dependencies: fields `see-also`, `emits`, `consumes` in frontmatter ## File Structure ``` specs/[domain]/ domain.spec.md — domain model (read first) _index.md — directory dashboard _draft/ — open OQ, cannot implement _deprecated/ — history, never modify DOMAIN-TYPE-NNN_slug.spec.md — verified active atom DOMAIN-TYPE-NNNa~param_slug.spec.md — amendment ``` ## Naming - Atom: `DOMAIN-TYPE-NNN_action-noun.spec.md` (AUTH-REG-010_phone-otp-registration) - Amendment: `BASE-IDx~type_slug.spec.md` where type: param | rule | flow | model - Creating new — numbering step 10 (010, 020, 030), reserve for variants ## Frontmatter — Key Fields ```yaml id, type, title, parent, children # structure emits, consumes, see-also # dependencies open-questions: # reason to be in _draft/ - id: OQ-N, question, status: open|resolved implementation.status: none|in-progress|done verification.status: none|in-progress|passed|failed verification.blocks-release: true|false ``` ## Change Types — Must Classify | Type | When | What to Do | |-----|-------|-----------| | ParameterChange | constant value | amendment ~param | | RuleChange | add/change DR | amendment ~rule | | FlowChange | change scenario | amendment ~flow | | ModelChange | aggregate/field/event | ~model file at domain level, blocks sprint | | BoundaryChange | transfer between domains | .rfc.md, requires RFC and voting | | PlatformChange | API contract only | edit § Platform section | ## Rules for Working with the Spec ### Feature Implementation 1. Read the domain's domain.spec.md 2. Read the target atom fully 3. Check amendments: `grep "amends: [ID]" specs/ -l` 4. Follow `§ Platform` contract literally — endpoint, fields, error codes 5. Write tests from `§ Acceptance Criteria` and `§ Platform Tests` 6. Update `implementation.status: in-progress` → `done` 7. Commit: `[ATOM-ID][ChangeType] brief description` + Requested-by, Reason, Affects ### Creating a New Atom 1. Create ONLY in `_draft/` — verification is human-only 2. All unknowns → `open-questions` with `status: open` 3. Do not move from `_draft/` to root independently 4. PR description: list of OQ requiring Analyst Agent answer ### Discovered Discrepancy Between Spec and Reality 1. Determine change type (see table above) 2. Check atom status (in-progress or done?) 3. Create amendment file alongside: `BASE-IDa~type_slug.spec.md` 4. Fill in `conflict.status`: `sprint-locked` | `pulled` | `merged` 5. DO NOT change the main atom directly 6. Note in PR description: "⚠️ Amendment created, requires Analyst Agent decision" ### Domain Model Change (ModelChange) 1. Create `DOMAIN~MODEL-NNN~model_slug.spec.md` in domain root 2. Fill in `affects-atoms` — all atoms affected by the change 3. Add `§ Model Delta` with aggregate diff 4. Fill in `§ Decision Log` — decision is mandatory 5. Do not start implementation until model-change is merged into main ## Categorically Forbidden - Delete `.spec.md` files (only deprecate) - Move files from `_draft/` to root - Modify files in `_deprecated/` - Guess at behavior without recording OQ - Expand scope of an existing atom - Change DR directly in the main atom (only via amendment) - Start implementing atoms from `_draft/` - Ignore pending amendments to the atom being implemented - Verify atoms (approve PR) — that is the role of humans ## Commit Convention ``` [ATOM-ID][ChangeType] action: description Requested-by: Name / Role Reason: brief reason Breaking: true|false Affects: ATOM-ID-1, ATOM-ID-2 ``` ChangeType: Additive | Breaking | ParameterChange | RuleChange | ModelChange | PlatformChange ## When to Create OQ Instead of Deciding - Spec contradicts actual API/system behavior - Two DR conflict with each other - Edge case not covered in Acceptance Criteria - Behavior depends on a business decision, not a technical one - Unclear if feature should be a separate atom or part of an existing one ## Answering the Question "What Should I Do" If the task is not tied to a specific atom: 1. Find the appropriate domain: `ls specs/` 2. Find the closest atom: `grep -r "title" specs/[domain]/ --include="*.spec.md"` 3. If no atom exists — create in `_draft/` and report that verification is needed 4. If atom is in `_draft/` — report that OQ must be closed before implementation

Example Dialogues with the Agent

✅ Correct: agent found a problem and created OQ

Dialogue Human: "Implement partial refund for SBP"

Agent: "Read PAY-INI-030. DR-5 explicitly says: 'SBP does not support partial capture — full amount only'. Partial refund requires a separate NSPK API — I do not know if it supports this.

Created PAY-REF-012_refund-after-sbp.spec.md in _draft/ with two open questions:
OQ-1: Does the NSPK API support partial refund via a separate method?
OQ-2: If not — offer full refund only or block UI?

I will start implementation after the Analyst Agent closes OQ-1 and OQ-2."

✅ Correct: agent updated status and created amendment

Dialogue Human: "Implement PAY-INI-030, NSPK API is already up in staging"

Agent: "Implementing. During the process found discrepancy: DR-2 says TTL=15 min, NSPK returns expires_in=600 (10 min). Code written for 10 min as actual behavior.

Created amendment PAY-INI-030a~param_sbp-qr-ttl.spec.md: was=15, becomes=10, conflict.status=sprint-locked.

PR ready. Noted in description: ⚠️ Amendment requires Analyst Agent decision — either update DR-2 or confirm 15 min was a typo."

❌ Wrong: agent guessed at behavior

Antipattern Human: "Implement partial refund for SBP"

Agent (WRONG): "Done! I decided since SBP is a bank transfer, refund works as a regular transfer in reverse. Created POST /v1/refunds/sbp with items[] field. If provider does not support it — return PROVIDER_NOT_SUPPORTED error."

Problem: The agent created an API contract from thin air, ignored DR-5, did not create OQ. Now the code has an endpoint that does not exist in the spec and may contradict the actual NSPK API.

Antipatterns When Agent Works with Spec

AntipatternHow It LooksCorrect Approach
Silent expansionAgent adds fields to API response that are not in the spec "for convenience"Create OQ or PlatformChange amendment
Self-verificationAgent creates atom and moves it from _draft/ to root itselfOnly PR + human approval = verification
Silent deprecationAgent sees atom is outdated and deletes it "to clean up"Move to _deprecated/ with reason
Scope creepAgent adds functionality to atom because "it makes sense here"New functionality = new atom
DR overrideAgent changes business rule in atom because "it is better technically"Create RuleChange amendment with justification
Draft bypassAgent writes code immediately for a feature still in _draft/First close OQ → verification → then code

Atomic Spec as an Operating System for AI Agents

Atomic Spec is not just documentation. For AI agents it is a machine-readable contract: what to do, what not to do, what is unknown. Agents do not interpret requirements — they execute them. This radically reduces the risk of hallucinations and deviations from team intent.

Key property: the atom explicitly separates knowledge from uncertainty. If information is not in the atom — the agent does not assume, it creates an Open Question and stops. This is the only safe way for agents to work with requirements.

Without Atomic SpecWith Atomic Spec
Agent receives task text in free form → interprets → does something similar to what is neededAgent reads atom → follows § Domain Rules → implements § Platform Contract → runs § Platform Tests
Unclear requirement → agent guessesUnclear requirement → agent creates OQ → stop
Requirement change → agent does not knowRequirement change → amendment file → agent sees when reading
Boundary between domains → agent guessesemits/consumes in frontmatter → boundary is explicit

Three Agents — Three Specializations

The multi-agent system architecture based on Atomic Spec is built from three specialized agents coordinated by the orchestrator:

Analyst Agent

Works with specification. Creates and updates atoms. Closes Open Questions. Classifies changes.

Input:
Feature request / change text / stakeholder decision
Output:
Ready .spec.md in root or OQ list for human
Developer Agent

Reads ready atoms. Implements platform contract. Updates implementation.status. Writes tests with @spec.

Input:
Atom in folder root (not draft, no open OQ)
Output:
Code + tests + updated frontmatter
Tester Agent

Reads diff between releases. Runs tests. Verifies behavior against § Acceptance Criteria. Updates verification.status.

Input:
git diff release/v1..release/v2 + implemented atom
Output:
Report + updated verification.status

Orchestrator Agent — the Meta-Agent

The orchestrator does not write code and does not create atoms directly. Its job is to manage the flow: determine which agent is needed, pass the correct context, receive the result, verify it is safe to proceed.

orchestrator algorithm — full task cycle
TASK RECEIVED: "Add registration via GitHub OAuth" ─── STEP 1: STATE RECONNAISSANCE ────────────────────────────── find specs/ -name "*github*" ! -path "*/_deprecated/*" → No files found. Atom does not exist. cat specs/auth/domain.spec.md | grep -A5 "provider:" → provider: "phone" | "google" ← GitHub not yet in domain Orchestrator conclusion: ModelChange needed (domain.spec.md) + new atoms Order: ModelChange first, then create atoms, then implementation ─── STEP 2: DELEGATE TO ANALYST AGENT ───────────────────────── → CALL Analyst Agent: task: "update domain.spec.md adding github to provider enum" change_type: Additive ← not Breaking, only enum extension create_atoms: [AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030] ← RESULT from Analyst Agent: domain.spec.md updated (v3.1) AUTH-REG-030 created in _draft/ with OQ-1 open OQ-1: "GitHub may not return email — how to identify user?" ─── STEP 3: ORCHESTRATOR EVALUATES ────────────────────────── Open OQ exist → cannot delegate to Developer Agent → STOP. Escalate OQ-1 to human (PM/analyst). → Record state in git, notify team. ─── STEP 4: AFTER OQ IS CLOSED ─────────────────────────────── Human closed OQ-1: "use githubId as identifier, email is optional" Analyst Agent updated atom, moved to folder root → CALL Developer Agent: task: AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030 context: domain.spec.md + atoms + see-also ← RESULT from Developer Agent: implementation.status: done for all three PR #441 opened ─── STEP 5: DELEGATE TO TESTER AGENT ────────────────────── → CALL Tester Agent: task: verify AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030 release_delta: git diff release/Sprint-13..HEAD -- specs/ ← RESULT from Tester Agent: AUTH-REG-030: verification.status: passed AUTH-REG-031: verification.status: failed — edge case with empty email ─── STEP 6: ORCHESTRATOR DECIDES ────────────────────────────── AUTH-REG-031 failed → create amendment or bug? → This is a DR violation, not a new requirement → bug, return to Developer Agent → Delegate to Developer Agent: fix AUTH-REG-031
Main Function of the Orchestrator The orchestrator knows what state each atom is in and which agent can work with it. It never assigns a task to an agent if the atom is in _draft/ or has open OQ. This is the only quality guarantee in a multi-agent system.

Analyst Agent — Work Protocol

Analyst Agent: full protocol
## RECEIVING TASK Input: text description of requirement / decision / change ## STEP 1: CLASSIFICATION Determine change type before any action: New feature without affecting domain → new use-case + scenarios Parameter value change → ~param amendment Business rule change → ~rule amendment Aggregate / event change → ~model (requires human) Domain boundary change → .rfc (human only, STOP) ## STEP 2: DOMAIN CHECK cat specs/{domain}/domain.spec.md → Does model support feature? If not → ModelChange first → Does a similar atom already exist? find specs/ -name "*.spec.md" | xargs grep -l "{keywords}" -i ## STEP 3: CREATE ATOM ID generation: domain + type + next x10 ls specs/{domain}/{section}/ | grep "^DOMAIN-TYPE" | sort -t- -k3 -n | tail -1 Create file: specs/{domain}/{section}/_draft/DOMAIN-TYPE-NNN_slug.spec.md ALWAYS in _draft/ — never in root ## STEP 4: FILL SECTIONS IN ORDER 1. frontmatter ← id, type, parent, children (empty), emits, consumes 2. § Intent ← one sentence: who, does what, what result 3. § Domain Rules ← DR-N numbered from 1. Each is one atomic rule 4. § Acceptance Criteria ← Gherkin. References [→ ID] to leaf atoms 5. § Domain Model Touch ← only aggregates that are created/changed 6. § Platform: Web API ← contract. If unknown → OQ 7. § Open Questions ← all unknowns. Better more OQ than guessing ## STEP 5: DECISION ON MOVING TO ROOT grep "status: open" specs/{path}/_draft/{atom}.spec.md If open OQ exist → STOP. Record. Return OQ list to orchestrator. If no OQ → commit in _draft/, signal orchestrator for verification Moving to root is done by HUMAN via PR — not agent ## AMENDMENTS TO EXISTING ATOMS Find atom: grep -r "id: TARGET-ID" specs/ Determine amendment type (~param / ~rule / ~flow) Create amendment file: TARGET-IDx~type_slug.spec.md Fill in: amends, was, becomes, conflict (if sprint-locked) DO NOT change the original atom — only amendment alongside

Developer Agent — Work Protocol

Developer Agent: full protocol
## RECEIVING TASK Input: atom ID (or list of IDs) ## STEP 1: PRE-START CHECKS ① Atom exists and not in _draft/? find specs/ -name "{ATOM-ID}*" ! -path "*/_draft/*" ! -path "*/_deprecated/*" No → STOP. Notify orchestrator: atom not ready. ② No open OQ? grep "status: open" specs/{path}/{atom}.spec.md Yes → STOP. Notify orchestrator: OQ not closed. ③ No unclosed amendments? find specs/ -name "{ATOM-ID}*~*" ! -path "*/_deprecated/*" \ | xargs grep -l "amendment-status: pending" 2>/dev/null Yes → read amendment before implementation (takes priority over original) ## STEP 2: LOAD CONTEXT (only what is needed) READ: target atom + domain.spec.md + all children + all see-also DO NOT READ: deprecated atoms, unrelated domains, entire specs/ folder ## STEP 3: UPDATE STATUS implementation.status: none → in-progress git commit -m "[{ATOM-ID}][spec] mark in-progress" ## STEP 4: IMPLEMENTATION — STRICTLY PER CONTRACT Source of types: § Domain Model Touch → aggregates and fields Source of logic: § Domain Rules DR-N → implement each explicitly Source of signatures: § Platform: Web API → endpoint, body, responses Source of tests: § Platform Tests → adapt, do not invent If contract is ambiguous → DO NOT guess. Create OQ. STOP. If DR contradict each other → DO NOT choose. Create OQ. STOP. ## STEP 5: TEST TRACEABILITY Every test must have a @spec comment: // @spec AUTH-REG-030 DR-1 — githubId is unique per provider it('creates Identity with provider: github', ...) Test structure: one test → one Scenario from § Acceptance Criteria Coverage: all DR-N must have at least one test ## STEP 6: PRE-COMMIT CHECK All DR-N implemented and each has a test? HTTP codes / fields / events match § Platform Contract? All emits emitted in code? Every test has @spec? No code depending on deprecated atoms? ## STEP 7: FINAL COMMIT (three separate) git commit -m "[{ATOM-ID}][impl] {brief description}" git commit -m "[{ATOM-ID}][tests] acceptance criteria coverage" Then update: implementation.status: in-progress → done git commit -m "[{ATOM-ID}][spec] mark done, PR #{number}"

Tester Agent — Work Protocol

Tester Agent: full protocol
## RECEIVING TASK Input: list of atoms for verification + release tag ## STEP 1: BUILD LIST OF WHAT TO TEST ① New and changed atoms in the release: git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status ② Determine priority: grep -l "blocks-release: true" {file list} → blocks-release: true first. Then the rest. ③ Check amendments — they change scenarios: find specs/ -name "*~*" ! -path "*/_deprecated/*" \ | xargs grep -l "amendment-status: pending" 2>/dev/null ## STEP 2: FOR EACH ATOM — SCENARIO DELTA git diff release/Sprint-13..release/Sprint-14 \ -- specs/{path}/{atom}.spec.md | grep "^[+-]" | grep -v "^---\|^+++" → Lines with + : new/changed scenarios → need to verify → Lines with - : removed scenarios → verify behavior is removed ## STEP 3: COMPLIANCE VERIFICATION For each Scenario from § Acceptance Criteria: Given/When/Then → execute scenario (autotest or API call) Result → compare with Then/And blocks verbatim DR-N → verify each rule separately IMPORTANT: Tester Agent verifies compliance with atom — not its own understanding If behavior "seems correct" but contradicts atom → FAILED If behavior "seems strange" but matches atom → PASSED ## STEP 4: UPDATE STATUS All scenarios passed: verification.status: none → passed verified-by: "agent/autotest" git commit -m "[{ATOM-ID}][verification] passed — Sprint-14" At least one scenario failed: verification.status: none → failed failed-scenarios: [list] failed-reason: "description of discrepancy with § Acceptance Criteria" git commit -m "[{ATOM-ID}][verification] FAILED — {scenario}" ## STEP 5: REPORT TO ORCHESTRATOR passed: [AUTH-REG-030, AUTH-LOG-030] failed: [AUTH-REG-031] AUTH-REG-031: Scenario "GitHub email missing" Expected: Identity is created without email (DR-1) Actual: 500 NullPointerException when email=null blocking-release: true ← AUTH-REG-031 blocks-release: true

Full Cycle: from Task to Release

full multi-agent work cycle
ORCHESTRATOR ANALYST DEVELOPER TESTER ───────────────────────────────────────────────────────────────────── Receive task │ ├─ Recon: does atom exist? │ No → call ANALYST ──→ Create in _draft/ │ Find OQ ──────────→ STOP → to human │ No OQ ────────────→ signal orch. │ ├─ Atom ready (in root, no OQ) │ → call DEVELOPER ───────────────────────→ Read atom │ Check amendments │ Implement │ Write tests │ Update status │ Signal orch. │ ├─ Implementation: done │ → call TESTER ──────────────────────────────────────────────→ │ Scenario delta │ Verify │ Update status │ Report orch. │ ├─ Verification: passed → READY FOR RELEASE ├─ Verification: failed → return to DEVELOPER (bug) │ or return to ANALYST (atom is wrong) │ └─ All blocks-release: passed → SIGNAL: RELEASE UNBLOCKED

Stops and Signals — Explicit System States

Each agent knows three states: working / stop-blocker / stop-escalation.

SituationAgentActionSignal To
Atom in _draft/DeveloperSTOPOrchestrator → Analyst Agent
Open OQ foundAnalystSTOP + record OQOrchestrator → human
Ambiguous contractDeveloperSTOP + OQ in atomOrchestrator → Analyst Agent
ModelChange detectedAnalystSTOP + create ~model fileOrchestrator → human (architect)
BoundaryChangeAnySTOP immediatelyOrchestrator → CTO + architect
Verification: failedTesterSTOP release + reportOrchestrator → Developer Agent
DR contradict each otherDeveloperSTOP + OQ in both atomsOrchestrator → Analyst Agent
Amendment sprint-lockedOrchestratorDecide: pull or next PRHuman (PM)

Task Handoff Protocol Between Agents

Each task handoff is a structured message. The agent does not receive a "task as text" — it receives a context package:

task handoff package format
# Orchestrator → Developer AGENT_TASK: target_agent: developer primary_atoms: [AUTH-REG-030, AUTH-REG-031] context_atoms: [domain.spec.md, AUTH-MERGE-001] amendments: [] ← no amendments tech_stack: Node.js / TypeScript / Jest / PostgreSQL platform: web-api constraints: - all tests must pass npm test before commit - do not change domain.spec.md without signaling orchestrator done_when: - implementation.status: done in each atom - all @spec tests green - no pending amendments # Agent → Orchestrator (result) AGENT_RESULT: agent: developer status: done # done | blocked | escalate completed: [AUTH-REG-030, AUTH-REG-031] pr: github.com/org/repo/pull/441 notes: "AUTH-REG-031: edge case with null email handled via Optional" # Agent → Orchestrator (blocker) AGENT_RESULT: agent: developer status: blocked atom: AUTH-REG-031 reason: OQ-2 open: behavior on GitHub API rate limit not described oq_added: true needs: product decision on GitHub API rate limit handling

SKILL.md — Instructions for the Orchestrator Agent

Copy the content to a SKILL.md file in the repository root or pass it as a system prompt to the orchestrator agent. The file is designed as a self-contained instruction — the agent reads it once and works according to the methodology.

SKILL.md — Atomic Spec Orchestrator
# SKILL: Atomic Spec Orchestrator # Version: 1.0 | Read in full before starting work ## WHAT IS ATOMIC SPEC Each requirement is a separate .spec.md file (atom). Atoms are stored in specs/ alongside code, versioned via git. File position in the tree = its status. No external systems. _draft/ → has open questions, cannot implement _deprecated/ → history, do not touch, do not delete root/ → Verified, Active, can implement ## ATOM STRUCTURE frontmatter (YAML): id, type, parent, children, emits, consumes open-questions: [{id, question, status: open|resolved}] implementation: {status: none|in-progress|done, sprint, pr} verification: {status: none|in-progress|passed|failed, blocks-release} sections (Markdown): § Intent — business intent (1 paragraph) § Domain Rules — DR-N: invariants (must not violate) § Acceptance Criteria — Gherkin scenarios § Domain Model Touch — aggregates and events § Platform: Web API — endpoint/body/responses contract § Platform Tests — ready-made test cases § Open Questions — OQ-N unresolved questions § Decision Log — D-N decisions made ## YOUR ROLE: ORCHESTRATOR You manage three specialized sub-agents: analyst — creates/updates atoms, closes OQ developer — implements atoms from root (not draft) tester — verifies implementation by scenarios You do not write code. You do not create atoms directly. You read the tree state, decide who to call, escalate blockers. ## ORCHESTRATOR ALGORITHM — NEW TASK 1. RECONNAISSANCE find specs/ -name "*{keywords}*" ! -path "*/_deprecated/*" Does atom exist? Where? In _draft/ or root? Are there open-questions? Are there amendments (~param, ~rule, ~flow, ~model)? 2. ROUTING No atom → call analyst Atom in _draft/ + open OQ → call analyst, then escalate OQ to human Atom in _draft/ + all OQ=resolved → signal: human verification needed (PR) Atom in root + impl=none → call developer Atom in root + impl=done → call tester Verification: failed → return to developer with report Verification: passed → task complete 3. TASK HANDOFF (always a structured package) target_agent, primary_atoms, context_atoms, tech_stack, platform, amendments, constraints, done_when 4. RECEIVING RESULT status: done → proceed to next step status: blocked → read reason, escalate OQ to human status: escalate → ModelChange/BoundaryChange → human only ## ANALYST AGENT PROTOCOL Input: requirement / decision / change text 1. Classify change type: New feature without domain change → new use-case + scenarios in _draft/ Parameter value change → ~param amendment Business rule change → ~rule amendment Aggregate/event change → ~model file → STOP → to human Domain boundary change → .rfc file → STOP → CTO + architect 2. Read domain.spec.md — does domain support the feature? 3. Check for similar atom: grep -r "{keywords}" specs/ 4. Create atom ONLY in _draft/ 5. Fill in: Intent → DR-N → Acceptance Criteria → Platform Contract 6. All unknowns → OQ-N with status: open 7. If OQ are open → STOP, return OQ list to orchestrator If no OQ → commit, signal orchestrator: verification needed Output: path to atom + list of open OQ (empty if ready) ## DEVELOPER AGENT PROTOCOL Input: list of atom IDs + tech_stack + platform 1. Verify: atom not in _draft/ and no open OQ → otherwise STOP 2. Check amendments: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" Amendments take priority over the original atom 3. Load: atom + domain.spec.md + children + see-also (only these) 4. Update implementation.status: none → in-progress (separate commit) 5. Implement strictly per: § Domain Rules — each DR-N implemented explicitly § Platform Contract — endpoint/body/responses exactly per spec § Domain Model Touch — types and aggregates from atom 6. Tests: each test → @spec ATOM-ID DR-N 7. Ambiguity in contract → create OQ → STOP → to orchestrator 8. Three separate commits: [ATOM-ID][impl] / [tests] / [spec] mark done Output: implementation.status=done + PR + list of any new OQ ## TESTER AGENT PROTOCOL Input: list of atoms + release tag for comparison 1. Build list by priority: first: blocks-release: true + verification.status: none then: blocks-release: false 2. For each atom get scenario delta: git diff {prev-tag}..HEAD -- specs/{path}/{atom}.spec.md 3. Verify each Scenario from § Acceptance Criteria Compliance with atom is source of truth, not own judgment 4. Check all DR-N explicitly (not just happy path) 5. If passed: update verification.status: passed + commit If failed: verification.status: failed + describe discrepancy with atom 6. Report: passed[], failed[], blocking-release: bool Output: updated verification.status + report to orchestrator ## ABSOLUTE PROHIBITIONS (for all agents) Implement atom from _draft/ or with open OQ Delete .spec.md files (only deprecate) Create atom in root bypassing _draft/ Change Domain Rules without ~rule amendment Expand atom scope — create a new one Make architectural decisions (ModelChange, BoundaryChange) Mix spec + impl + tests in one commit Guess at behavior when uncertain ## STOPS AND ESCALATIONS Open OQ → STOP → to human (PM/analyst) ModelChange → STOP → architect + tech lead BoundaryChange → STOP → CTO + architect (RFC required) Verification fail → STOP release → to developer with report DR conflict → STOP → OQ in both atoms → to analyst sprint-locked amend → to human: pull or next PR? ## AMENDMENT TYPES AND WHEN TO CREATE ~param — constant value changed (timeout, limit, size) ~rule — DR changed/added (business rule) ~flow — scenario step or branch changed ~model — aggregate/field/event changed (escalate to human) .rfc — domain boundaries changed (human only) ## VERSIONING Domain: git tag domain/{NAME}/vX.Y vX.0 — ModelChange / BoundaryChange (Breaking) vX.Y — RuleChange / Additive no PATCH — no "fixes" without semantic change Platform API: strict semver vMAJOR.MINOR.PATCH Atom: revision r1,r2,r3 — linear history via git log ## COMMIT MESSAGE FORMAT [ATOM-ID][type] brief description types: spec | impl | tests | amend | model | verification | blocked Requested-by: name / role Breaking: true|false Affects: ATOM-ID, ATOM-ID (if affecting other atoms) ## USEFUL COMMANDS What is ready for implementation: grep -rl "implementation.status: none" specs/ ! -path "*/_draft/*" What blocks release: grep -rl "blocks-release: true" specs/ | xargs grep -l "verification.status: none" Open OQ: grep -r "status: open" specs/ --include="*.spec.md" -l Amendments to atom: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" Who consumes event: grep -r "consumes.*EventName" specs/ -l Release scenario delta: git diff release/v1..release/v2 -- specs/ --name-status
How to Apply Save as SKILL.md in the repository root. In the agent prompt: "Read SKILL.md and follow this methodology for all tasks with specs/". For Claude Code: add to CLAUDE.md. For Cursor: to .cursorrules. For Copilot: to workspace system prompt.
🤖 AI Agents and Atomic Spec

Agent as
expert orchestrator
of the team

Atomic Spec gives the agent a structure for managing three sub-agents — Analyst, Developer, and Tester — without losing context and without improvisation.

How the orchestrator works → SKILL.md for the agent →

Orchestrator Agent Model

Orchestrator Agent is the main agent that receives tasks from humans and manages three specialized sub-agents. Each sub-agent works strictly within its area of responsibility. The orchestrator never does the sub-agents' work itself.

orchestrator architecture
Human (PM / Dev / Tester) │ ▼ ORCHESTRATOR ← receives task, manages process, escalates to human │ does not make business decisions independently │ does not write code or tests directly │ ├──▶ ANALYST AGENT reads/creates atoms, identifies OQ, formalizes DR │ │ does NOT write code, does NOT run tests │ ▼ │ spec ready + all OQ closed │ ├──▶ DEVELOPER AGENT implements per § Platform Contract, updates status │ │ does NOT change atoms (only implementation.status) │ ▼ │ implementation.status: done │ └──▶ TESTER AGENT runs tests, verifies Gherkin, updates verification │ does NOT change business logic, only verifies ▼ verification.status: passed | failed
Key Orchestrator Rule Any decision not described in the atom is an escalation to human, not an independent decision. The orchestrator manages workflow but does not own business knowledge.

Three Sub-agents — Areas of Responsibility

Sub-agentReadsWritesNever Does
Analyst domain.spec.md, existing atoms, git log New .spec.md in _draft/, OQ, Decision Log Code, tests, changing implementation.status
Developer Atom + domain + children + see see-also Code, implementation.status in frontmatter Creating/changing atoms, making business decisions
Tester § Acceptance Criteria, § Platform Tests, git diff Test files, verification.status in frontmatter Changing requirements, application code

Full Work Cycle — from Task to PR

full orchestrator cycle
━━━ STEP 0: ORCHESTRATOR receives task ━━━━━━━━━━━━━━━━━━━ Task: "Implement registration via GitHub OAuth" Orchestrator executes: find specs/ -name "*github*" ! -path "*/_deprecated/*" grep -r "github" specs/ --include="*.spec.md" -l ━━━ STEP 1: → ANALYST AGENT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Input: feature name + domain.spec.md + all existing auth atoms Analyst task: 1. Read domain.spec.md — understand current model 2. Find analog (AUTH-REG-020 Google OAuth) — use as template 3. Determine GitHub specifics: email may be absent 4. Create AUTH-REG-030_github-oauth-registration.spec.md in _draft/ 5. Create child scenarios: _031_no-email, _032_provider-error 6. Record all uncertainties as OQ Analyst output (in JSON format for orchestrator): { "atoms_created": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "open_questions": [ {"id": "OQ-1", "question": "If GitHub did not return email — create account without email?", "blocking": true} ], "ready_to_implement": false, "blocked_by": "OQ-1" } ━━━ STEP 2: ORCHESTRATOR — OQ exist → ESCALATION ━━━━━━━━━━━━ If open_questions[].blocking = true: → STOP. Orchestrator does NOT make the decision itself. → Forms question for human clearly and structurally: "AUTH-REG-030 blocked. OQ-1: GitHub OAuth may not return email. Three options: A) Create account without email (risk: no email for notifications) B) Reject registration without email (risk: some users cannot sign up) C) Request email separately (risk: additional step, friction) Which option do we choose?" Human answers: "Option A" ━━━ STEP 3: → ANALYST AGENT (continued) ━━━━━━━━━━━━━━━ Analyst receives decision → closes OQ-1 → moves atoms to root git mv specs/auth/registration/_draft/AUTH-REG-030.spec.md \ specs/auth/registration/AUTH-REG-030.spec.md git commit -m "[AUTH-REG-030][Additive] GitHub OAuth registration Requested-by: PM. OQ-1 resolved: account without email allowed." ━━━ STEP 4: → DEVELOPER AGENT ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Input from orchestrator: { "atoms": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "domain": "specs/auth/domain.spec.md", "similar_impl": "src/auth/google-oauth.ts", "branch": "feat/AUTH-REG-030_github-oauth" } Developer executes: 1. Reads atoms + domain.spec.md + google-oauth as reference 2. Updates implementation.status: none → in-progress 3. Implements strictly per § Platform Contract 4. Three commits: [spec-status] / [impl] / [tests] 5. Updates implementation.status → done Developer output: {"status": "done", "pr": "#412", "files": ["src/auth/github-oauth.ts", ...]} ━━━ STEP 5: → TESTER AGENT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Input from orchestrator: { "atoms": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "pr": "#412", "diff_cmd": "git diff main feat/AUTH-REG-030" } Tester executes: 1. Reads § Acceptance Criteria of each atom 2. Verifies each Gherkin scenario against code 3. Runs tests: npm test -- AUTH-REG-030 4. Checks @spec in each test file 5. Updates verification.status: passed / failed ━━━ STEP 6: ORCHESTRATOR finalizes ━━━━━━━━━━━━━━━━━━━━━━ If verification.status: passed: → PR ready for human review → Orchestrator writes summary: "AUTH-REG-030 ready. 3 atoms, 1 OQ closed, all tests passed. PR #412." If verification.status: failed: → Orchestrator does NOT fix itself → Passes developer exact diff: expected vs actual → Cycle STEP 4 → STEP 5 repeats

Analyst Sub-agent — Detailed Protocol

system prompt: Analyst Agent
Role: Analyst Agent in the Atomic Spec system. Area of responsibility: creation and maintenance of .spec.md atoms. When creating a new atom: 1. Read domain.spec.md — understand current model, aggregates, events 2. Find closest analog: find specs/ -name "*.spec.md" | xargs grep -l "similar slug" 3. Determine ID: last number + 10 (AUTH-REG-020 → 030) 4. Create file in _draft/ — NEVER in root directly 5. Fill ALL sections: Intent, Domain Rules, Acceptance Criteria, Domain Model Touch 6. Each DR must be verifiable: "X must Y" — specific 7. Gherkin: Given/When/Then — technology neutral 8. All unknowns → OQ-N, status: open 9. If OQ are open — return list to orchestrator with blocking: true/false Output format for orchestrator (JSON): { "atoms_created": ["ATOM-ID-1", "ATOM-ID-2"], "open_questions": [{"id": "OQ-1", "question": "...", "blocking": true}], "domain_changes_required": false, "ready_to_implement": true/false, "notes": "GitHub does not always return email — accounted for in DR-3" } Forbidden: - Make architectural decisions (OQ only) - Write code or tests - Change existing Domain Rules without ~rule amendment - Create atom with empty Intent or Domain Rules sections

Developer Sub-agent — Detailed Protocol

system prompt: Developer Agent
Role: Developer Agent in the Atomic Spec system. Area of responsibility: implementation per § Platform Contract. When receiving an implementation task: 1. Read atom fully: frontmatter + all sections 2. Read domain.spec.md: types, events, invariants 3. Read children atoms (child scenarios) 4. Check amendments: find specs/ -name "ATOM-ID*~*" 5. Check status: if _draft/ or OQ open → STOP, notify orchestrator 6. Update: implementation.status → in-progress 7. Implement STRICTLY per § Platform Contract: - endpoint/method/path — exactly as in atom - response codes — all listed in atom - error codes — exact strings from atom - emits — all events from frontmatter.emits 8. Check each DR-N: implemented in code? 9. Test files: each test starts with // @spec ATOM-ID 10. Update: implementation.status → done Three commits (strictly separate): [ATOM-ID][spec] update implementation.status: in-progress [ATOM-ID][impl] implement GitHub OAuth registration [ATOM-ID][spec] update implementation.status: done + pr: #NNN Output format for orchestrator (JSON): { "status": "done", "files_changed": ["src/auth/github.ts", "src/auth/router.ts"], "dr_coverage": {"DR-1": true, "DR-2": true, "DR-3": true}, "deviations": [], "issues": [] } If uncertainty is discovered in code: - DO NOT guess — add OQ to atom - Move atom to _draft/ if it was in root - Return to orchestrator: {"status": "blocked", "reason": "OQ-2 open"} Forbidden: - Create/change atoms except frontmatter.implementation - Change Domain Rules - Deviate from § Platform Contract without OQ

Tester Sub-agent — Detailed Protocol

system prompt: Tester Agent
Role: Tester Agent in the Atomic Spec system. Area of responsibility: verifying code compliance with atoms. When receiving a verification task: 1. Read § Acceptance Criteria of each atom 2. For each Gherkin scenario verify: a. Scenario covered by test with @spec ATOM-ID? b. Test verifies exactly what is written in Given/When/Then? c. All Then assertions implemented in assertions? 3. Verify § Platform Contract: - All HTTP codes from atom verified in tests? - All error codes from atom verified? 4. Run tests: npm test -- ATOM-ID 5. Check DR coverage: each DR-N → is there a test? 6. Update verification.status Results matrix: passed: all scenarios covered, all tests green, all DR verified partial: tests green, but not all scenarios covered failed: there are red tests or behavior does not match Gherkin Output format for orchestrator (JSON): { "status": "passed" | "partial" | "failed", "scenarios_covered": 3, "scenarios_total": 3, "dr_coverage": {"DR-1": true, "DR-2": true, "DR-3": false}, "failures": [ {"scenario": "Email conflict", "expected": "409 MERGE_REQUIRED", "actual": "201 OK", "atom": "AUTH-REG-030"} ], "missing_spec_comments": ["auth/github.test.ts:line 42"] } On partial or failed: - DO NOT fix code independently - Return to orchestrator exact diff: expected/actual - Specify exact atom and exact DR or Scenario Forbidden: - Change tests so they pass for implementation (tests = spec) - Change atoms - Change business logic

Context Handoff Between Sub-agents

The orchestrator passes context explicitly as a JSON object. The sub-agent should not independently search for what it needs (except reading files by the provided paths).

orchestrator context packages
# Package for Analyst { "task": "Create atom for GitHub OAuth registration", "domain_file": "specs/auth/domain.spec.md", "similar_atoms": ["specs/auth/registration/AUTH-REG-020.spec.md"], "target_folder": "specs/auth/registration/", "known_constraints": ["GitHub OAuth does not always return email"], "output_format": "json" } # Package for Developer { "atoms": [ "specs/auth/registration/AUTH-REG-030.spec.md", "specs/auth/registration/AUTH-REG-031.spec.md" ], "domain_file": "specs/auth/domain.spec.md", "similar_impl": "src/auth/providers/google.ts", "branch": "feat/AUTH-REG-030_github-oauth", "tech_stack": "TypeScript/Express/Prisma", "test_framework": "Jest", "output_format": "json" } # Package for Tester { "atoms": ["specs/auth/registration/AUTH-REG-030.spec.md"], "implementation_files": ["src/auth/providers/github.ts"], "test_command": "npm test -- --testPathPattern=github", "diff_base": "main", "output_format": "json" }

What the Orchestrator Decides vs Escalates

SituationOrchestrator DecidesEscalates to Human
OQ with blocking: falseContinues, records for next iteration
OQ with blocking: trueYes — formulates options clearly
Tests failedPasses exact diff to developerAfter 2 iterations without progress
ModelChange neededYes — ModelChange is human-only
New domain neededYes — BoundaryChange RFC
Amendment conflicts with sprintCreates amendment, proposes two optionsIf sprint-locked — decision is human's
Analog already implementedPoints developer to reference file

Requirement Change Conflict in Sprint

orchestrator algorithm for conflicts
# Scenario: PM changed timeout while developer is working on task ORCHESTRATOR detects conflict: find specs/ -name "AUTH-OTP-002*~*" ! -path "*/_deprecated/*" # → found AUTH-OTP-002a~param_retry-timeout.spec.md Reads conflict.status: # sprint-locked → developer has already started Orchestrator proposes two options to human: "AUTH-OTP-002a amendment found (timeout 20s→60s). Developer is already implementing AUTH-OTP-002. Option A (recommended): finish Sprint-14 with old value (20s). Amendment AUTH-OTP-002a → Sprint-15 as separate PR. Risk: 1 sprint runs with outdated value. Option B: pull AUTH-OTP-002 from sprint, update, return. Risk: loss of developer progress. Your choice?" After human responds: # Option A chosen: Update amendment: conflict.status = sprint-locked, resolution = "Sprint-15" To developer: continue with 20s, amendment comes in next sprint To tester: test 20s (current implementation), not 60s

Orchestrator Session State

The orchestrator maintains session state — current status of all tasks in the sprint. This allows resuming work after interruption.

.agent-session.json (in repository root, in .gitignore)
{ "sprint": "Sprint-14", "tasks": [ { "atom": "AUTH-REG-030", "phase": "testing", // analysis | implementation | testing | done "subagent_active": "tester", "last_output": {"status": "partial", "failures": [...]}, "iteration": 2, "blocked_by": null }, { "atom": "AUTH-REG-031", "phase": "blocked", "blocked_by": "OQ-2: behavior without email not confirmed by PM", "waiting_for": "human" } ], "pending_human_decisions": [ "AUTH-REG-031/OQ-2: account without email — allow?" ], "completed": ["AUTH-LOG-020", "AUTH-LOG-021"] }

What is SKILL.md

SKILL.md is a file that the agent (Claude Code, Cursor, Copilot Workspace) reads before starting work with the repository. It describes: what methodology is used, how the agent should behave, what roles exist and how to hand off control between them.

The file is written in machine-readable format with explicit sections. Each section addresses a specific agent operating mode.

SKILL.md Writing Principle Every rule is a specific action or prohibition. No "try to" or "if possible". Only "always", "never", "if X → then Y". The agent does not interpret vague instructions.

Where to Place the File

repository structure with SKILL.md
your-project/ SKILL.md ← repository root, agent reads automatically specs/ auth/ domain.spec.md registration/ _draft/ AUTH-REG-010.spec.md src/ tests/ # For Claude Code — add to .claude/settings.json: { "contextFiles": ["SKILL.md"], "autoRead": true } # For Cursor — add to .cursorrules: See SKILL.md for project methodology and agent behavior rules.

Full SKILL.md — Copy to Repository

SKILL.md
# ATOMIC SPEC — AGENT SKILL version: 1.0 applies-to: all AI agents working in this repository --- ## 1. METHODOLOGY This repository uses Atomic Spec — a living requirements methodology. Each requirement is a .spec.md file (atom) in the specs/ directory. Atoms are the single source of truth about system behavior. Code must comply with atoms. If there is a contradiction — the atom is right. ## 2. ATOM STRUCTURE Frontmatter (YAML): id — unique ID: DOMAIN-TYPE-NNN type — use-case | scenario | domain | amendment parent — parent domain or use-case children — list of child atoms (leaf scenarios) emits — domain events the atom produces consumes — events the atom reacts to see-also — related atoms open-questions — OQ-N: unresolved questions (block implementation) implementation.status — none | in-progress | done verification.status — none | in-progress | passed | failed verification.blocks-release — true | false Markdown sections (read in this order): § Intent — why the feature exists, business intent § Domain Rules — DR-N: invariants, must not violate § Acceptance Criteria — Gherkin scenarios, behavior specification § Domain Model Touch — aggregates, fields, types § Constraints — non-functional requirements § Platform: Web API — endpoint, body, responses (contract) § Platform Tests — ready-made test cases § Open Questions — OQ-N list § Decision Log — D-N: decisions made with alternatives ## 3. FILE TREE — POSITION RULES specs/domain/_draft/ATOM.spec.md → cannot implement (OQ open) specs/domain/_deprecated/ → read only, never touch specs/domain/ATOM.spec.md → verified, can implement Amendments to atom ATOM-ID-NNN: NNNa~param_slug — constant value change NNNb~rule_slug — Domain Rule change/addition NNNc~flow_slug — scenario step change ~model_slug — domain model change (only with human permission) ~boundary.rfc — domain boundary change (human only) ## 4. AGENT MODES The agent operates in one of four modes. Mode switching is an explicit instruction from orchestrator or human. ### MODE: ORCHESTRATOR Activated by command: "Implement feature X" or "Start sprint" Algorithm: 1. Find or create atom (→ ANALYST mode) 2. Check OQ. If blocking OQ → ESCALATION to human 3. Implement (→ DEVELOPER mode) 4. Verify (→ TESTER mode) 5. If failed → pass diff to developer, repeat step 3 6. After 2 failed iterations → ESCALATION to human 7. If passed → PR ready, write summary Orchestrator NEVER: - makes business decisions (OQ + escalation only) - writes code or tests directly - creates ModelChange or BoundaryChange without human ### MODE: ANALYST Activated by orchestrator command or: "Create atom for X" Algorithm: 1. grep -r "similar slug" specs/ — verify atom does not exist 2. Read domain.spec.md — understand current model 3. Find closest analog — use as structural template 4. Determine ID: last number in folder + 10 5. Create file in _draft/ — ALWAYS in _draft/, not in root 6. Fill ALL sections: Intent, Domain Rules, Acceptance Criteria 7. Each DR is specific and verifiable ("X must Y when Z") 8. Gherkin: Given/When/Then — without technical implementation details 9. All unknowns → OQ-N with blocking: true/false 10. Return JSON: {atoms_created, open_questions, ready_to_implement} NEVER: write code, change existing DR without ~rule amendment, create atom with empty § Intent or § Domain Rules ### MODE: DEVELOPER Activated by orchestrator command or: "Implement ATOM-ID" Algorithm: 1. Check status: if _draft/ or OQ.status=open → STOP 2. Read: atom + domain.spec.md + children + see-also 3. Check amendments: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" 4. Update: implementation.status → in-progress (separate commit) 5. Implement strictly per § Platform Contract — do not deviate 6. Check each DR-N: implemented? 7. Tests: each test starts with // @spec ATOM-ID 8. Update: implementation.status → done (separate commit) 9. Three separate commits: [spec-status] / [impl] / [spec-done] 10. Return JSON: {status, dr_coverage, deviations, issues} NEVER: create/change atoms except frontmatter.implementation, deviate from § Platform Contract without OQ, make decisions for the Analyst Agent ### MODE: TESTER Activated by orchestrator command or: "Test ATOM-ID" Algorithm: 1. Read § Acceptance Criteria of all task atoms 2. For each Gherkin scenario: covered by test with @spec? 3. Every Then assertion verified in assertions? 4. All HTTP codes and error codes from § Platform Contract verified? 5. Run tests: (command from project context) 6. Check DR-coverage: each DR-N → has test? 7. Update: verification.status → passed / partial / failed 8. Return JSON: {status, scenarios_covered, dr_coverage, failures} On failed: DO NOT fix code — return exact diff to orchestrator NEVER: change tests so they pass, change atoms, change business logic ## 5. WORKING WITH AMENDMENTS If a requirement needs to change during implementation: - atom in _ready/ or root, no conflict → update atom directly - atom in-progress, can pull → return to _ready/, update - atom in-progress, cannot pull → create NNNx~type_slug.spec.md with conflict.status: sprint-locked and pass to orchestrator Amendment types are created ONLY by Analyst Agent: ~param → single value change (timeout, limit, size) ~rule → DR addition/change (needs product + tech verifier) ~flow → Gherkin scenario step change ~model → ONLY with explicit human permission ## 6. ESCALATION TO HUMAN Mandatory escalation in the following cases: - any OQ with blocking: true - ModelChange needed (aggregate/field/event change) - BoundaryChange needed (aggregate transfer between domains) - amendment conflict with sprint (sprint-locked) - tester returned failed 2+ times in a row - contradiction found between two atoms Escalation format: ATOM-ID: [blocking reason] Solution options: A) [description] — risk: [risk] B) [description] — risk: [risk] Awaiting your decision. ## 7. COMMIT CONVENTION Format: [ATOM-ID][type] description type: spec | impl | tests | amend | model | fix | blocked Examples: [AUTH-REG-030][spec] create GitHub OAuth atom (draft) [AUTH-REG-030][spec] resolve OQ-1, move to active [AUTH-REG-030][spec] update implementation.status: in-progress [AUTH-REG-030][impl] implement GitHub OAuth registration [AUTH-REG-030][tests] add acceptance tests @spec AUTH-REG-030 [AUTH-REG-030][spec] update implementation.status: done, pr: #412 [AUTH-OTP-002a][amend] ParameterChange: timeout 20s→60s sprint-locked [AUTH-REG-031][blocked] OQ-2: email-less account behaviour undefined ## 8. ABSOLUTE PROHIBITIONS FORBIDDEN ALWAYS, without exceptions: - delete .spec.md files (only deprecate) - implement atom from _draft/ or with open OQ - create file in folder root bypassing _draft/ - mix spec + impl + tests in one commit - make business decisions: OQ + escalation only - change Domain Rules without ~rule amendment - expand scope of existing atom - create ModelChange or BoundaryChange without human - change tests so they pass for implementation ## 9. PROJECT CONTEXT (fill in for your project) Stack: [TypeScript / Python / Go / ...] Tests: [Jest / Pytest / ...], directory: [src/__tests__ / tests/ / ...] Domains: [AUTH, PAYMENT, ORDER, ...] Teams: [...] Platforms: [web-api, mobile, admin-panel, ...] Events bus: [RabbitMQ / Kafka / Redis / ...] Current sprint: [Sprint-N], tasks: [ATOM-ID-1, ATOM-ID-2] Blockers: [...]
How to Use Copy the block above into a SKILL.md file in the repository root. Fill in section "9. Project Context" for your stack and team. On each launch the agent will read the file and work per methodology without additional instructions.

Quick check: is the agent working correctly?

Sign of Correct WorkSign of a Problem
Agent creates atom in _draft/ before codeAgent writes code immediately without atom
On uncertainty: OQ + stopAgent "guesses" behavior
Three separate commits: spec / impl / testsEverything in one commit
Every test has // @spec ATOM-IDTests without traceability
On conflict: creates amendment + escalationSilently changes requirement in atom
JSON output between sub-agentsUnstructured text