Skip to content

Commit e4bc697

Browse files
authored
docs(rfd): Add RFD 053 for auto-refresh conversation titles (#461)
RFD 053 proposes periodic background re-titling of conversations that have accumulated a configurable number of new turns since their title was last generated. The core design introduces a `turn_interval` threshold (default 5) under `conversation.title.generate.auto_refresh`. When a `jp query` run starts, a background `TitleRefreshTask` scans all conversations, identifies stale candidates (those where `turn_count >= baseline + turn_interval`), and re-runs LLM title generation for up to `batch_size` of them — sorted by least recently activated to avoid churning active conversations. Key design decisions: - A new `title_generated_at_turn` field on `Conversation` tracks the turn count when the title was last auto-generated, serving as the staleness baseline. - A `retain_current` field in the title generation schema lets the LLM signal that the existing title is still accurate, advancing the checkpoint without replacing the title. - `turn_context` (default 10) caps how many recent turns are sent to the LLM, keeping costs predictable for long conversations. - Manual `conversation edit --title "..."` writes a `ConfigDelta` with `turn_interval = 0`, opting that conversation out of auto-refresh. - The full pipeline — scanning, loading, LLM calls — runs off the critical path and respects `CancellationToken` to avoid delaying CLI exit. RFD 020 is also updated to reflect that `get_conversation_mut` (not just `get_events_mut`) requires a `&ConversationLock`, as the title refresh sync phase needs to mutate conversation metadata under the same locking invariant. Signed-off-by: Jean Mertz <git@jeanmertz.com>
1 parent af70d0d commit e4bc697

3 files changed

Lines changed: 449 additions & 10 deletions

File tree

docs/.vitepress/rfd-summaries.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"summary": "Abandoned RFD split into RFD 048 and RFD 049; preserved for historical context on non-interactive mode design."
7373
},
7474
"020-parallel-conversations.md": {
75-
"hash": "81206c64fcf58682afb91178c17bfc945489ea89ecc380428798a5fa1a197724",
75+
"hash": "75ceb2a99600c700dc1fd304961cc5fdd5cdbe3391017b515a1496bab49caf9e",
7676
"summary": "Replace global active conversation with per-session tracking and add conversation locks for parallel terminal work."
7777
},
7878
"021-printer-live-redirection.md": {
@@ -206,5 +206,9 @@
206206
"052-workspace-data-store-sanitization.md": {
207207
"hash": "199bf135a50791b21bf4c5ef6028012334cef5f8be9acb9cea14c54f1f461621",
208208
"summary": "Workspace sanitization validates and repairs corrupted conversations, trashing broken data with recovery explanations."
209+
},
210+
"053-auto-refresh-conversation-titles.md": {
211+
"hash": "42b4f764ddcb1a2f2177a2b9d61d56177b723ce40bba39038a0a19370f6c0dfd",
212+
"summary": "Auto-refresh stale conversation titles periodically by re-running LLM generation when accumulated turns exceed a configured threshold."
209213
}
210214
}

docs/rfd/020-parallel-conversations.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ pub struct ConversationLock {
302302
obtain a `ConversationLock` is through this method. The lock is acquired once at
303303
the start of `jp query` and lives in a long-lived scope (e.g., `Ctx`).
304304

305-
Mutable access to a conversation's event stream requires a reference to the
306-
lock:
305+
Mutable access to a conversation's event stream or metadata requires a reference
306+
to the lock:
307307

308308
```rust
309309
impl Workspace {
@@ -312,26 +312,35 @@ impl Workspace {
312312
id: &ConversationId,
313313
_lock: &ConversationLock,
314314
) -> Option<&mut ConversationStream> { /* ... */ }
315+
316+
pub fn get_conversation_mut(
317+
&mut self,
318+
id: &ConversationId,
319+
_lock: &ConversationLock,
320+
) -> Option<Mut<'_, ConversationId, Conversation>> { /* ... */ }
315321
}
316322
```
317323

318-
All existing mutation methods stay on `ConversationStream`. The only change is
319-
that `get_events_mut` (and `try_get_events_mut`) take a `&ConversationLock`
320-
parameter. Call sites add one argument:
324+
All existing mutation methods stay on `ConversationStream` and `Conversation`.
325+
The change is that `get_events_mut`, `try_get_events_mut`,
326+
`get_conversation_mut`, and `try_get_conversation_mut` take a
327+
`&ConversationLock` parameter. Call sites add one argument:
321328

322329
```rust
323330
// before
324331
workspace.try_get_events_mut(&cid)?.add_config_delta(delta);
332+
workspace.try_get_conversation_mut(&cid)?.title = Some(title);
325333

326334
// after
327335
workspace.try_get_events_mut(&cid, &lock)?.add_config_delta(delta);
336+
workspace.try_get_conversation_mut(&cid, &lock)?.title = Some(title);
328337
```
329338

330339
This enforces the lock-before-mutate invariant at the API boundary. You cannot
331-
call `get_events_mut` without proof that the process holds the lock.
332-
`ConversationLock` is held for the entire `jp query` run, so any `&mut
333-
ConversationStream` obtained through it is guaranteed to be protected by the
334-
lock for its entire lifetime.
340+
call `get_events_mut` or `get_conversation_mut` without proof that the process
341+
holds the lock. `ConversationLock` is held for the entire `jp query` run, so any
342+
mutable reference obtained through it is guaranteed to be protected by the lock
343+
for its entire lifetime.
335344

336345
The lock file is deleted in the `Drop` implementation of `ConversationLock`.
337346

0 commit comments

Comments
 (0)