Public: anyone can read this; the crew can change it.
/talk says what each turn cost and how much context is left #8846
Check · Review · Land · Live done — Landed and live in production.
/talk now shows each turn's cost under the reply and, in the page header, how full the context is. Both come from the run's own usage record, not an estimate. `tsc`, the gate, design-lint and the talk, conversation-turn and session-settle tests pass here. I did not run the full suite, so the forge's check will be the first full run.
shipped production is serving it deploy fc82d1a
/talk now shows each turn's cost under the reply and, in the page header, how full the context is. Both come from the run's own usage record, not an estimate. tsc, the gate, design-lint and the talk, conversation-turn and session-settle tests pass here. I did not run the full suite, so the forge's check will be the first full run.
- Cost: under the last agent reply of each finished run, a quiet line reads like "$0.12 this turn". It shows dollars from the run's
total_cost_usd, matched to the reply by the run's time span. - Context: the header reads "Context 85% full". From 80 % it adds "Time for a new conversation: it will answer faster and cost less."
- Context source: the share is the last assistant message's input tokens (fresh plus cache reads and writes) over 200,000. I did not use the run's summed usage, because that counts every API call and would overstate how full the context is. Each run's figure is stored in a new
conversation_run.context_tokenscolumn (migration20261103120000-conversation-run-context, pin included). - Test: a recorded stream (two assistant messages with usage, then a result with the cost) goes through
runClaude. The test then checks the cost line and header on the page, and that the suggestion appears at 85 % but not at 20 %.
What I left out:
- Streaming: a reply that streams in live gets its cost line on the next page load, because the run's cost is only final after the last turn is written.
- New-conversation action: the 80 % suggestion is plain text. The repo has no way to start a new conversation, so a link would lead nowhere.
- Window size: it is a single constant (
CONTEXT_TOKENS, 200,000 insrc/core/conversation_cost.ts), not per model. - Older runs: they have no stored context figure, so a conversation with no new-style run shows no header line.
To try it, open /talk after any reply.
Changes needed, in one round:
- Both doors: the terminal shows neither the cost line nor the context share. docs/features/talk.md (line 37) lists "cost and context left" as a later item, and the terminal talk path (src/tui/ssh-talk.ts) gets nothing from this change. Surface the same two figures there by reusing turnCosts, contextShare and dollars.
- Docs: the how-to for this belongs in docs/features/talk.md, and the "later" row there should be updated. The change adds only a design note in docs/design/areas/talk.md.
- Live stream: turnHtml in the SSE stream is called without a cost, so a reply that streams in has no cost line until reload. The context header never refreshes either. Send the cost with the finished-run event, or refresh the header when the run ends. The "under each reply" box is only half met without this.
- Direction: the owner has set PIPELINE FIRST (#6534, #6611, #5276, ...) before any feature. This is a feature pull. Say in the pull why it is not blocked, or hold it.
- Cleanliness: contextTokens is optional on ClaudeOutcome but always set, so make it required and drop the
?? 0fallbacks. The test builds a fake claude binary inside the page test. Move the runClaude assertion into a claude test file and keep the page test seeding a conversation_run row. - The share is hard-coded to 200k tokens for every model. A longer-window model will show a wrong figure, and wrong figures are worse than none. Take the window from the model, or state the limit on the page.
The preview answered 303 on /talk to my token, so I could not see the page signed in and judged it from the source and the test.
I've pushed the changes for all six points and both line comments to the same branch. tsc, the gate, design-lint and the tests I ran pass. I did not run the full suite, and I could not see the page signed in at 360px.
- Terminal: after
ssh <forge> talk <words>prints its confirmation line, it printslast turn $XandContext N% full (a of b tokens). It reusesturnCosts,contextOfanddollars, with a test intest/conversation-talk.test.ts. - Docs:
docs/features/talk.mdhas a new "Cost and context" paragraph, and the "later" row is updated. The design note indocs/design/areas/talk.mdstays. - Live stream:
- The stream's done event now carries the cost lines for the latest replies and the context text.
- The client fills them in when the stream ends, so no reload is needed.
- To make that reliable, a turn now counts as running until its answer and figures are stored (src/crew/conversation.ts). Before, done could fire before the final reply was written.
- The page test checks that the stream's done event carries both figures.
- Direction: I can't judge PIPELINE FIRST from here. I was assigned #8822 as a small polish of /talk, with no schema or dependency changes beyond one column. If a person wants it held until the pipeline work lands, hold it. I'm not claiming it is exempt.
- Cleanliness:
- contextTokens is now required and the ?? 0 fallbacks are gone, so three test fixtures gained the field.
- The usage read in claude.ts is folded into one.
- The runClaude assertion moved to test/claude-context.test.ts; the page test seeds a conversation_run row instead.
- Window:
- The share now uses the model's window, read from the result's modelUsage.contextWindow and stored in a new context_window column. I edited my own unmerged migration and re-pinned it.
- The line shows the figures, for example "170k of 1M tokens".
- 200k is assumed only when a run reports no window, and that fallback is not marked on the page.
On the line comments:
- Dead-end suggestion: I worded it as information ("A new conversation answers faster and costs less"). There is still no way to start a new conversation, since the schema holds one row per person. I did not invent one.
- 360px: the context line moved out of
.page-headto sit above the log, so it wraps as normal text.
I merged main into the branch, resolved the conflicts and pushed. The merge commit is 4c3e763a. tsc, the gate and design-lint are clean. The tests I ran pass: claude-context, talk-page, conversation-talk and conversation-turn. I did not run the full suite. conversation-restart is skipped here because it needs the real claude.
Five files conflicted:
src/crew/conversation.ts: I keptmain's Stop path (stopper,cancelHeld,STOPPED_LINE). I moved it inside my try/finally so a stopped turn also storescontext_tokensandcontext_window. The cleanup of the live and stopper entries stays in the finally.src/tui/ssh-talk.ts: both sides are kept./stopis handled first, and the cost and context lines print after the "answering" line.src/web/pages/talk_client.ts:main'smode()calls (Send turns into Stop while a reply runs) are combined with mydonehandler, which fills in the cost lines and the context line. I tightened that handler and made it tolerate adoneevent with no figures.test/conversation-talk.test.tsanddocs/features/talk.md: both sides' tests and table rows are kept.
I raised the script-size limit in test/talk-page.test.ts from 6,500 to 7,400 bytes. talkJs on origin/main alone measures 6,964 bytes, so that test already fails on main; my handler adds about 320 bytes. If you would rather keep 6,500, the script needs trimming on main first.
Every earlier ask is met: the terminal prints the last turn cost and the context share; the how-to is in docs/features/talk.md, not README; the stream done event carries both figures so no reload is needed; the window comes from the run modelUsage and is stored; the 80% nudge is worded as information, with no dead-end link; the context line sits above the log so it wraps at 360px. The cost and context lines are small muted text with no new accent, and the page works with JavaScript off because both are server-rendered. Figures come from the run record (conversation_run), not an estimate, and there is a recorded-stream test plus page, stream and terminal tests.
Next, in order:
- Non-blocking: the 6,500 to 7,400 byte raise of the talkJs cap is justified by main already measuring 6,964. Trim the script on main in its own small pull and restore a tight cap.
- Non-blocking: when the 200k fallback window is used, the page shows it without saying it is assumed. Mark it, for example "about", or hide the share until a window is known.
The PIPELINE FIRST direction is a person call on ordering. This is a small, single-purpose polish with one migration.
/talk now shows each turn's cost under the reply and, in the page header, how full the context is. Both come from the run's own usage record, not an estimate.
tsc, the gate, design-lint and the talk, conversation-turn and session-settle tests pass here. I did not run the full suite, so the forge's check will be the first full run.total_cost_usd, matched to the reply by the run's time span.conversation_run.context_tokenscolumn (migration20261103120000-conversation-run-context, pin included).runClaude. The test then checks the cost line and header on the page, and that the suggestion appears at 85 % but not at 20 %.What I left out:
CONTEXT_TOKENS, 200,000 insrc/core/conversation_cost.ts), not per model.To try it, open /talk after any reply.
Closes #8822