The ask
I want to be able to delete my last message, and any last message — in case I say something wrong
or it picked up noise and did something based on that. And a voice command like "scrap last
message" so I can do it by voice instead of deleting manually.
Today
DELETE /v1/instances/:id/messages is clear-the-whole-thread (the console's Clear chat,
InstanceDetail.tsx:800). There is no per-message delete anywhere.
- The voice vocabulary is
repeat | mute | unmute | exit | next — no undo.
So the only way to remove one bad turn is to destroy the entire conversation.
Filed as one ticket, two triggers
The button and the voice command are the same operation invoked two ways — the same reasoning that
made #277 ("next") and #279 (transfer) share one switch primitive. Building them separately would
produce two delete paths that disagree about what a "turn" is. Happy to split if preferred.
Delete the TURN, not the message
A user message never stands alone: the assistant's reply and the tool-log system message are
written in the same turn and are meaningless without it. Deleting one line would leave an answer to
a question nobody asked.
Unit of deletion = the exchange: the user message, its assistant reply, and the tool-call system
message from that turn.
The part that must not be got wrong
Deleting the message does not undo what it caused.
This is the whole motivation — "it picked up some noise and did something based on that" — and the
naive implementation makes it worse. If noise triggered start_work, removing the transcript
leaves the run going, with nothing left on screen explaining why the agent is working. That is #251
inverted: there the side effects persisted while the record vanished by accident; here we would do
it deliberately.
So deleting a turn must:
- Say what it caused. The tool calls are already recorded in the turn's system message and in
agent_events; the confirmation should name them ("this turn started run 4a03… and wrote 2
memory entries").
- Offer to cancel what is still running. A live loop run has
requestCancel already.
- Not silently orphan durable effects — a
write_memory, a created ticket, a filed GitHub
issue. Deleting the message cannot retract those, and pretending otherwise is the failure mode.
If nothing can be undone, say so plainly and let the user delete anyway. The goal is an honest
record, not a fake one.
Soft delete, with a window
The undo needs its own undo. The voice trigger especially: "scrap that" arrives as a voice turn,
so the same noise that caused the problem can plausibly produce the command — and #332 is the
standing evidence that silence transcribes into real-looking phrases.
Mark deleted, hide from the thread and from the model's context, purge after a short window (and
purge the audioKey blob with it, as Clear chat already does). A "Deleted — undo" affordance for a
few seconds costs nothing and makes both directions recoverable.
The voice command
Add scrap to VoiceCommand with the usual treatment: a SCRAP_BY_LANG phrase table, custom-word
override, and the existing whole-utterance or trailing-phrase matching so "we should scrap that
approach" stays a message.
Phrases worth including: "scrap that", "scrap last message", "disregard that", "delete that",
"ignore that".
Note #334: normalizeTranscript does not strip hyphens, so "scrap-that" would fail to match. Fix
that first or this command inherits the same intermittency.
Why it matters beyond tidiness
The thread is the context. A phantom turn is not just visual clutter — it is fed back to the
model on every subsequent turn, so a noise message keeps influencing answers until the conversation
rolls over. Deleting it is the only way to stop that.
Verification
- Deleting a turn removes the user message, the reply and the tool log together.
- The confirmation names any durable effects, and offers to cancel a live run.
- A deleted turn is absent from the next request's context.
- "Scrap that" as a whole utterance deletes; "scrap that approach" does not.
- Undo restores the turn intact, including its audio, within the window.
The ask
Today
DELETE /v1/instances/:id/messagesis clear-the-whole-thread (the console's Clear chat,InstanceDetail.tsx:800). There is no per-message delete anywhere.repeat | mute | unmute | exit | next— no undo.So the only way to remove one bad turn is to destroy the entire conversation.
Filed as one ticket, two triggers
The button and the voice command are the same operation invoked two ways — the same reasoning that
made #277 ("next") and #279 (transfer) share one switch primitive. Building them separately would
produce two delete paths that disagree about what a "turn" is. Happy to split if preferred.
Delete the TURN, not the message
A user message never stands alone: the assistant's reply and the tool-log system message are
written in the same turn and are meaningless without it. Deleting one line would leave an answer to
a question nobody asked.
Unit of deletion = the exchange: the user message, its assistant reply, and the tool-call system
message from that turn.
The part that must not be got wrong
Deleting the message does not undo what it caused.
This is the whole motivation — "it picked up some noise and did something based on that" — and the
naive implementation makes it worse. If noise triggered
start_work, removing the transcriptleaves the run going, with nothing left on screen explaining why the agent is working. That is #251
inverted: there the side effects persisted while the record vanished by accident; here we would do
it deliberately.
So deleting a turn must:
agent_events; the confirmation should name them ("this turn started run4a03…and wrote 2memory entries").
requestCancelalready.write_memory, a created ticket, a filed GitHubissue. Deleting the message cannot retract those, and pretending otherwise is the failure mode.
If nothing can be undone, say so plainly and let the user delete anyway. The goal is an honest
record, not a fake one.
Soft delete, with a window
The undo needs its own undo. The voice trigger especially: "scrap that" arrives as a voice turn,
so the same noise that caused the problem can plausibly produce the command — and #332 is the
standing evidence that silence transcribes into real-looking phrases.
Mark deleted, hide from the thread and from the model's context, purge after a short window (and
purge the
audioKeyblob with it, as Clear chat already does). A "Deleted — undo" affordance for afew seconds costs nothing and makes both directions recoverable.
The voice command
Add
scraptoVoiceCommandwith the usual treatment: aSCRAP_BY_LANGphrase table, custom-wordoverride, and the existing whole-utterance or trailing-phrase matching so "we should scrap that
approach" stays a message.
Phrases worth including: "scrap that", "scrap last message", "disregard that", "delete that",
"ignore that".
Note #334:
normalizeTranscriptdoes not strip hyphens, so "scrap-that" would fail to match. Fixthat first or this command inherits the same intermittency.
Why it matters beyond tidiness
The thread is the context. A phantom turn is not just visual clutter — it is fed back to the
model on every subsequent turn, so a noise message keeps influencing answers until the conversation
rolls over. Deleting it is the only way to stop that.
Verification