mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add persisted whole-chat summary with background generation (#26657)
Adds a persisted whole-chat summary that backs the chat summary popover. A new nullable `chats.summary` column is populated in the background after a successful root-chat turn and pushed to clients via a new `chat_summary_change` watch event (distinct from `summary_change`, which is bound to `last_turn_summary`), so the popover reads `chat.summary` straight off the loaded `Chat` with no extra query. This is the data source for the popover and per-chat cost UI built in #26649; the popover can consume `chat.summary` once this lands (the field is nullable, so merge order does not matter). ## How it works - **Generation** runs in the existing successful-turn finalize hook, detached from the request so the user's turn is never blocked. A cadence gate generates the first summary after one completed turn, then regenerates every three turns, using the `chats.summary_generated_at` freshness marker. Generation reads compaction-aware history, renders it to a bounded plain-text transcript (short transcripts are skipped), and asks for a 1-3 sentence summary via structured output. Failures never clear an existing summary. - **Staleness** is guarded by `history_version` (mirroring `last_turn_summary`), so a background write racing a newer turn loses while worker lifecycle transitions cannot reject a fresh write. - **Model selection** uses the chat's configured model. ## Deferred to follow-ups - **Cost accounting**: the `chat_messages.cost_source` discriminator and summary/title usage recording were removed from this PR so summary persistence is not blocked by hidden accounting rows advancing `history_version`. Title usage recording stays on main's `InsertChatMessages` path. - **Model override**: deployment-wide summary generation model selection is split into #26803; the base feature always uses the chat model. ## Notes - Migration `000540` adds `chats.summary` and `chats.summary_generated_at`, and recreates `chats_expanded` to expose the new columns. - Root chats only; shared viewers pick up the summary on their next refetch (live watch events are owner-only). Refs #26649 --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Generated
+15
@@ -122,6 +122,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -218,6 +219,7 @@ Status Code **200**
|
||||
| `» root_chat_id` | string(uuid) | false | | |
|
||||
| `» shared` | boolean | false | | Shared is true when this chat's root chat has explicit user or group ACL entries. |
|
||||
| `» status` | [codersdk.ChatStatus](schemas.md#codersdkchatstatus) | false | | |
|
||||
| `» summary` | string | false | | Summary is the persisted whole-chat summary, generated in the background. It is nil until the first summary has been produced. |
|
||||
| `» title` | string | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
| `» warnings` | array | false | | |
|
||||
@@ -397,6 +399,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -490,6 +493,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -740,6 +744,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -887,6 +892,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -980,6 +986,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -1164,6 +1171,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -1257,6 +1265,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -1439,6 +1448,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -1532,6 +1542,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2283,6 +2294,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2376,6 +2388,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2883,6 +2896,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2976,6 +2990,7 @@ Experimental: this endpoint is subject to change.
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
|
||||
Generated
+7
-3
@@ -2164,6 +2164,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2257,6 +2258,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -2298,6 +2300,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `root_chat_id` | string | false | | |
|
||||
| `shared` | boolean | false | | Shared is true when this chat's root chat has explicit user or group ACL entries. |
|
||||
| `status` | [codersdk.ChatStatus](#codersdkchatstatus) | false | | |
|
||||
| `summary` | string | false | | Summary is the persisted whole-chat summary, generated in the background. It is nil until the first summary has been produced. |
|
||||
| `title` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
@@ -4042,6 +4045,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
"root_chat_id": "2898031c-fdce-4e3e-8c53-4481dd42fcd7",
|
||||
"shared": true,
|
||||
"status": "waiting",
|
||||
"summary": "string",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"warnings": [
|
||||
@@ -4078,9 +4082,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action_required`, `context_dirty`, `created`, `deleted`, `diff_status_change`, `status_change`, `summary_change`, `title_change` |
|
||||
| Value(s) |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action_required`, `chat_summary_change`, `context_dirty`, `created`, `deleted`, `diff_status_change`, `status_change`, `summary_change`, `title_change` |
|
||||
|
||||
## codersdk.ClusterConfig
|
||||
|
||||
|
||||
Reference in New Issue
Block a user