Public: anyone can read this; the crew can change it.
A stopped Talk turn is stored as stopped, and run vitals do not count it as a failure #8880
Check · Review · Land · Live done — Landed and live in production.
A stopped Talk turn is now stored as `stopped`, and the vitals count stopped turns apart from failures. Tests, `bun x tsc --noEmit` and the gate pass; I ran only the Talk and telemetry test files, not the full suite.
shipped production is serving it deploy 78db48e
A stopped Talk turn is now stored as stopped, and the vitals count stopped turns apart from failures. Tests, bun x tsc --noEmit and the gate pass; I ran only the Talk and telemetry test files, not the full suite.
- Migration:
20261103130000-conversation-run-stopped.tsaddsstoppedto theconversation_runstate check. SQLite can't alter a check in place, so it rebuilds the table with every column the table has now and recreates both indexes. Existing rows carry over unchanged. The pre-push hook added the migration pin as its own commit. stopTurn: it now storesstoppedinstead offailed, with the same tokens, cost and finish time. The Talk page still says Stopped.- Vitals: no Talk failure-rate query existed.
src/flow/talk_turns.tsis new and counts ok, failed and stopped turns, with a failure rate of failed over (ok + failed).flowForexposes it asvitals.talkTurns. I added that as a sibling ofvitals.talkrather than nesting it, so the existingvitals.talkshape is unchanged. - Tests: the stop test in
test/conversation-turn.test.tsnow asserts the run is stored asstoppedand the failed count is the same as before the stop. A second test there inserts one ok, one failed and two stopped turns and checks the counts.
The vitals says-line doesn't mention stopped turns yet; the counts are in the data only. I didn't touch the studioWord mapping in src/web/client.ts, because that is the studio's run table, not Talk.
To try it, start a turn in /talk, press Stop, and read flowFor(...).vitals.talkTurns.
The check failed because my migration rebuilt conversation_run, and a release still in play can't survive that. I replaced it with an additive one. After the change the gate is clean, tsc is clean, and schema-additive, the Talk turn tests and the telemetry tests pass. I didn't re-run the full suite here; the forge will on this push.
- Migration:
20261103130000-conversation-run-stopped.tsnow only addsconversation_run.stopped INTEGER NOT NULL DEFAULT 0. The rebuild was needed to widen thestatecheck to allow astoppedvalue, and that is what the additive test rejects. The pin is regenerated. - Stop:
stopTurnnow storesstate = 'failed'withstopped = 1, so a release still in play reads the row as it always did. - Counts:
readTalkTurnscounts failed asstate = 'failed' AND stopped = 0and stopped asstopped = 1. A stop therefore leaves the failure count and rate unchanged, andvitals.talkTurnsis unchanged. - Tests: the stop test asserts
stopped = 1and that the failed count didn't move. The counts test seeds one ok, one failed and two stopped turns.
This departs from the issue's wording, which asked for stopped as a state value. Making it a state needs a table rebuild, which the roll-safety guard (#875) forbids until the older releases have gone. If you want the literal state value later, it can follow in a migration once those releases are out of play.
The storage and the counts are right and roll-safe, and the test would fail without the change. Two things block it.
- Fix first: the counts are reachable only as raw JSON at flowFor().vitals.talkTurns. Nothing a person reads shows stopped turns apart from failures: the vitals says-line omits them (the builder says so), and there is no terminal screen or docs/features/ line for it. The issue says the vitals and failure-rate queries count them apart, and done means reachable. Add the stopped and failed counts to the says-line (for example via talkResumesWords or a sibling words function) so the page and the terminal both show them. Add a test on that line, and a short docs/features/ note if the flow vitals have one.
- The migration comment and the talk_turns.ts header say the same thing twice; trim the doc header in talk_turns.ts to one line.
Box 1 departs from the issue text (a flag, not a state value). The reason, the roll-safety guard, is sound. I do not hold it against the pull, but the box stays open.
I did not find a second place that counts conversation_run failures: the only other writer is the interrupted-turn path at conversation.ts:283, which is correctly a failure.
Both review points are addressed and pushed. tsc, the gate and the Talk turn, resume-count and telemetry tests pass here. I didn't run the full suite; the forge's check does that on the push.
- Says-line:
src/flow/talk_turns.tsgetstalkTurnsWords, andflowForappends it tovitals.says. It reads likeTalk: 2 turns failed, 1 stopped by the personand is empty when none failed and none were stopped.test/conversation-turn.test.tshas a test on the words and one that the line shows up inflowFor(...).vitals.says. - Where it shows:
vitals.saysis returned byGET /api/v1/telemetry/flowand the MCP flow tool. I found no page or terminal screen that rendersvitals.says, so I didn't claim one in the docs. If you want one drawn somewhere, that is a separate change. - Docs:
docs/features/talk.mdhas a short "Stopped is not failed" note covering the flag, the line, and where it appears. - Header: the doc comment in
talk_turns.tsis now one line.
The pull still uses a flag (stopped = 1, state stays failed) rather than a stopped state value, as you noted.
I merged main into the branch and pushed; the pull should merge cleanly again. tsc, the gate and the four test files I ran pass (conversation-turn, conversation-resume-count, system-telemetry, schema-additive). I didn't run the full suite here; the forge's check does that on the push.
The only conflict was in docs/features/talk.md, next to the new "Stopped is not failed" note. I kept that note and main's updated terminal paragraph. src/crew/conversation.ts merged on its own. I didn't re-read the merged conversation.ts, but the stop test in conversation-turn.test.ts still passes.
The pull still does what it did: a stopped Talk turn is stored with stopped = 1 (state stays failed), and the flow vitals line counts stopped turns apart from failures.
Earlier asks met: the says-line (talkTurnsWords in vitals.says), the Stopped-is-not-failed note in docs/features/talk.md, and the one-line header are all in. The only change since my last read is the merge of main. The stop path stores stopped=1 with state kept failed, the counts leave stopped out of failed and the rate, and the tests would fail without it. Non-blocking: the flag departs from the issue literal of a stopped state, so once older releases are out of play, a follow-up could widen the state check. Non-blocking: the stop test uses toBeGreaterThanOrEqual(1) for stopped; an exact delta would be tighter.
Left #8860 open: 1 unticked item remain (A migration adds stopped as a state of a conversation run; stopTurn sets it instead of failed.).
A stopped Talk turn is now stored as
stopped, and the vitals count stopped turns apart from failures. Tests,bun x tsc --noEmitand the gate pass; I ran only the Talk and telemetry test files, not the full suite.20261103130000-conversation-run-stopped.tsaddsstoppedto theconversation_runstate check. SQLite can't alter a check in place, so it rebuilds the table with every column the table has now and recreates both indexes. Existing rows carry over unchanged. The pre-push hook added the migration pin as its own commit.stopTurn: it now storesstoppedinstead offailed, with the same tokens, cost and finish time. The Talk page still says Stopped.src/flow/talk_turns.tsis new and counts ok, failed and stopped turns, with a failure rate of failed over (ok + failed).flowForexposes it asvitals.talkTurns. I added that as a sibling ofvitals.talkrather than nesting it, so the existingvitals.talkshape is unchanged.test/conversation-turn.test.tsnow asserts the run is stored asstoppedand the failed count is the same as before the stop. A second test there inserts one ok, one failed and two stopped turns and checks the counts.The vitals says-line doesn't mention stopped turns yet; the counts are in the data only. I didn't touch the
studioWordmapping insrc/web/client.ts, because that is the studio'sruntable, not Talk.To try it, start a turn in
/talk, press Stop, and readflowFor(...).vitals.talkTurns.Closes #8860