Your first capability
A capability is one file in src/capabilities/ that owns records, decides, and carries its examples. Never hand-write the boilerplate: generate it.
1. Scaffold
Section titled “1. Scaffold”npx atlas new loyalty-points --dir src/capabilitiesatlas new writes src/capabilities/loyalty-points.ts and prints the next steps. In an app the file imports @ts-atlas/core/runtime; inside the Atlas repository it imports ../runtime/index.ts. The generated file is a working capability with one command and three examples:
import { capability, t } from '@ts-atlas/core/runtime';
// loyalty-points: one file owns its records, its decisions and the examples that specify them.// Replace the TODOs, then run: node bin/atlas.ts check && node bin/atlas.ts testexport default capability({ name: 'loyalty-points', intent: 'TODO: one sentence on what loyalty-points decides and which rule it protects.', owns: { 'loyalty-points': t.object({ id: t.string, createdAt: t.string }) }, emits: { LoyaltyPointsCreated: t.object({ id: t.string }) }, // sends: { SomeCommand: t.object({ ... }) }, // commands this capability sends to others}) .command('CreateLoyaltyPoints', { access: 'user', // who may dispatch it: 'public' | 'user' | 'internal' | ['role', ...] input: t.object({ id: t.string }), reads: { existing: { from: 'loyalty-points', key: (input) => input.id } }, decide({ input, state, now }, d) { if (state.existing !== null) return d.reject('already-exists', { id: input.id }); return d.accept({ write: [d.write('loyalty-points', input.id, { id: input.id, createdAt: now })], emit: [d.fact('LoyaltyPointsCreated', { id: input.id })], }); }, examples: [ { name: 'creates a new loyalty-points', state: { existing: null }, input: { id: 'x1' }, expect: { emits: ['LoyaltyPointsCreated'], writes: ['loyalty-points/x1'] } }, { name: 'rejects a duplicate id', state: { existing: { id: 'x1', createdAt: '2026-01-01T00:00:00.000Z' } }, input: { id: 'x1' }, expect: { rejects: 'already-exists' } }, { name: 'rejects an anonymous caller', actor: null, input: { id: 'x1' }, expect: { rejects: 'unauthenticated' } }, ], }); // React to other capabilities' facts with: // .on('SomeFact', { needs: t.object({ ...only the fields you use }), decide({ fact, state, now }, d) { ... }, examples: [...] })2. Fill it
Section titled “2. Fill it”Fill intent, owns, emits, each command’s access, the decide function and its examples. To use another capability’s data, react to its fact with .on('Fact', { needs }), or declare a foreign read with a shape listing only the fields you use. Never import another capability.
The rules a decision follows:
- It is pure: no
Date,Math.random,fetch,processor timers. Time comes fromnowin the context; ids come from the input. - It returns
d.reject(code, params)with a constant kebab-case code, ord.accept({ write, emit, send, remove, schedule }). - Identity is
ctx.actor, never a payload field. Ownership is checked against the records you read.
3. Check
Section titled “3. Check”npx atlas check --dir src/capabilitiesEvery diagnostic says what rule broke, where, why it matters, and the exact fix. Apply each fix: line literally and re-run until it prints:
✓ system is consistent (0 errors, 0 warnings)See check rules for every code.
4. Test
Section titled “4. Test”npx atlas test --dir src/capabilitiesatlas test runs every handler and query example. Examples are the spec: documentation for people and tests for machines, in the same place.
A real capability
Section titled “A real capability”src/capabilities/orders.ts in the repository is the canonical example: two commands, versioned reads, an ownership check against the record, and examples for every rejection. Read it next to Capabilities.