Add session titles, /rename, and a type-to-filter resume picker - #326
Add session titles, /rename, and a type-to-filter resume picker#326mohitpaddhariya wants to merge 6 commits into
Conversation
Sessions now carry a human-readable title so they can be found again, the way ChatGPT and Claude name conversations. - Add a persisted session_title field to Session (in get_trajectory, reset on /new) plus a _title_user_set flag so an explicit rename is never clobbered by auto-titling. - New agent/core/title.py: generate_conversation_title asks the active model for a 3-6 word title (tiny token budget, no telemetry/billing impact) and falls back to a pure-Python slug of the first user message on any error. Long secret-like tokens are stripped before a title reaches disk. - Auto-title fires once, fire-and-forget, after the first completed turn, so a slow or failing title never blocks or breaks a turn. - Add the /rename <name> command and list it in /help. Part of huggingface#325.
Session logs were written to ./session_logs relative to the launch directory, so they scattered across folders and /resume only saw the current cwd's logs. - Add resolve_session_log_dir(config): config.session_log_dir > the ML_INTERN_SESSION_DIR env var > legacy ./session_logs if it already exists > XDG default ($XDG_DATA_HOME/ml-intern/sessions, defaulting to ~/.local/share/ml-intern/sessions). - Route save_trajectory_local, the /resume picker, and the failed-upload retry scanner through the one resolver so reads and writes never diverge. - Add a session_log_dir config field; keep ./session_logs as a back-compat fallback so existing checkouts are undisturbed. Part of huggingface#325.
Splice a slugified session title into the log filename so saved sessions are human-scannable: session_<slug>_<uuid8>_<timestamp>.json, falling back to the legacy session_<uuid>_<timestamp>.json when there's no usable title. - Add slugify() in title.py (secret-stripped, lowercase, length-capped). - Factor filename construction into Session._session_log_filename. - When a title is set (auto-title or /rename), rename the active log file in place via apply_title_to_local_file so even a single-turn session ends up titled, preserving the original timestamp and leaving no orphan/dup. - Keep the session_ prefix + .json suffix so the upload-retry glob matches. Part of huggingface#325.
Replace the 'type a session number' /resume prompt with a type-to-filter arrow-key picker (prompt_toolkit, no new dependency) and surface session titles, while fixing the listing's order and duplicate rows. - New agent/utils/session_picker.py: fzf-style picker — filter by title/preview/model, up/down to move, Enter to select, Esc to cancel, with a scrolling viewport for long lists. /resume <index|id|path> stays as a non-interactive fast path and headless fallback. - Show the session title in each row (preview fallback for older logs) and in the resume confirmation; skip slash-command first messages in previews. - Sort and display now use one canonical, tz-aware timestamp (session_end_time then start_time, mtime as tiebreaker/fallback), so the list is genuinely newest-first and labels never look scrambled. session_end_time is now tz-aware. - Dedupe the listing by session_id (newest kept) so a resumed-and-continued conversation — which forks the save path into a new-timestamp file — no longer shows up multiple times. Namespace the legacy no-session_id fallback so it can't wrong-merge. - /rename now persists immediately via Session.persist_title: rename the active log in place AND refresh the persisted session_title, or save a fresh titled log when none exists yet (e.g. right after a resume forked the path), so /resume reflects the new name without waiting for the next turn. - Carry session_title across resume. Closes huggingface#325.
|
Hi @lewtun and @akseljoonas, this PR (#326) implements #325 and includes:
The full write-up, diagrams, and screenshots are in the PR description, and the changes are covered by unit tests. Whenever you have a chance, I'd appreciate a review and merge consideration. Happy to make any adjustments if needed. Note: The red "Claude PR Review" check is expected to fail on this PR because it originates from a fork and only has read-only token permissions. It does not indicate a problem with the code or tests. |
- Auto-title race: snapshot a per-conversation epoch when spawning the fire-and-forget title task and bail if /new or /resume rotated the conversation during the title LLM call, so a stale title can't be stamped onto a different session. - Fire the auto-title trigger from the usage-threshold / YOLO / abandon completion paths too, so a first turn that paused for an approval still gets titled instead of staying permanently untitled. - Hold a strong reference to the title task so it can't be GC'd mid-await. - Scrub titles through redact.scrub_string on the auto-title path, the /rename path, and the persisted JSON/filename, closing the gap where AWS key ids, Bearer tokens, and NAME=value dumps reached disk via session_title. - Escape titles before rendering them as Rich markup. - Union-read a legacy ./session_logs alongside the resolved dir so pre-XDG-migration sessions stay visible in /resume, and drop the divergent cwd-relative directory defaults that bypassed the resolver. - Guard the title rename and trajectory save with a lock so a heartbeat save on the worker thread can't resurrect a pre-title log file.
|
Pushed a follow-up commit ( Titling
Secret stripping (the "secret-like tokens are stripped from titles" design point)
Storage (the "one resolver" design point)
Concurrency
Full unit suite passes locally (542). |
|
Claude finished @lewtun's task in 3m 19s —— View job Review — PR #326 (session titles, /rename, resume picker)No blocking issues — 1 P1, 4 P2. The concurrency/race concerns the author called out are genuinely handled: the epoch + session-id guard in P1 — Auto-title LLM call is unmetered, bypassing the usage/YOLO accounting
In practice the impact is small ( P2 — Orphaned older-timestamp files for resumed sessions are never cleaned
P2 — Resumed legacy (untitled) sessions can never auto-title
P2 — Picker viewport math counts entries, not rendered lines
P2 —
|
Summary
Implements #325. Makes local CLI sessions easy to name, find, and resume.
/rename+ auto-title — every session gets a short human-readable title (auto-generated from the conversation after the first turn, with a pure-Python fallback), and/rename <name>lets you set it explicitly../session_logsto an XDG path ($XDG_DATA_HOME/ml-intern/sessions), overridable via config orML_INTERN_SESSION_DIR, with a legacy fallback so existing checkouts are undisturbed.session_<slug>_<uuid8>_<timestamp>.jsoninstead of a bare UUID./resumeopens an arrow-key picker (filter by title/preview/model), shows titles, fixes the sort order, and dedupes a resumed conversation so it never appears twice./resume <index|id|path>still works as a fast path.How it works
1. Titling:
/rename+ auto-titleflowchart TD A["First user turn completes"] --> B{"Already titled<br/>or renamed?"} B -- no --> C["Auto-title: ask the model<br/>for a 3-6 word title"] C -- success --> T["session_title"] C -- "error / empty" --> D["Fallback: slug of<br/>first message"] D --> T B -- yes --> T RN["/rename name"] --> T T --> P["persist_title()"] P --> P1["rename log in place +<br/>refresh title inside the JSON"] P --> P2["or save a fresh titled log<br/>if none exists yet"]2. Storage:
resolve_session_log_dir(config)flowchart TD S0["resolve_session_log_dir(config)"] --> S2{"config.session_log_dir set?"} S2 -- yes --> U1["use it"] S2 -- no --> S3{"$ML_INTERN_SESSION_DIR set?"} S3 -- yes --> U2["use it"] S3 -- no --> S4{"./session_logs exists in cwd?"} S4 -- yes --> U3["use legacy ./session_logs"] S4 -- no --> U4["XDG: ~/.local/share/ml-intern/sessions"]3. Filenames
flowchart TD N0["save a session log"] --> N1{"has a usable title?"} N1 -- yes --> N2["session_<slug>_<uuid8>_<timestamp>.json"] N1 -- no --> N3["session_<uuid>_<timestamp>.json (legacy)"]4. Resume:
/resumeKey design points
session_end_time→session_start_time→ file mtime), tz-aware, so the list is genuinely newest-first and labels never look scrambled.session_id— a resumed-and-continued conversation forks the save path into a new-timestamp file; the listing collapses these to the newest entry so the same conversation never shows twice.persist_title()— setting a title (auto or/rename) renames the active log in place and refreshes the title stored inside the JSON, or saves a fresh titled log when none exists yet (e.g. right after a resume), so/resumereflects the new name immediately.resolve_session_log_dir) is used by every reader and writer, so reads and writes can never diverge.Testing
test_title.py,test_session_title.py,test_session_dir.py,test_session_filename.py,test_session_picker.py, plus additions totest_session_resume.py./rename, XDG resolution + precedence, titled/legacy filenames, in-place rename + title refresh, sort/display agreement, and dedupe (including not merging genuinely distinct sessions).Screenshots
Auto-generated title after the first turn
/resume— type-to-filter picker/rename— renaming a sessionCloses #325