Skip to content

Four patterns

Layer 2 carries structured behavior. The question is who produced each field, and whether automation may overwrite it.

AtomPRD’s answer: a per-field provenance map keyed by dotted field path.

{
"provenance": {
"behavior.steps.0": { "pattern": "C", "source": "cri_x + fix_y", "confidence": 0.85 },
"behavior.steps.1.kind":{ "pattern": "D", "source": "llm:claude-sonnet-4.6", "confidence": 0.70 },
"behavior.steps.2": { "pattern": "A", "source": "manual", "lockedByUser": true }
}
}

Every entry tags one field path with one of four patterns.

You typed it. Highest confidence; nothing automatic touches it. (A entries are inherently “authored on purpose”; they ignore the lock flag because they’re already locked by definition.)

When to use. Domain logic that’s specific, custom action chains an LLM won’t get right, security-sensitive validations.

Behaviour. Survives every Pattern B/C/D re-run. Disappears only when you explicitly delete the field.

The bridge codegen’s fallback when Layer 2 is absent. You don’t see B entries in the provenance map — they’re applied at codegen time, not stored.

When to use. Skeleton features you don’t want to author yet. The bridge emits TODO log stubs / list-view scaffolds / empty scenario files. Schema-valid; not executable until filled.

The Pattern B table for the SenLang bridge:

KindBehavior absent → bridge emits
featureevents[<slug>] = [{ $do: "log", level: "warn", msg: "TODO from @atomprd/codegen-senlang ..." }]
ui_view (viewType=list + rendered_by)Box + Heading + List + $Each over state.<entitySlot> + empty fallback Text
ui_view (viewType=form + entity)TextInput per field + Submit Button
ui_view (viewType=detail + entity)Heading + Text rows per field
ui_view (viewType=dashboard|wizard|custom)Heading + TODO Text stub
rulePreserved in rules[<slug>] but not bound
criterionScenario emits when[] = [{ dispatch: "__todo_<id>" }], then{} = {}
fixtureLegacy fixture.payload used as scenario.given.state directly

Deterministic. Same input → same output. Requires linked atoms with Layer 2 already filled in. Free, no LLM call.

When to use.

  • feature.behavior.steps from criterion.behavior.before_after + fixture.behavior.api_calls.
  • criterion.behavior.before_after from a feature’s already-defined steps (reverse direction).
  • ui_view.behavior.screens from rendered_by entity + viewType template.
  • rule.behavior.enforced_at from uses_rule reverse edges to features.
  • fixture.behavior.before_after from linked_criteria array.

Lower confidence than A; higher than D (no creative guessing). Each inferred field carries confidence: 0.85 by default.

Calls OpenRouter (Claude Sonnet 4.6 default) with a kind-specific system prompt + your Layer 1 prose + the atom’s current metadata. The model emits JSON matching the Layer 2 schema; the validator rejects shapes that don’t fit before they’re applied.

When to use.

  • Brand-new feature with no linked criterion yet.
  • Filling in details that are hard to derive deterministically (UI section layouts, action-chain branching).
  • “Inspire me” mode when stuck.

Costs ~$0.001-0.01/call. Cached at sha256(prompt + system + model + version) by AiCache so repeated prompts on the same atom hit cache. Lower confidence than A/C; review the diff before applying.

Every C/D suggestion goes through a diff modal before applying. Once you trust a field, click 🔒 in the Provenance tab to mark it lockedByUser: true. Subsequent re-runs of C / D will:

  1. Compute the suggested value.
  2. For each field path in current provenance with lockedByUser: true, keep the current value.
  3. Merge.

Lock things like:

  • A custom action chain you wrote by hand (Pattern A — already locked, but explicit lock doesn’t hurt).
  • A step that depended on a specific business invariant.
  • A specific endpoint URL the LLM keeps mangling.
graph TD
  classDef a fill:#dcfce7,stroke:#16a34a,color:#0a0a0a;
  classDef b fill:#dbeafe,stroke:#3b82f6,color:#0a0a0a;
  classDef c fill:#fef3c7,stroke:#f59e0b,color:#0a0a0a;
  classDef d fill:#f1f5f9,stroke:#64748b,color:#0a0a0a;

  L1[Fill Layer 1 — userStory] --> Decide{Linked atoms<br/>have Layer 2?}
  Decide -- yes --> InferC[Re-infer Pattern C]:::c
  Decide -- no --> SuggestD[Suggest with AI Pattern D]:::d
  InferC --> Diff[Diff modal: apply or cancel]
  SuggestD --> Diff
  Diff -- apply --> Lock[Lock fields you trust]
  Lock --> Done[Layer 2 ready for codegen]:::a
  Diff -- cancel --> L1
  L1 -. skip Layer 2 .-> B[Codegen falls back to B at gen time]:::b

The lock + iterate dance is the whole point of the four-pattern split. You hand-author 20% of the structure (the parts that matter), automate 80%, and review the diff. Re-running C / D leaves your locked fields alone.