Skip to Content
文档DSL 规范

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:

LayerMechanismNormalisation
Parser (dsl/parser.py)Raw token → SymbolNone
Loader (ontology/loader.py)Forms passed verbatim to def objectsNone
Store (store.py)SQL WHERE name=? (exact)None
Engine (ontology/engine.py)dict.get(name)None
Reactive bus (ontology/reactive.py)== / in comparisonsNone
Evaluator (dsl/eval.py)env[name] dict accessNone

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", then dispatch("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 ProjectBinding

3.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_sprint

3.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

TokenExampleCase-sensitive?
Symbol (unquoted name)Thought, project_taskYes
String literal"hello", "Thought"N/A (literal)
Boolean#t, #fLowercase only

5. Rationale

Case sensitivity is chosen for three reasons:

  1. Consistency with the host language. Python is case-sensitive. Mixed rules between DSL and host create subtle bugs that are hard to localise.

  2. Preserves expressiveness. Once case-insensitive matching is introduced, the language can never distinguish two identifiers that differ only in case. OpenLoopProposal and Proposal carry semantic distinction in their PascalCase form; the language should not collapse them.

  3. Predictable failure. A case mismatch produces a deterministic KeyError("unknown action: ...") at the exact point of dispatch, rather than silently matching the wrong definition.

Last updated on