Master's log-backed session-title capability already titles sessions durably (deterministic fallback in the spine, optional model providers). Remove the TUI's own autoTitle generation — the latch, prompt, cap, and llm stream call — and keep the terminal rename: the TUI folds the logged title on mount and sets '<session title> — <configured title>' on every accepted session/title event. The tui-agent example and the scripted PTY fixture mount session-title-first-message-llm so titles stay model-made; the scripted adapter's tool-less branch now answers that provider's auxiliary request. See .agents/notes/implemented/simplification/2026-07-22-tui-titles-from-session-title-service.md
5.3 KiB
Agent Note: Auto-titled terminal from the first message
Status: implemented
English | 中文
Superseded by the session-title consolidation Agent Note: the TUI-local
autoTitlegeneration is removed; titles come from the log-backed session-title service, and the terminal rename consumessession/titleevents.
Superseded for the default and the resume behavior by the auto-title default-on Agent Note:
autoTitlenow defaults on, and a resumed session re-derives its title from the stored first message instead of keeping the static one. The OSC 0 path, the one-shot latch, the model-summary shape, the fire-and-forget call, and every failure fallback below stand.
Problem
The TUI's terminal title is a single static string (title, default DeepSeek Harness) shared by every session. A user who runs one agent per tmux pane or terminal tab sees the same label on all of them, so panes are indistinguishable at a glance and the tab bar carries no signal about what each session is doing.
Decision
TuiConfiggains anautoTitleboolean (defaultfalse). When it is on, the TUI issues one background model call after the first user message of a fresh session and replaces the terminal title with a short, model-generated label; the statictitleis the pre-title and the fallback.- The label is a model summary, not a truncation of the prompt. The request carries a fixed task instruction (summarize the request as a short title of two to five lowercase words, no punctuation) plus the user's first message and no tools; the TUI takes the first non-empty line of the reply and caps it at 40 characters (39 plus an ellipsis).
- The title is set through
runtime.terminal.setTitle, the same OSC 0 path the statictitlealready uses. No new terminal-control surface is introduced, and pi-tui keeps ownership of terminal writes. - The call is fire-and-forget and one-shot per session. A
titleSettledlatch guards it: withautoTitleoff it is pre-settled and never runs; on a resumed session whose firstuser/messageis already logged it is pre-settled so the static title stands; a whitespace-only first message is skipped without consuming the slot. Any failure, an empty reply, a missingllmservice, or a missing agent provider/model leaves the static title untouched. A dedicatedAbortControllercancels an in-flight request on shutdown. - The title call reaches
ctx.llm.streamdirectly rather than throughagent.send, so it never appends to the session or transcript and cannot perturb the agent loop. - The feature defaults off and is enabled only in the interactive product config (
examples/tui-agent/cordis.yml) and the scripted PTY fixture. Enabling it in the shareddsh-tui-demoschema default would fire an extra model call in keyless replay and boot scenarios that send no user message.
Alternatives considered
Truncate the first user message instead of a model title. Rejected: the user chose a short model-made label; a truncated raw prompt is noisy, often begins with boilerplate, and rarely reads as a title.
Rename the window (OSC 2) or the tmux window. Rejected: OSC 0 sets only pane_title, so it labels the pane without renaming or leaking into the user's window title; the user confirmed OSC is the right lever.
Default the feature on. Rejected: enabling it in the shared demo schema perturbs keyless replay and boot snapshots and spends a model call on every fresh session; opt-in per deployment keeps the default surface inert.
Fold this into the log-backed session-title work (PR #451). Rejected: that change is session metadata persisted to the log; this is a terminal label with no persistence. Keeping them independent leaves each self-contained and avoids a shared dependency.
Block the first turn until the title resolves. Rejected: awaiting the title before sending the user's message adds latency to the actual request; fire-and-forget makes the rename invisible to the turn.
Consequences
- When enabled, a fresh session spends one extra, tool-less model call with a single short user message and a few output tokens; off by default, it costs nothing.
- Because the title call stamps
sessionId, it shares the session'sllm-replaycursor: enablingautoTitlein a replay-backed snapshot scenario would consume a recorded script entry. This is why the default is off and the scripted PTY fixture answers the call with a tool-branching adapter rather than replay. packages/ui/tui/tests/tui.spec.tspins the behavior with a mockllmadapter: a generated title replaces the static one, over-long output is truncated with an ellipsis, a whitespace-only first message keeps the one-shot slot, empty or failing replies leave the title, a resumed session never fires, and the feature-off / no-service / missing-provider / missing-model paths keep the static title. A shutdown test asserts the in-flight request is aborted.examples/tui-agent/tests/tui-keyless-smoke.e2e.tsproves the real Loader-booted path: the scripted adapter answers the tool-less title call with a fixed string, and the conversation scenario asserts the OSC 0 sequence reaches the PTY. Boot scenarios send no user message, so they never fire the call.