Action Compose¶
ControlAction¶
@dataclass
class ControlAction:
knob: str # "K", "alpha", "zeta", or "Psi"
scope: str # "global" or "layer_{n}"
value: float # target value or delta
ttl_s: float # time-to-live in seconds
justification: str
Valid knobs: {"K", "alpha", "zeta", "Psi"}.
ActuationMapper¶
Maps ControlAction to domain-specific actuator commands.
Initialised with list[ActuatorMapping] from the binding spec:
actuators:
- name: coupling_knob
knob: K
scope: global
limits: [0.0, 5.0]
- name: damping_knob
knob: zeta
scope: global
limits: [0.0, 2.0]
map_actions(actions) returns a list of command dicts:
[
{
"actuator": "coupling_knob",
"knob": "K",
"scope": "global",
"value": 0.5, # clipped to limits
"ttl_s": 10.0,
}
]
Values are clamped to [limits[0], limits[1]]. Scope matching: a global action maps to all actuators for that knob. A layer-scoped action maps only to matching-scope actuators.
validate_action(action) returns True if the action's knob is valid and value is within limits for at least one matching actuator.
ActionProjector¶
Rate-limits and clips control actions relative to the previous value:
projector = ActionProjector(
rate_limits={"K": 0.1, "alpha": 0.05, "zeta": 0.2},
value_bounds={"K": (0.0, 5.0), "alpha": (-3.14, 3.14)},
)
projected = projector.project(action, previous_value=0.45)
- Clamp value to
[lo, hi]. - If
|value - previous| > rate_limit, cap the delta atrate_limit. - Re-clamp after rate limiting.
Returns a new ControlAction with the projected value.
Composition Order¶
SupervisorPolicy.decide()
→ list[ControlAction]
→ ActionProjector.project() (per action)
→ ActuationMapper.map_actions()
→ list[dict] (actuator commands)
→ Domain-specific execution
Positioning in an operations stack¶
This is the boundary where policy intent becomes safe executable action.
By keeping this path explicit, teams can independently inspect each stage:
- Which knobs were requested.
- Which constraints were applied.
- Which actuators received each command.
That auditability is valuable because policy rules, safety limits, and actuator interfaces evolve at different cadences. This module keeps those cadences ordered and reviewable.
Why this shape matters¶
ControlActionenforces a compact, typed command vocabulary.ActionProjectormakes transient oscillation shocks less likely to create unsafe actuator jumps.ActuationMappercentralizes domain adaptation so supervisory logic stays platform-agnostic.
When this interface is respected, a policy change can be tested in replay, simulated in the same composition path, and only then promoted with full context and command traces.
References¶
Rate limits and value bounds are empirical — see ASSUMPTIONS.md § Rate Limits. Knob definitions follow the Sakaguchi–Kuramoto model documented in knobs_K_alpha_zeta_Psi.md.
Production interpretation¶
- This is the canonical place where high-level intent is converted into bounded, auditable actuator commands.
- Layer scope and knob validation are both policy boundaries: they prevent accidental cross-layer bleed-through.
- Projection + mapping order is intentional; it ensures every downstream command is clamped and constrained before domain adapters execute.