Skip to content

feat(chat): continue a stopped answer, and ask a bad one again - #153

Merged
mrsibe merged 1 commit into
mainfrom
feat/continue-retry
Sep 27, 2026
Merged

mrsibe merged 1 commit into
mainfrom
feat/continue-retry

Conversation

@mrsibe

@mrsibe mrsibe commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Closes #151, the second half of #143 and the last child of epic #138.

A stopped, truncated or failed answer is kept and explained (#142). Until now that was a dead end: the reader could see what arrived and had no way to get the rest.

Two actions, two meanings

  • Continue writes into the same answer. The part that arrived seeds the new turn — on both sides, Main and the renderer, from the same record — so the message grows instead of being replaced by its continuation. The prompt is the partial answer plus an instruction to carry on from it.
  • Retry opens a sibling: a new row whose attempt_of points at the first attempt of the group, so a third attempt joins the same group rather than nesting under the second, and the attempt that failed is left exactly as it was.

The buttons appear only where they can help: canRecover excludes blocked — the provider refused, and an identical request would be refused the same way.

The record change (option (a))

One column, chat_messages.attempt_of, migration 0018 — a single additive ALTER TABLE. Additive, queryable, and the same grouping that "regenerate" and multi-branch answers will need, so it is built once.

It is deliberately not a foreign key: deleting one attempt must not take its siblings with it, and a dangling id is only ever used to group rows that live and die with the same session (they are all in one session, which cascades as a whole).

The prompt is rebuilt, not remembered

replayPrompt takes the history up to and including the question, so a retry asks the same thing in the same context instead of continuing a conversation that has moved on. Two details that took a moment to get right:

  • the cut is made at the group's first attempt, not at the one being retried — otherwise re-asking a retry would feed the previous attempt's answer back in as context;
  • retrieval moved out of send-message into one helper, because the third caller is what makes the extraction worth it. The failure semantics are unchanged: a failed search is recorded as failed and never blocks the answer.

Verification

npm test 340 pass, 0 fail — 4 new
npm run typecheck clean
npm run check:design no violations
npm run build bundles
npm run build:unpack + smoke:packaged PASS — 24 checks, one of them new: attempt_of round-trips through the packaged database
npx eslint <changed files> 0 errors (2 pre-existing no-explicit-any in chatHandlers.ts; the two new handlers use unknown[])

New coverage: the manager links a sibling to the group's first attempt, and a continuation extends the same message without creating a row; the renderer appends to what is on screen rather than starting a second message, and adds a sibling that points at its question while leaving the failed attempt untouched.

Where the epic stands

With this merged, every Definition-of-Done item in #138 is satisfied — statuses are persisted and rendered, the terminal outcome is set exactly once, the renderer is told after the write, a simulated provider stream is driven end to end, and the four checks pass. #143's parent issue closes with its two halves (#150 merged, this one) and #138 closes with it.

Closes #151, the second half of #143 and the last child of #138.

A stopped, truncated or failed answer is kept and explained (#142), and until now that
was a dead end: the reader could see what arrived and had no way to get the rest.

Two actions, two meanings:

- **Continue** writes *into the same answer*. The part that arrived seeds the new turn —
  on both sides, Main and the renderer, from the same record — so the message grows
  instead of being replaced by its continuation, and the prompt carries the partial
  answer plus an instruction to carry on from it.
- **Retry** opens a **sibling**: a new row with `attempt_of` pointing at the *first*
  attempt of the group, so a third attempt joins the same group rather than nesting
  under the second, and the attempt that failed is left alone.

`attempt_of` is option (a) from the issue: one column, additive, queryable, and the same
grouping that "regenerate" and multi-branch answers will need. It is deliberately not a
foreign key — deleting one attempt must not take its siblings with it, and a dangling id
is only ever used to group rows that live and die with the same session. Migration
`0018` is a single `ALTER TABLE`.

The prompt for both is **rebuilt, not remembered**: the history up to and including the
question, so a retry asks the same thing in the same context instead of continuing a
conversation that has moved on. That required reading retrieval out of `send-message`
into one helper — the third caller is what made the extraction worth it — and the
cut is made at the group's first attempt, so re-asking a retry does not include the
previous attempt's answer.

The buttons appear only where they can help: `canRecover` excludes `blocked`, because the
provider refused and an identical request would be refused the same way.

Verified: `npm test` 340 pass (four new: the manager links a sibling to the group's first
attempt, a continuation extends the same message and creates no row, the renderer appends
to what is on screen, and the renderer adds a sibling that points at its question),
`npm run typecheck`, `npm run check:design`, `npm run build`, `npm run build:unpack` +
`smoke:packaged` — which also round-trips `attempt_of` through the packaged database
(24 checks).
@github-actions github-actions Bot added the enhancement New feature or request label Sep 27, 2026
@mrsibe
mrsibe merged commit a04dfa6 into main Sep 27, 2026
4 checks passed
@mrsibe
mrsibe deleted the feat/continue-retry branch September 27, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] Recover a bad answer: Continue vs Retry, with sibling attempts

1 participant