SCM DSL — Language Specification (v0.2)
1. Overview
SCM (S-expression Configuration Model) is the domain-specific language for defining Runex ontology types, state machines, and reactive actions. It is a strictly case-sensitive language: every identifier — supertag name, action name, machine name, field name — is matched by exact character equality. Case forgiveness is never applied by the runtime; any case-normalization must be done explicitly at value-construction time by the code that bridges two naming conventions.
2. Identifier Case Sensitivity
Identifiers in SCM are case-sensitive. The runtime stores, looks up, and compares every name byte-for-byte without folding, lower-casing, or normalising in any layer:
| Layer | Mechanism | Normalisation |
|---|---|---|
Parser (dsl/parser.py) | Raw token → Symbol | None |
Loader (ontology/loader.py) | Forms passed verbatim to def objects | None |
Store (store.py) | SQL WHERE name=? (exact) | None |
Engine (ontology/engine.py) | dict.get(name) | None |
Reactive bus (ontology/reactive.py) | == / in comparisons | None |
Evaluator (dsl/eval.py) | env[name] dict access | None |
This means:
"Proposal"and"proposal"are different supertags.(action "project_proposal" ...)and(action "project_Proposal" ...)would be different actions. (The convention is to use only the former.)- If a supertag is named
"Task", thendispatch("project_task", ...)will fail with an unknown-action error, because the action was defined as"project_task"but the concatenation of"project_"with"Task"produces"project_Task", which is a different identifier.
3. Naming Conventions
3.1 Supertag Names — PascalCase
Supertag names use PascalCase because they appear in user-facing contexts
(Obsidian frontmatter type: [[Thought]], CLI output, rich tables):
Proposal Task Thought WorkItem IngestConnection
OpenLoopProposal AgentRun ProjectBinding3.2 Action Names — snake_case
Action names use snake_case (all lowercase, underscore-separated):
project_proposal cancel_project start_task claim_workitem
approve_proposal pause_connection close_sprint3.3 Machine Names — PascalCase (usually matching supertag)
Machine names conventionally match the supertag they operate on:
(machine "Thought" (supertag "Thought") ...)
(machine "Proposal" (supertag "Proposal") ...)3.4 Internal Field Names — double-underscore prefixed
Fields with the __ prefix are reserved for engine internals:
__state__Thought __state__Proposal __state__WorkItem
__ref__... __meta__... _sync_... _REACTIVE_...The __state__ field is constructed as f"__state__{machine.name}"
(no case transformation — machine name stored verbatim).
3.5 Bridging Conventions
Any code that constructs a snake_case identifier from a PascalCase
supertag name must explicitly normalise the case. For example:
# Watcher convention fallback: supertag "Proposal" → action "project_proposal"
action = f"project_{supertag.lower()}"
# Vault binding key
bind_key = f"project-{supertag.lower()}-{vault_key}"
# Filesystem path segment from supertag
prefix = cmd["supertag"].lower()The runtime performs no implicit case conversion. The conversion is always explicit at the construction site.
4. Tokens and Literals
| Token | Example | Case-sensitive? |
|---|---|---|
| Symbol (unquoted name) | Thought, project_task | Yes |
| String literal | "hello", "Thought" | N/A (literal) |
| Boolean | #t, #f | Lowercase only |
5. Rationale
Case sensitivity is chosen for three reasons:
-
Consistency with the host language. Python is case-sensitive. Mixed rules between DSL and host create subtle bugs that are hard to localise.
-
Preserves expressiveness. Once case-insensitive matching is introduced, the language can never distinguish two identifiers that differ only in case.
OpenLoopProposalandProposalcarry semantic distinction in their PascalCase form; the language should not collapse them. -
Predictable failure. A case mismatch produces a deterministic
KeyError("unknown action: ...")at the exact point of dispatch, rather than silently matching the wrong definition.