Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Model the system's behavioral contract — state machines per stateful aggregate, sagas for multi-step workflows with explicit compensations and timeouts, an idempotency-key registry, concurrency rules, temporal ordering invariants, and event emission timing under stable `SM-*` and `SAGA-*` IDs. Use when asked to model the behavior, design state machines and sagas, specify workflows and idempotency, define concurrency rules, or produce a BEHAVIOR.md.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 286% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 282% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 459% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 267% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 274% | 0% |
Produce a BEHAVIOR.md that serves as the single source of truth for everything the system does that is stateful or multi-step: how every stateful aggregate moves between states; how every multi-step operation crosses components, compensates on partial failure, and bounds itself with timeouts and retries; which idempotency keys exist and with what lifecycle; what concurrency rules govern interleaving and locking; what temporal ordering invariants must always hold; and exactly when in a state-machine or saga each domain / integration event is emitted. This is a declarative artifact — it states what must happen, never how to code it.
Without this document, state transitions get scattered across implementation files, saga compensations become discoverable only by reading code, idempotency semantics drift silently between endpoints, and concurrency rules live in comments the next agent does not read. BEHAVIOR.md sits between INTERFACES.md (which owns per-endpoint contracts in isolation) and per-unit SPECs (which implement individual behaviours) and pins the cross-endpoint sequencing that neither isolates. Downstream readers are /quality (metrics and SLOs derive from transition rates and saga timings), /security (threat-on-state-transition analysis and idempotency abuse), /operations (runbook procedures for compensating a stuck saga), every per-unit /SPEC.md for a stateful unit (cites the SM-* or SAGA-* it implements), and /system-verification (end-to-end scenarios exercise saga happy paths and failure modes). The defining discipline — and the commonest violation — is every citation resolves: every INV-NN exists in DOMAIN § 9, every ERR_CODE in ERRORS.md § 3, every EP-name in INTERFACES § 6, every EVT-name in INTERFACES § 7, every UC-NN in USE_CASES.md.
INV-NNs transitions must preserve; § 7 Domain Events supply event names whose emission timing this document fixes.ADR-NN on consistency and transaction boundaries frames § 4 Concurrency Rules.Idempotency-Key header shape that § 3 here maps to saga-level keys.ERR_CODEs from § 3; non-retryable failures in saga blocks cite ERR_CODEs classified there. If ERRORS.md does not yet exist, cite codes by their intended string and surface the lack of a registry entry as an open question for later reconciliation.SM-* and SAGA-* cites at least one UC-NN; orphans reveal either an unneeded behavioural model or a missing use case.SM-*, SAGA-*, and idempotency-key name is permanent. New entries take new unused IDs. Retired entries remain with (retired — superseded by {new-id}) markers.Read set size: 3 required artifacts + ERRORS when present + USE_CASES + optional prior BEHAVIOR. Read all required inputs end-to-end. Truncated reads cause two specific failure modes: invariant violations in transitions (DOMAIN not fully read) and saga steps that cite endpoints that do not exist (INTERFACES not fully read).
Behavioral-contract construction proceeds in seven phases: aggregate-to-SM mapping, saga identification, idempotency-key registration, concurrency rules, temporal invariants, event emission timing, validation. Phases are sequential — later phases cite IDs introduced by earlier phases — but revisit earlier phases if a later one reveals a missing transition, a mis-scoped key, or an un-enforced invariant.
List every aggregate in DOMAIN.md § 5 whose description mentions a lifecycle, states, or a sequence of observable conditions. Every such aggregate gets an SM-{aggregate-name} block following references/saga-template.md verbatim.
For each SM:
Lifecycle bullet is the seed. Enumerate every named condition (pending, active, archived, suspended) as a state. If DOMAIN uses a short prose description (create → review → publish → archive), convert each arrow-separated condition to a state name in lowercase-hyphen form.Invariants preserved column lists every INV-NN from DOMAIN § 9 that applies to the aggregate. Transitions that might violate an invariant name the mitigation explicitly (a pre-transition guard, an atomic effect, or a compensation path if the mitigation is only partial).ERR_CODE emitted. Silent rejection is forbidden.stateDiagram-v2. Every state in the transition table appears in the diagram; every terminal state ends with --> [*]. Diagrams must parse — an SM whose diagram has a dangling edge is a modelling error.Aggregates explicitly without a lifecycle (invariant-only aggregates per DOMAIN § 5) do not get an SM — instead § 1 of BEHAVIOR opens with a one-line note that explicitly names them as SM-free and why. This prevents silent omission.
Every multi-step operation that crosses two or more aggregates, two or more components, or requires compensation on partial failure is a saga. Scan three sources to identify sagas:
EP-X → EVT-Y → consumer component → effect) is a saga.For each saga produce a SAGA-{name} block per the template in references/saga-template.md. Rules:
EP-name; scheduled event on a named cadence; domain event EVT-name; state-machine transition SM-entity: from → to. Multi-trigger sagas either split into separate SAGA-* entries or declare a discriminator trigger.none (read-only). Missing compensations are modelling bugs. A step whose side effects are "inherently irreversible" (an external email sent, a refund wired) has a compensation that records the fact and triggers operator action — not a silent gap.5s, 30s, 2m). (none) is permitted only with explicit rationale — e.g., (none — step is bounded by external SLA of downstream EP-name).retryable column. A saga that retries on a code ERRORS.md classifies as no is a contract violation.Used by column.§ 3 is the registry of every idempotency key the system uses. One row per key, sorted alphabetically by key name. Columns: Key name, Scope (per-{resource}, global), Storage (Postgres table, Redis namespace, in-memory cache), TTL (duration or permanent), Generation (client-provided via header, server-derived from body hash, system-generated ULID), Collision handling (return cached result, reject with ERR_CODE, serialise behind lock), Used by (comma-separated SAGA-* / EP-* / EVT-* references).
Rules:
Idempotency key bullet and that key has a row here. Bidirectional citation — the row's Used by column names the saga.Idempotency-Key HTTP header from INTERFACES.md § 4 is one row in § 3 with scope global (header) and Used by listing every mutating endpoint that honours it (or all mutating endpoints per INTERFACES § 4 if the list is the default).repo_id + branch) state the derivation precisely enough that two implementations would agree on whether two requests are duplicates.24h, 15m, 7d) or permanent. (none) is never permitted — a key without a TTL leaks storage, which is an operational bug.§ 4 enumerates the rules that govern interleaving and locking across the system. Organise into four subsections — each either populated or explicitly stating "(none applicable)":
Push operations acquire a per-repository advisory lock on repo_id; concurrent pushes to the same repo serialise." Name the lock kind (advisory, row-level, distributed-via-Redis-per-ARCHITECTURE.md § X) and the release condition.ERR_CODE from ERRORS.md (e.g., "Repository carries an _etag; stale-write attempts return VERSION_CONFLICT").EP-name or SAGA-name and the invariant their serialisation preserves.Every rule in this section cites the aggregate, endpoint, or saga it governs and the INV-NN or ERR_CODE it preserves or emits.
§ 5 is a bulleted list of facts about ordering that must always hold. Examples:
INV-03)INV-09, INV-14)INV-22)Each invariant:
INV-NN in DOMAIN § 9. If DOMAIN does not yet have a matching invariant, propose a new INV-NN as an open question for the /domain skill to register.For every EVT-name in INTERFACES.md § 7, state exactly when in the state-machine transition or saga step the event is emitted. The form is one row per event in a table:
| EVT-name | Emitted by | Emission point | Transactional? | Durability | |------------|-----------|----------------|----------------|------------|
Emitted by is the SM-* transition (SM-push: validating → accepted) or saga step (SAGA-push step 3) that emits it.Emission point is one of three values: inside DB transaction, after commit (outbox), before commit (optimistic). State the concrete mechanism.Transactional? is yes (emitted atomically with state change via outbox or transactional topic) or no (at-least-once fire-and-forget; consumers deduplicate).Durability states what survives a crash: persisted in outbox; retried until ack / in-memory only; lost on crash / persisted but single-shot; manual replay on loss.Every event in INTERFACES § 7 appears exactly once in this table. Events emitted by multiple transitions (rare) get one row per transition with distinct Emitted by values.
Before finalising verify:
SM-* block in § 1 or an explicit one-line exemption in the § 1 opening note.SM-* block has initial state, terminal states (or an explicit (none)), a full transition table, an illegal-transition table with every row citing an ERR_CODE, and a mermaid stateDiagram-v2 that parses.SAGA-* block has a single-form trigger, participating components and aggregates, numbered happy-path steps, a compensation row per step, per-step and overall timeouts, retry policy citing ERR_CODE classes, and a failure-modes table with observable signals.Used by non-empty) and every mutating saga in § 2 cites a key from § 3 (bidirectional).INV-NN referenced in §§ 1, 4, 5 exists in DOMAIN § 9.ERR_CODE referenced in §§ 1, 2 exists in ERRORS.md § 3. If ERRORS.md is not yet produced, cite by intended string and list each code in § 8 Open Questions for reconciliation.EP-name cited in § 2 saga steps exists in INTERFACES § 6.EVT-name cited in §§ 1, 2, 6 exists in INTERFACES § 7 (or in DOMAIN § 7 if the event is pre-registration pending INTERFACES).UC-NN cited in any block exists in USE_CASES.md.EVT-name in INTERFACES § 7; no event is missing.INV-NN without a documented mitigation.try, catch, panic, async, await, mutex, semaphore, goroutine, thread).camelCase, snake_case, JSON, protobuf, HTTP status).Update frontmatter counts to reflect the final document. status is complete if § 8 Open Questions reads All questions resolved., has_open_questions otherwise.
These rules govern the output document. Violations are detected by the quality checklist.
SM-{entity-name}, SAGA-{name}, and idempotency-key names are assigned once and never renumbered, never reused, never silently deleted. Retired entries remain with (retired — superseded by {new-id}) markers so downstream citations keep resolving.
Every INV-NN must exist in DOMAIN § 9. Every ERR_CODE must exist in ERRORS.md § 3 (or, if ERRORS.md is not yet produced, the code string is listed in § 8 Open Questions for reconciliation). Every EP-name must exist in INTERFACES § 6. Every EVT-name must exist in INTERFACES § 7 or DOMAIN § 7. Every UC-NN must exist in USE_CASES.md. Every component name must exist in ARCHITECTURE.md § 2. Every aggregate name must exist in DOMAIN.md § 5. An unresolvable citation is a contract bug and surfaces in § 8 Open Questions, never silently inlined.
Every aggregate in DOMAIN § 5 that carries a Lifecycle bullet with named conditions or a multi-step progression has a matching SM-{entity-name} in § 1. Aggregates that are genuinely lifecycle-free (static reference data, write-once ledgers) are listed explicitly in § 1's opening note as SM-free, with a one-line reason. Silent omission is a modelling error.
Every (from-state, action) pair that must be rejected names the ERR_CODE the system emits. The code must exist in ERRORS.md § 3. Silent rejection ("the state machine simply does not advance") is forbidden — observers need an observable signal.
A transition whose effects could violate any INV-NN must state the mitigation in the same row (a pre-transition guard tightening, an atomic effect, a compensation path). A transition that is known to temporarily break an invariant must document the compensating transition that restores it and the window during which the violation is observable.
Every happy-path step has a row in the compensations table. Missing rows are modelling bugs. none (read-only) is a legitimate compensation value for steps without side effects. Steps whose side effects are inherently irreversible record the fact and the operator-action path — never silence.
A saga that mutates state and has no idempotency key is a contract violation — replays will double-apply. Every mutating saga cites a key from § 3; that key's § 3 row reciprocally lists the saga in Used by.
Every key row has a named store (Postgres table, Redis namespace, outbox topic) and a concrete TTL (24h, 15m, permanent). (none) TTL is never permitted — unbounded storage is an operational bug.
Every locking, optimistic-concurrency, or serializability rule in § 4 names the INV-NN it preserves or the ERR_CODE it emits on conflict. A concurrency rule without a citation is a guess about runtime behaviour, not a contract.
Every invariant in § 5 names the mechanism that enforces the ordering — a lock from § 4, a wait-for-event step in a saga from § 2, a compensating action in an SM from § 1. Invariants without enforcement are wishes, not contracts.
§ 6 states, per event, whether emission is inside the DB transaction, after commit via outbox, or before commit optimistically. Every event from INTERFACES § 7 has a row. Ambiguity here causes split-brain between database and message bus — the commonest source of event-driven bugs.
BEHAVIOR.md never names try / catch blocks, async / await keywords, mutex / semaphore primitives, thread pools, goroutine counts, specific libraries. Those are SPEC / IMPLEMENTATION concerns. This document states what the contract is; how the code realises it lives downstream.
Event payload shapes, HTTP request bodies, JSON field casing belong to INTERFACES.md. This document cites events by EVT-name and endpoints by EP-name — never inlines their shape. A saga block that inlines a JSON body is a rule violation.
Every mermaid block in § 1 must be valid stateDiagram-v2. Before finalising, mentally trace every state listed in the States: bullet and confirm it appears in the diagram as either a source, target, terminal (--> [*]), or initial ([*] -->). A dangling diagram fails the checklist.
One YAML block at the top containing common fields (skill, date, status) and behavior-specific counts (state_machines, sagas, idempotency_keys, concurrency_rules, temporal_invariants, tracked_events, open_questions). Never emit a second block. Counts match the body exactly.
No "appropriate", "relevant", "as needed", "etc.", "various", "and so on", "many", "eventually". Use exact state names, exact IDs, exact durations. Unresolvable ambiguity surfaces in § 8 Open Questions with options, tradeoffs, and a recommendation.
The per-state-machine block template in § 1 and per-saga block template in § 2 are defined in references/saga-template.md. Read that file and apply both templates verbatim — field order, heading level, bullet labels, table column order, and fence style are the contract.
markdown--- skill: BEHAVIOR.md date: {YYYY-MM-DD} status: {complete | has_open_questions | blocked} state_machines: {N} sagas: {N} idempotency_keys: {N} concurrency_rules: {N} temporal_invariants: {N} tracked_events: {N} open_questions: {N} --- # BEHAVIOR — {ProductName} > Behavioral contract. Every state machine, saga, idempotency key, concurrency > rule, temporal ordering invariant, and event emission timing lives here. > Downstream artifacts cite by stable `SM-*` and `SAGA-*` IDs. Wire shapes of > endpoints and events live in INTERFACES.md; error code definitions in > ERRORS.md; domain invariants in DOMAIN.md. ## § 1. State Machines {Opening note. If every aggregate in DOMAIN § 5 with a lifecycle has an SM, a single sentence: "Every stateful aggregate in DOMAIN § 5 has an SM block below." If any aggregate is SM-free, list it explicitly with a one-line reason: "Aggregates without state machines: `{AggregateName}` — {reason}."} {State-machine blocks per `references/saga-template.md`, one per entity, sorted alphabetically by `SM-name`.} --- ## § 2. Sagas {Saga blocks per `references/saga-template.md`, one per multi-step workflow, sorted alphabetically by `SAGA-name`. If the system has no multi-step workflows: "No sagas — every operation is a single state-machine transition or a single stateless endpoint call."} --- ## § 3. Idempotency Keys | Key name | Scope | Storage | TTL | Generation | Collision handling | Used by | |----------|-------|---------|-----|-----------|--------------------|---------| | `{key_name}` | `per-{resource}` / `global` | `{store}` | `{duration}` | `{client-provided / server-derived / system-generated}` | `{cached result / ERR_CODE / serialise}` | `SAGA-{name}`, `EP-{name}` | (One row per key, sorted alphabetically by key name. If the system has no idempotency keys — a pure read-only or strictly non-replayable system: "No idempotency keys — the system is strictly non-replayable; every mutating request is assumed unique." This is rare and demands rationale.) --- ## § 4. Concurrency Rules ### Per-aggregate locking - `{AggregateName}`: `{lock kind — advisory / row-level / distributed}` on `{key}`; acquired at `{point}`, released at `{point}`; preserves `INV-{NN}`. (Repeat for every aggregate with locking. If none: "No per-aggregate locking — every aggregate relies on optimistic concurrency or natural serialisation by natural key.") ### Optimistic concurrency - `{AggregateName}`: carries `{version field name}`; stale-write attempts return `{ERR_CODE}`; enforced at `{point}`. (Repeat for every aggregate using optimistic concurrency. If none: "No optimistic concurrency — every write path uses locking from the previous subsection.") ### Serializability expectations - `{OperationA}` and `{OperationB}` must not interleave — preserves `INV-{NN}`; enforced by `{mechanism}`. (Repeat for every such pair or set. If none: "No cross-operation serializability rules — every operation pair is independent.") ### Parallel-safe operations - `{OperationName}` requires no coordination — `{justification, e.g., read-only / commutative}`. (Repeat for every operation explicitly permitted to run in parallel. This list is positive: it prevents future agents from adding locks where none are needed.) --- ## § 5. Temporal Ordering Invariants - `{One-sentence ordering fact — e.g., "A repository's first push creates the record; subsequent pushes require it to exist."}` (cites `INV-{NN}`; enforced by `{mechanism — lock from § 4 / wait-step in SAGA-{name} / compensating transition in SM-{name}}`) (Repeat for every temporal invariant. If none: "No temporal ordering invariants beyond those enforced by state-machine transition guards in § 1.") --- ## § 6. Event Emission Timing | `EVT-name` | Emitted by | Emission point | Transactional? | Durability | |------------|-----------|----------------|----------------|------------| | `EVT-{name}` | `SM-{entity}: {from} → {to}` / `SAGA-{name} step {N}` | `inside DB transaction` / `after commit (outbox)` / `before commit (optimistic)` | `yes` / `no` | `{durability statement}` | (One row per event in INTERFACES § 7. No event is omitted. If the system has no events: "No events — the system is request/response only. See INTERFACES § 7.") --- ## § 7. Relationship to Other Artifacts - **DOMAIN.md** owns the invariants; this document preserves them through state-machine transitions and saga compensations. Every `INV-NN` cited here exists in DOMAIN § 9. - **ARCHITECTURE.md** owns components; this document sequences them in sagas. Every component named in § 2 exists in ARCHITECTURE § 2. - **INTERFACES.md** owns per-endpoint and per-event wire shapes; this document owns the cross-endpoint sequencing (sagas) and the in-process state changes (SMs). Every `EP-name` and `EVT-name` cited here exists there. - **ERRORS.md** owns the code registry; this document cites codes for illegal transitions, saga non-retryable failures, and concurrency conflicts. Every `ERR_CODE` cited here exists in ERRORS.md § 3. - **USE_CASES.md** owns the actor-driven scenarios; this document names the `UC-NN` every SM and SAGA realises. - **QUALITY.md** derives metrics and SLOs from the transitions and saga timings named here — saga duration, transition counts, compensation rates. - **SECURITY.md** maps `THREAT-NN` entries onto state transitions and saga steps where security-relevant; citations flow from SECURITY into this document, not the reverse. - **OPERATIONS.md** derives runbooks for stuck sagas and compensation playbooks from § 2 and § 4. - **SPECs** for stateful units cite the exact `SM-*` or `SAGA-*` they implement and must use state names and transition event names from this document verbatim. - **/system-verify** exercises saga happy paths and selected failure modes from § 2. --- ## § 8. Open Questions - [ ] {Question — e.g., "Should compensation of `SAGA-push` step 3 also cancel step 2's side effects, or is step 2 designed to be idempotent across cancellations? Current block assumes idempotent step 2, but DOMAIN's `Push` aggregate description suggests step 2 holds a reservation that must be released."} - **Option A:** Step 2 is naturally idempotent; compensation of step 3 leaves step 2's effects in place. Simpler; matches current block. Requires explicit confirmation from `/domain`. - **Option B:** Step 2 holds a reservation; compensation chain must include step 2's release. More correct if reservation is observable; complicates the saga. - **Recommendation:** Option B, pending confirmation from DOMAIN that the reservation is observable. If confirmed, add a row to § 2's `SAGA-push` compensations table releasing the reservation. (If none: "All questions resolved.")
ERR_CODE citations, and a parsing mermaid diagramERR_CODE classes, failure modes with observable signalsERR_CODEs, serializability expectations, and a positive list of parallel-safe operationsINV-NN citations and enforcement mechanismEVT-name whether inside transaction, after commit via outbox, or before commit/interfaces. This document cites by EP-name / EVT-name.code → http_status → user_action → retryable → log_level) — owned by /errors. This document cites by code string./data. This document names the rule ("acquire advisory lock on repo_id"), not the SQL that implements it./spec and /implement./quality. This document names what is observable; /quality names the signal shape./security. This document provides the transition map; /security overlays threats./operations. This document provides the compensations and failure modes from which runbooks are derived./domain. This document cites by aggregate name and INV-NN.skill, date, status, state_machines, sagas, idempotency_keys, concurrency_rules, temporal_invariants, tracked_events, open_questions)SM-* block has Entity, Owning context, States, Initial state, Terminal states, Use cases, full transition table, illegal-transition table, and mermaid diagramSM-* state listed in States: appears in the transition table or as initial / terminalERR_CODE that exists in ERRORS.md § 3 (or is surfaced in § 8 as pending ERRORS.md creation)SM-* mermaid diagram is valid stateDiagram-v2 and parsesSAGA-* block has single-form Trigger, Participating components, Participating aggregates, Idempotency key, Use cases, numbered happy path, full compensations table (one row per happy-path step), per-step timeout, overall timeout, retry policy, non-retryable errors, and failure-modes tableSAGA-* compensations table has one row per happy-path step — no gaps; none (read-only) is a legitimate valueSAGA-* cites an idempotency key that has a row in § 3; the § 3 row reciprocally names the saga in Used byKey name, Scope, Storage, TTL, Generation, Collision handling, Used by — no blanks; TTL is concrete ((none) is forbidden)INV-NN it preserves or ERR_CODE it emitsINV-NN and names the enforcement mechanismEVT-name in INTERFACES § 7; no event missing; Emission point is one of the three literal valuesINV-NN cited anywhere exists in DOMAIN § 9ERR_CODE cited anywhere exists in ERRORS.md § 3 (or is listed in § 8 Open Questions pending ERRORS.md)EP-name cited in § 2 saga steps exists in INTERFACES § 6EVT-name cited anywhere exists in INTERFACES § 7 (or DOMAIN § 7 for pre-wire events)UC-NN cited exists in USE_CASES.mdtry, catch, panic, async, await, mutex, semaphore, goroutine, thread)camelCase, snake_case, JSON body, HTTP status, protobuf)SM-* / SAGA-* / key entries retain their row with (retired — superseded by {new-id}) markersstate_machines, sagas, idempotency_keys, concurrency_rules, temporal_invariants, tracked_events, open_questionsstatus is complete if § 8 is "All questions resolved." and has_open_questions otherwiseOther measured skills in the registry, with their headline benchmark lift.