state-machine-design
Skill32 starsChanged 7 days agoMITView on GitHub
Use when reviewing or fixing async protocols, retries, ACK/NACK handling, pending flags, timeout logic, or state-machine lockups in embedded firmware. NOT for generic network protocol design (TCP/HTTP/MQTT) unless targeting embedded firmware stack.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- definite-d/agent-skillstheir own version, first seen Jul 31, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- definite-d/agent-skills★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- c-cpp-devSkill
- debug-methodologySkill
- embedded-firmware-devSkill
- embedded-workbenchSkill
- fact-checkSkill
- hardfault-triageSkill
- keil-mdk-buildSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 7 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Do not trust a low-level send return value as proof of delivery when an application-layer ACK exists.
- Distinctive: line 19
When adding retries, also define attempt timestamps, backoff rules, and cleanup paths so the state machine cannot lock up silently.
- Distinctive: line 20
If pause, stop, or reconnect can interrupt the normal flow, add an explicit recovery or re-drive branch instead of assuming the old path …
- Distinctive: line 24
When a state transition depends on multiple preconditions, verify every one explicitly at the transition gate.
- Distinctive: line 25
If a target state's preconditions can become false while already in that state, define a reverse transition back to the source state.
- Distinctive: line 141
All core rules in one function: timeout gated only when work is pending, explicit retry count, predefined max retries, all exits defined.
- Distinctive: line 190
When diagnostics point to an architecture-level or state-machine design defect, proactively offer high-level remediation focused on …
- Very specific: line 192
REQUIRED SUB-SKILL: If you find a state machine bug, also load Skill("debug-methodology") to apply structured root-cause analysis.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<HARD-GATE>
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.
</HARD-GATE>State Machine Design
For a design that has to be decided rather than reviewed:
dsh-model-everythingverifies the machine itself —model_verifyexplores every interleaving for unreachable states, deadlock, a timeout that can never fire, and a lock held across an await;model_compose_verifydecides two mechanisms interacting, including a lost update on a shared location. Load itsmodel-everythingskill when the plugin is installed. This skill stays the implementation guide: the C code, the flags, the timeouts.
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 < 500) return; // 500ms cooldown
retry_tick = 0;
rt->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 < ARRAY_LEN(comm_state_table); i++) {
if (g_comm_runtime.state == comm_state_table[i].state
&& comm_state_table[i].process != NULL) {
comm_state_table[i].process();
return;
}
}
// Unknown state: reset to safe default
g_comm_runtime.state = COMM_STATE_INIT;
}Pattern C: ACK Timeout With Explicit Retry Limit
All core rules in one function: timeout gated only when work is pending, explicit retry count, predefined max retries, all exits defined.
static void comm_ack_check(uint32_t now_sec) {
// GUARD: timeout logic only runs when there is real pending work
if (!g_comm.report_in_progress) return;
// GUARD: timeout hasn't expired yet
if (elapsed_sec(g_comm.send_time, now_sec) < COMM_ACK_TIMEOUT_S) return;
// Timeout fired. Explicit retry branch:
if (g_comm.retry_count == 0) {
g_comm.retry_count++;
g_comm.send_time = now_sec;
comm_send_report(); // One automatic retry
return;
}
// All retries exhausted → terminal exit
g_comm.report_in_progress = false;
g_comm.retry_count = 0; // Reset for next cycle
comm_report_result(false); // Notify caller: failed
}Anti-Patterns
// BAD: implicit state via flags — new flag creates untested state combinations
if (g_flags.busy && !g_flags.paused && g_flags.online) { ... }
// Fix: use explicit enum — exactly one valid state at all times
// BAD: idle state triggers timeout — retry fires with nothing pending
if (elapsed_ms(t0, now) > 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").
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

