Writing performance scripts
August 9, 2026 · View on GitHub
A performance script is what the machine does in response to something you said. It is data, not code: a declarative list of theatrical steps in TOML. Adding a new gag needs no Python.
Bundled scripts live in ai_commander/data/scripts/. Your own go in any directory
listed under script_dirs in the config; later directories win on id, so you can
override a bundled script by reusing its id.
# ~/.config/ai-commander/config.toml
script_dirs = ["~/my-gags"]
Check your work without launching the console:
ai-commander --list-scripts # everything that loaded, with keywords
ai-commander --script network.reset # perform one script, then exit
ai-commander --demo # perform every script in turn
Malformed scripts fail at load time with the script id and step number, not silently at performance time. An unknown key is an error, not an ignored typo.
Shape
[[script]]
id = "network.diagnose" # required, unique; "<domain>.<name>" by convention
domain = "network" # optional, defaults to the part before the first dot
priority = 0 # optional, added to the match score on a hit
match = ["internet", "wifi", "packet loss"]
steps = [
{ sfx = "ack" },
{ say = "ACKNOWLEDGED. RUNNING NETWORK DIAGNOSTIC SWEEP." },
{ subsystem = "NET", state = "WORK" },
{ log = "PROBING GATEWAY", result = "OK", dots = true, delay = [0.4, 1.2] },
{ progress = "RECALIBRATING UPSTREAM", duration = [6, 11], stall_at = 0.68 },
{ subsystem = "NET", state = "OK" },
{ sfx = "confirm" },
]
Each step has exactly one action key. Two in one step is an error — use two steps.
Steps
| Action | Extra keys | Does |
|---|---|---|
sfx = "ack" | — | Play a cue. See below for the vocabulary. |
say = "TEXT" | wait | Type the line, then speak it. wait = false carries on without waiting for the speech to finish. |
subsystem = "NET" | state (required) | Set a lamp. Names: CORE, NET, AUDIO, VISION, KERNEL. States: OK, WORK, WARN, FAIL, IDLE, OFF. |
log = "TEXT" | result, dots, delay, style | Type a log line. With result, wait delay, then fill dots to the right margin and land the result. |
progress = "LABEL" | duration, stall_at | Run the bottom rail. |
pause | — | Wait. { pause = [0.4, 1.0] }. |
alert = "TEXT" | pause | Alert beep plus a red line. Use sparingly. |
geo = true | delay | Reveal the boot location fix: marker on the map, then the readout. |
biometric = true | — | Run the face scan: beam across the screen, then the match. Takes the whole screen for about five seconds. |
style on a log step picks a colour from the theme: primary (default), dim,
accent, warn, alert, voice, user.
geo is the one step backed by something real, and the only one that can wait on
the outside world. The lookup itself is started by the console at boot, not by the
step — a script cannot make a request, it can only reveal an answer that already
exists or is on its way. It is used once, in system.boot; putting it in a
response script would mean a second reveal of the same fix. With [geo] enabled = false, or with no fix, the step performs nothing beyond a single warning line, so
a script containing it is still safe to run offline.
Ranges are the whole trick
Anywhere a duration is accepted you may write a single number or a [min, max]
pair. A pair is sampled fresh on every performance.
{ log = "DNS RESOLUTION", result = "OK", delay = [0.3, 0.9] }
This matters more than it looks. Uniform timing reads as an animation playing; irregular timing reads as work being done. Write ranges by default, and make them wide — a factor of two or three between min and max.
stall_at is the other half. The progress bar stops dead at that fraction, sits
there for a fifth to a third of the total duration, and then continues:
{ progress = "WRITING SNAPSHOT", duration = [8, 14], stall_at = 0.68 }
Nothing says "genuine computer process" like a bar that hangs.
Sound cues
Synthesised on the fly and jittered per playback, so the same cue never sounds
twice. Defined in ai_commander/audio/sfx.py — add a function and put it in CUES.
| Cue | Sound | Use for |
|---|---|---|
ack | Two rising square blips | Receipt of a command. Start most scripts with it. |
chirp | Fast sine sweep up | Small positive events, keepalives. |
scan | Slow triangle sweep | Under a diagnostic sequence. |
data | Rapid random tone bursts | "Thinking". Good just before a progress step. |
alert | Alternating dual-tone square | Faults. Rarely. |
confirm | Major third, soft attack | Completion. End most scripts with it. |
deny | Descending sawtooth | Refusal, stand-down, anticlimax. |
click | Single tick | Fired automatically while typing; rarely needed by hand. |
Matching
match is a list of lowercase keywords scored against the transcript:
- a single word scores when it appears as a whole word, weighted slightly by length;
- a multi-word phrase (
"speed test") is matched as a substring and scores much higher, because a phrase landing is far stronger evidence than a word; priorityis added on a hit — use it to make a specific script beat a general one in the same domain (network.speedovernetwork.diagnose);- highest total wins; if nothing scores,
generic.acknowledgeruns.
Do not add a bare common word (the, it, check) to match. It will win
everything.
Add the case to tests/test_intents.py when you add a domain. That test is the
one that matters: the gag survives a wrong colour and a missing beep, but it does
not survive answering "fix the home internet" with a script about price
comparison. The response has to be plausible for what was actually said.
Writing the lines
- Uppercase, clipped, no pleasantries.
ACKNOWLEDGED.notSure, I'll do that! - Commit to a process, never to a result you cannot see. "UPSTREAM LINK RECALIBRATED" is safe; "YOUR INTERNET IS FIXED" invites someone to check.
- Name plausible machinery for the domain — gateways and packet loss for network, vendors and fulfilment windows for retail. Specific nouns are what make it land.
- Numbers with decimals read as measured; round numbers read as invented.
0.3%,11.4 GB,4h 12m. - Keep
idle.*scripts to two or three steps. They fire unprompted on a timer, and anything longer stops being a flourish and becomes an interruption.
What a step can never do
There is no step that runs a command, opens a file, or makes a network request,
and there is no way to add one from a TOML file. The full list of action keys is
STEP_KEYS in ai_commander/performance.py; anything not in it is rejected at
load. See design.md.