Install
$ agentstack add skill-amethystluna-embedded-workbench-state-machine-design ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
This is a domain implementation skill. If you are planning, designing, or entering plan mode — load Skill("embedded-workbench") first to activate the workflow gates (Plan Verification Gate, Approval Gate, Closure Gate). Domain skills carry implementation guidance, not workflow enforcement.
State Machine Design
Core Rules
- Fix the state model, not the symptom. Every in-progress or pending flag must have explicit success, failure, timeout, and reset exits.
- Timeout logic must be gated on real pending work. Idle states must not trigger retry, recovery, or error transitions.
- Do not trust a low-level send return value as proof of delivery when an application-layer ACK exists. Use the protocol's completion signal.
- When adding retries, also define attempt timestamps, backoff rules, and cleanup paths so the state machine cannot lock up silently.
- If pause, stop, or reconnect can interrupt the normal flow, add an explicit recovery or re-drive branch instead of assuming the old path will naturally resume.
Transition Gates
- When a state transition depends on multiple preconditions, verify every one explicitly at the transition gate. Do not rely on implicit assumptions (e.g., "the timer expired, therefore everything must be healthy"). A single unchecked precondition is the most common source of silent state corruption.
- If a target state's preconditions can become false while already in that state, define a reverse transition back to the source state. One-way state latches without fallback paths will eventually leak incorrect state to downstream consumers.
Transient Tolerance
- Distinguish between genuine state-changing events and transient perturbations during mode switches, direction reversals, or re-initialization windows. The latter need a tolerance or grace window; only the former should advance the state machine or increment error counters.
Implementation Patterns
Pattern A: Per-State Handlers + Unified Error Gate
Each state gets its own handler function. The dispatcher is a pure switch(state). A unified fault-threshold check runs after all state handlers — no handler triggers the error transition itself. This keeps handlers simple and fault logic centralized.
// === State enum: exactly one valid state at all times ===
typedef enum {
COMM_STATE_INIT,
COMM_STATE_IDLE,
COMM_STATE_SAMPLE_STARTING,
COMM_STATE_SAMPLING,
COMM_STATE_ERROR,
COMM_STATE_RECOVERING,
} comm_state_t;
// === Runtime context: all flags explicit in one struct ===
typedef struct {
comm_state_t state;
uint32_t command_fail_count;
bool data_ready;
bool communication_lost;
} comm_runtime_t;
// === Per-state handlers: each reads only what it needs ===
static void comm_handle_idle(comm_runtime_t *rt) {
rt->warmup_start_time = sys_tick();
comm_start_sample();
rt->state = COMM_STATE_SAMPLE_STARTING;
}
static void comm_handle_error(comm_runtime_t *rt) {
static uint32_t retry_tick = 0;
if (retry_tick == 0) {
retry_tick = sys_tick();
rt->data_ready = false;
}
comm_power_off();
if (sys_tick() - retry_tick state = COMM_STATE_RECOVERING;
}
static void comm_handle_recovering(comm_runtime_t *rt) {
comm_handle_initializing(rt); // Recovery RE-USES init — no duplicated paths
}
// === Dispatcher: pure switch, single exit ===
static void comm_state_process(comm_runtime_t *rt) {
switch (rt->state) {
case COMM_STATE_INIT: comm_handle_initializing(rt); break;
case COMM_STATE_IDLE: comm_handle_idle(rt); break;
case COMM_STATE_SAMPLE_STARTING: comm_handle_sample_starting(rt);break;
case COMM_STATE_SAMPLING: comm_handle_sampling(rt); break;
case COMM_STATE_ERROR: comm_handle_error(rt); break;
case COMM_STATE_RECOVERING: comm_handle_recovering(rt); break;
default:
rt->state = COMM_STATE_INIT; // Unknown state → safe fallback
break;
}
// Unified error gate: checked AFTER every state, not buried inside handlers.
// A new state cannot accidentally bypass this check.
if (rt->command_fail_count >= COMM_MAX_FAILS) {
rt->state = COMM_STATE_ERROR;
rt->command_fail_count = 0;
rt->communication_lost = true;
}
}
Key properties:
- Fault logic is centralized — the error gate runs exactly once, after every state. New states cannot bypass it.
- Recovery reuses init —
comm_handle_recovering()callscomm_handle_initializing(). No duplicated paths to drift apart. - All exits are explicit —
Errorhas a cooldown period (500ms), then transitions toRecovering. No fall-through, no implicit assumption. - Unknown state → safe fallback — the
defaultcase resets toInit.
Pattern B: Function-Pointer Table Dispatch
Heavier than switch-case, but useful when states are added/removed frequently or handlers need different signatures.
static const struct {
comm_state_t state;
void (*process)(void);
} comm_state_table[] = {
{COMM_STATE_INIT, comm_init_process},
{COMM_STATE_IDLE, comm_idle_process},
{COMM_STATE_CONNECTED, comm_connected_process},
{COMM_STATE_ERROR, comm_error_process},
{COMM_STATE_RECOVERING, comm_recovering_process},
};
void comm_state_dispatch(void) {
for (size_t i = 0; i TIMEOUT) { retry(); }
// t0 is always running, even when no work is in flight
// BAD: retry loop with no exit condition
void retry_forever(void) {
while (!send_packet()) { delay(100); } // Will lock up if HW is dead
}
// BAD: recovery path duplicates init logic instead of reusing it.
// The copy drifts over time — one path gets a fix, the other doesn't.
// BAD: Error handler directly calls power_off() without cooldown period.
// Power-cycling faster than the hardware spec causes unpredictable state.
When To Escalate
- When diagnostics point to an architecture-level or state-machine design defect, proactively offer high-level remediation focused on boundary clarity, lifecycle contracts, and reversible transitions — don't just propose ad-hoc runtime patches.
REQUIRED SUB-SKILL: If you find a state machine bug, also load Skill("debug-methodology") to apply structured root-cause analysis. If the bug involves async lifecycle flags or hardware events, load Skill("embedded-firmware-dev"). If the state machine lockup triggers a watchdog reset or HardFault, load Skill("hardfault-triage").
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: AmethystLuna
- Source: AmethystLuna/embedded-workbench
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.