arizuko › concepts › live progress
live progress
Ask for something that takes real work and you get a checklist while you wait — one message that keeps rewriting itself, not a stream of separate “still working” pings. The list is the agent’s own plan. arizuko posts it on the agent’s behalf, so you seeing progress never depends on the model remembering to tell you.
what you see
One message lands as soon as the agent has a plan, and changes in place as the work moves:
⏳ ☑ Read the three log files
☐ Cross-check the timestamps
☐ Write the summary
… and a minute later, the same message, edited:
⏳ ☑ Read the three log files
⏳ Cross-checking the timestamps
☐ Write the summary
Three marks, and that’s the whole vocabulary:
- ☑ — done.
- ⏳ — working on it right now. If the plan supplies an in-progress phrasing (“Cross-checking the timestamps”), that’s what shows; otherwise you get the plain item text.
- ☐ — not started.
The leading ⏳ on the first line belongs to the message, not to an item — every status message carries it so a glance tells you the agent is still going.
why the agent can’t forget
The agent plans with the SDK’s TodoWrite tool. It writes that list to organise itself, not to report to you — it would write it whether anyone were watching or not. arizuko hangs a hook on that tool: every time the list changes, the hook reads the list the agent just wrote, renders it, and sends it. Nothing ever asks the model to report progress, so there is nothing for it to skip.
That is the whole point of the design. The two earlier attempts both leaned on the model choosing to narrate — a <status> block it had to remember to emit, then a skill that told it to post updates — and models apply instructions like that unevenly. Users got either silence for ten minutes or a wall of twelve separate plan/step/done messages. Progress is bookkeeping over a list that already exists; it should not be a judgement call.
one message, edited
The first update of a turn sends a new message. Every later one edits that same message. routd finds which message to edit by looking up the last status row it stored for the turn — there is no in-memory table of live messages, so a routd restart in the middle of a long turn doesn’t strand the checklist. It picks the message back up from the database and keeps editing.
channels that can’t edit
Email, WhatsApp and Reddit have no edit-a-sent-message operation. On those, each update arrives as a fresh message — the fallback is deliberate and only fires when the adapter reports the operation as unsupported.
An edit that fails for a real reason — expired token, adapter timeout, a 500 from the platform — surfaces as an error instead. It is never quietly re-posted as a new message, because a silent duplicate looks exactly like the ping-spam this replaced.
the checklist is never the answer
verb="status" in the messages table and are skipped when routd asks “has this turn replied yet?”. Without that exclusion a checklist would count as the agent’s reply and the real answer would be dropped on the floor.So the final answer always arrives as its own message, alongside the checklist — which by then shows every box ticked.
when you won’t see one
- The plan has one step. A one-item checklist is noise, so it isn’t sent. Two or more items earn a live message.
- The agent answers without planning. Short turns don’t need a plan and don’t make one.
A long turn that never planned still isn’t silent: roughly every 90 seconds the container nudges the agent for a one-line <status>, and that feeds the same one-message-edited path. It’s the floor, not the main route.
go deeper
The design and the two decisions that carry it: specs/5/24. The hook that renders the list lives in ant/src/todo-status.ts; the edit-or-send half is mcpSubmitStatus in routd/mcp.go. What happens when the turn dies before it can finish the list: retries.