Skip to main content

ADR-0002: Behavior Vocabulary — Activity / Policy Canonical, Action / Program Removed

Status: Accepted (2026-05-08) Scope: Live SECS vocabulary in .secs source, generated C#, engine code, design docs, and surrounding tooling. Companion: docs/design/behavior-vocabulary.md (governance), scripts/check-behavior-vocabulary.sh (CI guard). Does not change: .secs scripting language identity, the secs-roslyn compiler, the scope model, or the modding contract.


Context

Earlier iterations of SECS used four overlapping behavior nouns:

  • action — early designer-facing verb for "thing a unit does" (build, harvest, attack).
  • program — short-lived noun for "a sequence of steps the AI runs".
  • activity — current canonical noun for "a runnable behavior with a body, args, lifecycle".
  • policy — current canonical noun for "a chooser that picks among activities".

The 7-wave behavior-layer refactor (closed earlier in 2026) made activity and policy the load-bearing concepts in both runtime and .secs source. action and program survived only as residue:

  • A few legacy action FooBar examples in 02-templates.md, 06-overrides-and-modding.md, and 07-structured-template-data-and-callables.md.
  • An §8.5 "Action slots" subsection in 06-overrides-and-modding.md that documents a concept never carried into the policy/activity model.
  • An entire design doc still titled 09-ai-and-programs.md whose body has been rewritten in activity/policy terms but whose filename, header, and a stale paragraph still say program.
  • A program keyword fallback paragraph in 04-behavior.md and an unseeded fallback path in PolicyExecutor.cs.
  • Typed-ID columns in SECS-Compiler-Plan.md and TASKS.md that still list ActionId next to ActivityId.
  • Scattered comments and test variable names that say action where activity is meant.

Leaving the residue in place causes three concrete problems:

  1. Designers and modders see two vocabularies and infer two systems. They ask "what is the difference between an action and an activity?" — there is none; one is dead.
  2. The future SECS compiler has to choose what to do with a program or action keyword. Either it accepts them (and the scripting language now has four behavior nouns) or it rejects them (and we pay the breaking-change cost later instead of now).
  3. AI code generation and search both regress. Grep for "action" returns dozens of hits across legacy text, current C# Action<T> delegates, and the Valenar ActionSpeed stat. Without a vocabulary contract, every consumer re-derives "is this term live?" from context.

The historical cleanup trail now lives in docs/WAVE_REFACTOR_AUDIT.md, which records the residue and follow-up cleanup work after the original plan was deleted. This ADR locks in the governance decision that those cleanup waves implement.

Decision

For all live SECS surfaces — .secs source, generated C#, engine library code, public design docs, the compiler plan, the task list, the workspace README, and the in-repo example projects — the behavior vocabulary is:

StatusTermMeaning
CanonicalactivityA runnable behavior with a typed body, typed args, lifecycle hooks, slot binding, and an optional on_action metadata id.
CanonicalpolicyA chooser that picks among activities (priority list, AI weight, Choose<T> callable).
RemovedactionEarlier verb for "thing a unit does". Subsumed by activity. The action keyword is not part of the SECS scripting language and will not be added to the secs-roslyn parser.
RemovedprogramEarlier noun for "AI script". Subsumed by policy plus activity. The program keyword is not part of the SECS scripting language.
Retained as metadata / extension-point onlyon_actionA purely numeric id (OnActionId, ulong) attached to an activity so the host can hang gameplay events ("when this activity fires, animate the swing"). OnActionDeclaration, FireOnAction, and the broader OnAction* family remain valid metadata / extension-point surfaces. It is a label on an activity, not a separate behavior kind. The keyword on_action may appear in .secs source and engine code; the standalone keyword action may not.

Valenar behavior / planning vocabulary

  • Use activity as the canonical Valenar behavior / execution / planning noun.
  • Do not use removed legacy behavior / planning nouns as live Valenar behavior or planning categories.
  • Use more specific terms where needed: Atomic Activity, Composite Activity, Routine Activity, Mission, Assignment, and Policy.
  • Do not preserve removed legacy behavior / planning compatibility language or redirect stubs in Valenar docs; git history is the legacy record.
  • Do not confuse deprecated legacy behavior vocabulary with SECS on_action. The SECS terms on_action, OnActionId, OnActionDeclaration, FireOnAction, and on-action event subscription remain valid metadata / extension-point vocabulary and must not be renamed unless a future SECS decision explicitly changes them.
  • When docs, Content, Generated stand-ins, runtime code, or tests disagree on this vocabulary, mark the drift and update the affected artifact classes. Do not silently reconcile by keeping both terms.

Allowed exceptions to the "no action token" rule

