Back to Feed
article 4m read

Pausing my TV now takes a ticket number, one lock and a second look

The way the house assistant controls the TV got rebuilt: the planner can only suggest a registered action, the browser holds nothing but an ID, one locked claim stops double presses, and a run counts as done only when a fresh read shows the TV changed. Plus the lamp request that could have been routed to the TV.

Article DepthPractice

Flow

The path a suggestion takes before the TV moves

  1. 1Pick from a short list

    The planner names an action and fills its blanks, nothing more.

  2. 2Store a proposal

    Details, the TV's state, who asked, and an expiry stay on the server.

  3. 3Run by ID only

    Anything else in the body is refused; other sessions get not found.

  4. 4Check again

    Still valid, not expired, caller allowed, box online.

  5. 5One locked claim

    The first click sends the key; later clicks get the same run.

  6. 6Read the TV back

    Verified only if a fresh read shows it changed; otherwise timed out.

The endpoint behind my TV controls was a thin pipe. The browser sent a target and a command, and the server checked the command against a list and passed the keypress to the streaming box. That's fine when I'm the one tapping. It's not fine once an assistant writes the request.

So this week I rebuilt the path from "pause the living-room TV" to a paused TV around one rule: whatever reads my sentence may suggest an action, and the server decides the rest, including whether it worked.

There's no language model in it yet. The planner is a set of text patterns that only know the TV. The point this round was the guard rails, not the smarts.

A short action list and an ID-only run button

The planner can only pick an action from a short list and fill in the blanks that action allows. It can't decide risk, who's allowed, or whether something worked. Right now the list is short: press one remote key, or open the media app.

The server stores each suggestion as a proposal: the details, a note of what the TV was doing, who asked, and an expiry a few minutes out. The browser gets a label and an ID, never the details.

So the run button can only send the ID.

The run request refuses to carry anything except the ticket, and it is rejected if it has a body.

Before, whoever wrote the request picked the device and the key. Now there's nothing to change. A test tries to swap the room and send power-off, and the box gets nothing. A proposal also belongs to the browser session that made it. Any other session gets "not found". When I hit run, the server checks again: still valid, not expired, I'm allowed, and the box is online.

Double clicks on a toggle

Pause is a toggle, so pressing it twice undoes it. Double clicks happen. The server claims the run inside one locked database update. The first click sends the key. Every later click gets the same run back and sends nothing. A test hammers one proposal with many claims at once and expects one winner.

When the box's yes counts

The box saying "got it" is not the TV pausing. A run is "accepted" when the box takes the command. It's "verified" only when a fresh read shows the TV changed: playback flipped, the power state matches, or the right app is in front. If proof doesn't come before a short deadline, the run ends as "timed out." If the database is down, the server says so instead of faking success.

When old TV talk beat the lamp

My first planner decided "is this about the TV?" by scanning the last few messages. It was a sloppy shortcut. Ask about the TV, then ask to turn off the lamp, and the old TV talk still won. As far as I can tell, "turn off the lamp" would have offered me a button to turn off the TV.

Then I changed it so the newest message wins. If it names a lamp, plug or fan, the TV planner steps aside. Only a bare "turn it off" looks back at earlier messages. Both cases have tests.

Volume keys and the crash window

Volume and mute can't be checked, because the box doesn't report them reliably. So they end as "timed out" even if the TV did get quieter. "Sent, can't check" would be a more honest label.

If the server crashes between claiming a run and pressing the key, the run just times out and a retry won't send. I still can't tell a run that never went out from one the box ignored.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Related idea

Failed uploads the cleanup couldn't find, so the replacement I built mostly refuses to delete things

A failed upload would have left its partial archive behind because the cleanup only learned the file's name from a return value. I fixed that, then built a scheduled cleanup that decides what it must keep before it deletes anything, checks every target on its own, uses a lock that dies with its owner, and retries an upload exactly once, only when it runs out of disk space or quota.

via #safety