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 FooBarexamples in02-templates.md,06-overrides-and-modding.md, and07-structured-template-data-and-callables.md. - An §8.5 "Action slots" subsection in
06-overrides-and-modding.mdthat documents a concept never carried into the policy/activity model. - An entire design doc still titled
09-ai-and-programs.mdwhose body has been rewritten inactivity/policyterms but whose filename, header, and a stale paragraph still sayprogram. - A
programkeyword fallback paragraph in04-behavior.mdand an unseeded fallback path inPolicyExecutor.cs. - Typed-ID columns in
SECS-Compiler-Plan.mdandTASKS.mdthat still listActionIdnext toActivityId. - Scattered comments and test variable names that say
actionwhereactivityis meant.
Leaving the residue in place causes three concrete problems:
- 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.
- The future SECS compiler has to choose what to do with a
programoractionkeyword. 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). - AI code generation and search both regress. Grep for "action" returns dozens of hits across legacy text, current C#
Action<T>delegates, and the ValenarActionSpeedstat. 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:
| Status | Term | Meaning |
|---|---|---|
| Canonical | activity | A runnable behavior with a typed body, typed args, lifecycle hooks, slot binding, and an optional on_action metadata id. |
| Canonical | policy | A chooser that picks among activities (priority list, AI weight, Choose<T> callable). |
| Removed | action | Earlier 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. |
| Removed | program | Earlier 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 only | on_action | A 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
activityas 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, andPolicy. - 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 termson_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:
ActionSpeed— a Valenar game stat declared inexamples/valenar/Generated/Declarations.csandexamples/valenar/Content/characters/stats/movement.secs. It is an in-game stat name analogous to "MoveSpeed" or "AttackSpeed" and has no relationship to the legacyactionkeyword.- 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. inject_or_createandreplace_or_create— committed mod operation keywords fromdocs/design/06-overrides-and-modding.md § 3. They contain the substring_or_createand have nothing to do withaction. The grep guard must not match these as if they wereactiontokens.on_actionkeyword,OnActionId,OnActionDeclaration,FireOnAction, and theOnAction*C# type family that implements them (OnActionDeclarations, etc.) — metadata / extension-point system described above.- Plain English
actionablein prose (e.g., "this is actionable feedback"). Free natural-language use is fine; the single tokenactionas 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.mdenumerates the wording rules in normative form. - The CI guard
scripts/check-behavior-vocabulary.shwalks the live roots and fails when a forbidden term appears outside the allowlist. - The path-scoped rule
.claude/rules/behavior-vocabulary.mdactivates 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 slotsis deleted.09-ai-and-programs.mdis renamed09-ai-policies-and-activities.mdand itsprogramreferences are rewritten.02-templates.mdlegacyactionexamples becomeactivityexamples.00-overview.md'sNeedDeclarationexample replacesTargetStatIdwith the current canonical name.07-structured-template-data-and-callables.mddropsactionfrom its special-construct list.SECS-Compiler-Plan.mdandTASKS.mdtyped-ID lists dropActionId.examples/valenar/docs/implementation/pr-wave-template.mdandexamples/valenar/docs/implementation/pr-roadmap.mddropActionId.- The engine comment in
src/SECS.Engine/Pipeline/TickContext.csis rewritten. tests/SECS.Engine.Tests/ActivityExecutorTests.csrenames itsRecordingActivitylocal fromactiontoactivity.
Wave 3 (engine, follow-up)
- The unseeded fallback path in
PolicyExecutor.csis removed; policy execution becomes a single deterministic seeded path with no silent default.
Long-term
- The
secs-roslynparser will not gainactionorprogramkeywords. Phase 1 of the compiler plan (template) and subsequent phases declare only the committed source forms fromSECS-Compiler-Plan.md § Source keyword whitelist and grammar status: declaration keywords such asscope,contract,template,channel,field,modifier,system,event,on_action,activity,policy, plus the committed mod operations. A top-levelformulakeyword 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 --checkexits 0 against the wave's recorded baseline.
Alternatives considered
- Keep
actionandprogramas 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_actionas 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 § 3—inject/replace/inject_or_create/replace_or_createmod 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.