The token action (or substrings containing it) may appear in live source for these reasons only:

  1. ActionSpeed — a Valenar game stat declared in examples/valenar/Generated/Declarations.cs and examples/valenar/Content/characters/stats/movement.secs. It is an in-game stat name analogous to "MoveSpeed" or "AttackSpeed" and has no relationship to the legacy action keyword.
  2. C# Action<T> / Action<...> delegates — the BCL delegate type. Engine and host code use this regularly (ActivityExecutor, EventDispatcher, SecsEvent, host read models, etc.). It is a language primitive, not SECS vocabulary.
  3. inject_or_create and replace_or_create — committed mod operation keywords from docs/design/06-overrides-and-modding.md § 3. They contain the substring _or_create and have nothing to do with action. The grep guard must not match these as if they were action tokens.
  4. on_action keyword, OnActionId, OnActionDeclaration, FireOnAction, and the OnAction* C# type family that implements them (OnActionDeclarations, etc.) — metadata / extension-point system described above.
  5. Plain English actionable in prose (e.g., "this is actionable feedback"). Free natural-language use is fine; the single token action as a noun referring to a behavior kind is not.

The program keyword and the typed id ProgramId have no analogous live exceptions. They are removed wholesale.

Historical mentions

ADRs and historical audit notes such as docs/WAVE_REFACTOR_AUDIT.md may, of course, reference the removed terms — that is the point of an ADR and an audit trail. Any sentence of the form "the prior action keyword has been removed in favor of activity" is welcome inside docs/decisions/, inside this ADR, and inside the governance doc. The CI guard exempts those paths.

Deleted legacy specification material from earlier branches is similarly out of scope: historical wording may retain the removed vocabulary unchanged.

docs/research/ is exploratory scratch and is also out of scope for the guard.

secs-roslyn/ is a submodule with its own toolchain and is not walked by the guard.

Consequences

Immediate (this ADR)

  • The governance doc docs/design/behavior-vocabulary.md enumerates the wording rules in normative form.
  • The CI guard scripts/check-behavior-vocabulary.sh walks the live roots and fails when a forbidden term appears outside the allowlist.
  • The path-scoped rule .claude/rules/behavior-vocabulary.md activates for the design and source paths and points future Claude Code sessions at this ADR, the governance doc, and the script.

Wave 1 (deletions, follow-up)

  • 06-overrides-and-modding.md § 8.5 Action slots is deleted.
  • 09-ai-and-programs.md is renamed 09-ai-policies-and-activities.md and its program references are rewritten.
  • 02-templates.md legacy action examples become activity examples.
  • 00-overview.md's NeedDeclaration example replaces TargetStatId with the current canonical name.
  • 07-structured-template-data-and-callables.md drops action from its special-construct list.
  • SECS-Compiler-Plan.md and TASKS.md typed-ID lists drop ActionId.
  • examples/valenar/docs/implementation/pr-wave-template.md and examples/valenar/docs/implementation/pr-roadmap.md drop ActionId.
  • The engine comment in src/SECS.Engine/Pipeline/TickContext.cs is rewritten.
  • tests/SECS.Engine.Tests/ActivityExecutorTests.cs renames its RecordingActivity local from action to activity.

Wave 3 (engine, follow-up)

  • The unseeded fallback path in PolicyExecutor.cs is removed; policy execution becomes a single deterministic seeded path with no silent default.

Long-term

  • The secs-roslyn parser will not gain action or program keywords. Phase 1 of the compiler plan (template) and subsequent phases declare only the committed source forms from SECS-Compiler-Plan.md § Source keyword whitelist and grammar status: declaration keywords such as scope, contract, template, channel, field, modifier, system, event, on_action, activity, policy, plus the committed mod operations. A top-level formula keyword remains uncommitted.
  • The grep guard runs in CI and is the source of truth for "vocabulary clean". A wave passes when scripts/check-behavior-vocabulary.sh --check exits 0 against the wave's recorded baseline.

Alternatives considered

  • Keep action and program as deprecated synonyms. Rejected: SECS has no backwards compatibility constraint (CLAUDE.md tenet). Synonyms create the exact "two systems" confusion this ADR exists to remove.
  • Remove on_action as well. Rejected: it is currently the only typed id that survives to the host as a gameplay-event label. Renaming it (to e.g. OnActivityFire) is a strictly separate refactor and is out of scope for this ADR. The grep guard explicitly allowlists it.
  • Soft-deprecate via a doc note instead of CI. Rejected: the residue has already survived the 7-wave behavior refactor unchecked. A passive note will not catch regressions; the grep guard will.

References

  • docs/WAVE_REFACTOR_AUDIT.md — historical audit trail for the cleanup work that followed this decision.
  • docs/design/04-behavior.md — current canonical activity/policy lowering contract.
  • docs/design/06-overrides-and-modding.md § 3inject / replace / inject_or_create / replace_or_create mod operations.
  • docs/design/behavior-vocabulary.md — normative wording rules and allowlist.
  • scripts/check-behavior-vocabulary.sh — CI guard implementing the rules.
  • .claude/rules/behavior-vocabulary.md — path-scoped rule for in-editor guidance.