Repository navigation
feat(ui): name snapshots, and put fork, rename and delete on their mark - #2847
Conversation
…elete "Checkpoint" is "Snapshot" in the chat, and the marker on the transcript opens a details modal rather than carrying its own buttons. A snapshot now has a name, generated at creation from the conversation and the turn it sits at, and a fork is given that name. The column that already held the name a fork receives, `agent_instance_checkpoint.source_name`, is renamed `name` rather than a second one being added beside it. The schema change is folded into the unreleased baseline migration, so a development database has to be recreated. Signed-off-by: Nicholas Bucher <[email protected]>
summary-reel.mp4🤖 written by Claude |
The mark names itself — Snapshot "<name>" — and pressing it opens the record behind it. Fork, Rename and Delete sit under it, and an extension point sits beside them. The mark is a tinted card rather than a rule, at a strength per theme: the accent pair is several steps above a near-black page on dark and within a whisker of white on light, so one value cannot be a mark on both. Signed-off-by: Nicholas Bucher <[email protected]>
|
Claude was trying to update the |
Reuses the existing `source_name` column rather than renaming it, so the unreleased baseline migration is untouched and no development database has to be recreated. The column's job is the same one it always had — the name a fork is given — and only what fills it has changed. Also from review: the name no longer reaches the `data` payload, where a rename left it permanently disagreeing with the column; renaming honours the READY filter that Get and List both apply; the two snapshot extension points agree on `label`; and a mark whose record has not arrived yet no longer offers a button that opens nothing. Signed-off-by: Nicholas Bucher <[email protected]>
summary-reel.mp4🤖 written by Claude |
Delete took its red from antd's generated danger palette, which is rebuilt from the config when the theme changes and lands a frame after the two Emotion-styled buttons beside it — so mid-switch it was still wearing the theme just left. It now reads the danger token directly, as they do. Signed-off-by: Nicholas Bucher <[email protected]>
summary-reel.mp4🤖 written by Claude |
The pointer that opens the confirmation is still resting on Delete, so its tooltip stayed up and the tooltip's container sat over the confirmation's own buttons — a reader could not press Cancel. CI saw it as a flaky click. What the tooltip said, the confirmation already says. Signed-off-by: Nicholas Bucher <[email protected]>
EItanya
left a comment
There was a problem hiding this comment.
🤖 AI-generated review.
Reviewed the Go changes at 90dadcde8969951d167ea25f90a0c2cc9bba0ff8 for correctness, code cleanliness, and structure. The RPC/service/store boundaries look sensible; the main simplification is in checkpoint persistence.
-
Use
Checkpoint.Nameas the single source of truth and removesource_name.The new serializer clones the protobuf, removes
Name, and reconstructs it fromSourceNameon reads.SourceNamenow means the checkpoint's own name, requiring a lengthy comment explaining the misleading field name.Breaking schema changes are allowed right now, so preserving this column does not justify introducing a second persistence convention. The “so rename is one UPDATE” explanation also does not require this arrangement: the existing instance rename already updates the protobuf with one UPDATE under a transaction. No query filters or sorts checkpoints by name.
Please persist
Checkpoint.Namein the protobuf, follow the existing transactional rename pattern, and have forks inherit the decoded checkpoint's name. Removesource_name/SourceName,marshalCheckpoint, the read-side name override, and the comments explaining those exceptions. That keeps one representation without compatibility branches or duplicate storage. -
Preserve source-payload validation when removing the source-name lookup.
Removing the
toAgentInstance(instance)call also removes validation of the stored source protobuf.I reproduced this against PostgreSQL by creating an instance and a valid completed task/snapshot, then deliberately replacing only the instance payload with the invalid byte
0xff.GetAgentInstancerejects the payload, but checkpoint reservation succeeds and commits aCREATINGcheckpoint. Restoring just the decoding guard makes the probe pass.This concerns already-corrupt durable data; ordinary renaming does not cause the corruption. Please preserve that validation independently of reading the source name.
-
Make the default-name tests independent of the implementation helper.
The creation and reset assertions compute their expectations with the same
defaultCheckpointNamehelper used by production. Returning an incorrect constant from that helper would leave those assertions green. Use explicit expected strings to verify the naming contract. Also cover rename rejectingCREATING,FAILED, andDELETINGcheckpoints.
The transport handler, authorization policy, service delegation, and MCP field addition look appropriately scoped; no additional abstractions are needed there.
Validation: all five relevant Go packages passed, including PostgreSQL persistence and inline SQL preparation checks:
go test ./core/internal/service/checkpoint ./core/internal/grpcserver ./core/internal/mcp
go test ./core/internal/database ./core/pkg/migrationsThe additional corruption probe fails on the reviewed head and passes with only the removed validation restored. I did not run live-cluster E2E tests, UI tests, or local lint/generation checks.
The name was split out into the `source_name` column and stripped from the protobuf, so every read had to put it back and the column's name no longer described what it held. One representation instead: `Checkpoint.Name` is persisted like every other field, rename follows the transactional pattern `UpdateAgentInstanceName` already uses, and forks take their name from the decoded checkpoint. Reservation decodes the source instance again. That call went when the source name lookup did, and it was also the guard rejecting a corrupt stored payload — without it a checkpoint could be reserved against an instance that cannot be read. The default-name assertions now compare against literal strings, so a wrong constant in `defaultCheckpointName` fails them, and rename is covered against creating, failed and deleting checkpoints. Signed-off-by: Nicholas Bucher <[email protected]>
This comment has been minimized.
This comment has been minimized.
Both claimed the tooltip returns the half of a long name the row clips. It reads "Open snapshot details." and always has. The full name is on the record and in the row's aria-label. Signed-off-by: Nicholas Bucher <[email protected]>
snapshot-demo.mp4🤖 written by Claude |
|
All three done in
🤖 written by Claude |
The comment asked callers to pass a parsed id rather than the string that arrived on the wire, so a differently-cased id would generate a different default name. The parameter type says it instead, and both callers already held a uuid.UUID they were converting down. Signed-off-by: Nicholas Bucher <[email protected]>
|
Re-recorded against snapshot-demo-2847.mp4🤖 written by Claude |
kagent-dev/kagent#2847 renamed the chat's checkpoint to "Snapshot", gave it a generated name, and moved Fork, Rename and Delete onto the snapshot's mark. The refreshed capture in this PR shows that card; the prose still described the old Checkpoint/Fork/Remove strip, and the alt text promised a tool call that the taller card pushed out of frame. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Rachael Graham <[email protected]>
Kagent 1.0 recorded core version 1 with an earlier 000001 baseline, and 000001 has since changed in place (upstream kagent-dev#2847, kagent-dev#2857, kagent-dev#2868). Goose sees version 1 applied on a database created by 1.0 and runs nothing, so the controller finds no runtime_revision.credentials, no agent instance operation_id and executor_id, no generated pinned_checkpoint_id and no DELETED state, and fails to store a runtime revision with column "credentials" of relation "runtime_revision" does not exist. 000002 tells the two baselines apart by the four columns the current one added: none of them present is the 1.0 baseline, which it upgrades with the current baseline's columns, constraints (under the names a fresh install gives them) and partial index; all of them present is a database the current 000001 created, which it leaves unchanged; anything else is neither baseline and fails the migration, in its transaction. agent_instance_checkpoint.source_name stays: the 1.0 controller reads and writes it in every checkpoint query and the current controller never names it, so a migrated database still runs 1.0 and nothing is lost. The Down section has nothing to undo for the same reason. TestCoreUpgradesThe1_0Baseline replays the 1.0 baseline (a copy of it in testdata/core-1.0) with rows, runs the builtin migrations over it and compares the schema with a fresh install's, which 000002 must leave as 000001 made it; TestCoreRefusesAMixedBaseline proves the refusal. TestBuiltinMigrationsRoundTrip expects every migration a source ships.
Kagent 1.0 recorded core version 1 with an earlier 000001 baseline, and 000001 has since changed in place (upstream kagent-dev#2847, kagent-dev#2857, kagent-dev#2868). Goose sees version 1 applied on a database created by 1.0 and runs nothing, so the controller finds no runtime_revision.credentials, no agent instance operation_id and executor_id, no generated pinned_checkpoint_id and no DELETED state, and fails to store a runtime revision with column "credentials" of relation "runtime_revision" does not exist. 000002 tells the two baselines apart by the four columns the current one added: none of them present is the 1.0 baseline, which it upgrades with the current baseline's columns, constraints (under the names a fresh install gives them) and partial index; all of them present is a database the current 000001 created, which it leaves unchanged; anything else is neither baseline and fails the migration, in its transaction. agent_instance_checkpoint.source_name stays: the 1.0 controller reads and writes it in every checkpoint query and the current controller never names it, so a migrated database still runs 1.0 and nothing is lost. The Down section has nothing to undo for the same reason. TestCoreUpgradesThe1_0Baseline replays the 1.0 baseline (a copy of it in testdata/core-1.0) with rows, runs the builtin migrations over it and compares the schema with a fresh install's, which 000002 must leave as 000001 made it; TestCoreRefusesAMixedBaseline proves the refusal. TestBuiltinMigrationsRoundTrip expects every migration a source ships.
…rk (kagent-dev#2847) Signed-off-by: alexdarbyshire <[email protected]> --- *🤖 written by Claude (start)* "Checkpoint" is "Snapshot" in the chat. A snapshot carries a name, its mark on the transcript carries Fork, Rename and Delete, and pressing the mark opens the record behind it. | Change | Detail | |---|---| | A snapshot has a name | `Checkpoint.name`, generated on creation as `<agentInstanceId>-<headTaskId>`. | | Renaming one | `UpdateCheckpointName`, mirroring `UpdateAgentInstanceName`. An empty name restores the generated default, so a fork always has something to be called. | | A fork takes the snapshot's name | The chat no longer renames after forking, because `ForkAgentInstance` copies the checkpoint's name onto the new conversation. | | The mark carries its actions | Fork, Rename and Delete sit under it; pressing the mark itself opens the record, which holds the id, turn, state and age. | | Two extension points | `app_agents_agentChat_snapshotDivider_actions` beside those controls, and `app_agents_agentChat_snapshotDetails_footer` at the foot of the record. Both take `{ snapshotId, instanceId, label }`. | The name lives in the `data` payload with the rest of the checkpoint, and the `source_name` column is gone. Nothing filters or sorts checkpoints by name, so a column earned it nothing and cost a second place for the value to live. ## Testing 1. Chat with an agent for two turns, then take a snapshot from the composer. 2. The mark reads `Snapshot "<instance id>-<turn id>"`, clipped if it is long. Hovering offers to open the record; the full name is on the record itself. 3. Send a third turn and take a second snapshot. 4. Rename the first one from its mark, reload the page, and press the mark. The new name is on the record. 5. Fork from that mark. The new conversation opens named after the snapshot, holding only the turns above it. 6. Check both themes: the mark is a tinted card, and Rename's outline stays legible on each. --- *🤖 written by Claude (end)* --------- Signed-off-by: Nicholas Bucher <[email protected]>
🤖 written by Claude (start)
"Checkpoint" is "Snapshot" in the chat. A snapshot carries a name, its mark on the transcript carries Fork, Rename and Delete, and pressing the mark opens the record behind it.
Checkpoint.name, generated on creation as<agentInstanceId>-<headTaskId>.UpdateCheckpointName, mirroringUpdateAgentInstanceName. An empty name restores the generated default, so a fork always has something to be called.ForkAgentInstancecopies the checkpoint's name onto the new conversation.app_agents_agentChat_snapshotDivider_actionsbeside those controls, andapp_agents_agentChat_snapshotDetails_footerat the foot of the record. Both take{ snapshotId, instanceId, label }.The name lives in the
datapayload with the rest of the checkpoint, and thesource_namecolumn is gone. Nothing filters or sorts checkpoints by name, so a column earned it nothing and cost a second place for the value to live.Testing
Snapshot "<instance id>-<turn id>", clipped if it is long. Hovering offers to open the record; the full name is on the record itself.🤖 written by Claude (end)