@@ -46,7 +46,8 @@ func (c *Client) ForkAgentInstance(ctx context.Context, checkpointID, userID, re
4646 if err != nil {
4747 return fmt .Errorf ("lock checkpoint: %w" , err )
4848 }
49- if _ , err := toAgentInstanceCheckpoint (checkpoint ); err != nil {
49+ source , err := toAgentInstanceCheckpoint (checkpoint )
50+ if err != nil {
5051 return err
5152 }
5253 if checkpoint .PreparedRevision == nil {
@@ -67,7 +68,7 @@ func (c *Client) ForkAgentInstance(ctx context.Context, checkpointID, userID, re
6768 now := timestamppb .Now ()
6869 instance := & apiv1alpha1.AgentInstance {
6970 Id : instanceID , Creator : userID , ContextId : sourceContextID .String (),
70- Name : checkpoint . SourceName ,
71+ Name : source . GetName () ,
7172 Harness : & apiv1alpha1.ResourceReference {Namespace : revision .Namespace , Name : revision .HarnessName },
7273 AgentTemplate : & apiv1alpha1.ResourceReference {Namespace : revision .Namespace , Name : revision .AgentTemplateName },
7374 PreparedRevision : * checkpoint .PreparedRevision ,
@@ -190,6 +191,10 @@ func (c *Client) ReserveAgentInstanceCheckpoint(ctx context.Context, checkpoint
190191 if err != nil {
191192 return fmt .Errorf ("lock AgentInstance %s: %w" , checkpoint .GetAgentInstanceId (), err )
192193 }
194+ // Decoding rejects a corrupt stored instance before it becomes a checkpoint source.
195+ if _ , err := toAgentInstance (instance ); err != nil {
196+ return err
197+ }
193198 if instance .State != "AGENT_INSTANCE_STATE_READY" || instance .Operation != "AGENT_INSTANCE_OPERATION_UNSPECIFIED" {
194199 return fmt .Errorf ("AgentInstance %s cannot checkpoint in state %s with operation %s: %w" , checkpoint .GetAgentInstanceId (), instance .State , instance .Operation , ErrConflict )
195200 }
@@ -243,24 +248,24 @@ func (c *Client) ReserveAgentInstanceCheckpoint(ctx context.Context, checkpoint
243248 value .State = apiv1alpha1 .CheckpointState_CHECKPOINT_STATE_CREATING
244249 value .CreatedAt = timestamppb .Now ()
245250 value .Failure = nil
246- data , err := marshalCheckpoint (value )
251+ value .Name = defaultCheckpointName (sourceID .String (), boundary .ID )
252+ data , err := proto .Marshal (value )
247253 if err != nil {
248254 return fmt .Errorf ("encode checkpoint: %w" , err )
249255 }
250256 row , err := queryOne (ctx , tx , `
251257 INSERT INTO agent_instance_checkpoint (id, source_instance_id, user_id, request_id, head_task_id,
252258 history_sequence, snapshot_atespace, snapshot_uri, snapshot_content_scope, source_history_id,
253- prepared_revision, data, source_name, state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,
254- $10, $11, $12, $13, 'CREATING')
259+ prepared_revision, data, state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,
260+ $10, $11, $12, 'CREATING')
255261 ON CONFLICT DO NOTHING
256262 RETURNING id, source_instance_id, user_id, request_id, head_task_id, history_sequence, snapshot_atespace,
257- snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision,
258- source_name
263+ snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision
259264 ` ,
260265 pgx .RowToStructByName [agentInstanceCheckpointRow ], checkpoint .GetId (),
261266 checkpoint .GetAgentInstanceId (), userID , requestID , boundary .ID , * boundary .HistorySequence ,
262267 * boundary .SnapshotAtespace , * boundary .SnapshotURI , * boundary .SnapshotContentScope , instance .HistoryID ,
263- instance .PreparedRevision , data , defaultCheckpointName ( sourceID . String (), boundary . ID ),
268+ instance .PreparedRevision , data ,
264269 )
265270 if errors .Is (err , pgx .ErrNoRows ) {
266271 existing , existingErr := readCheckpointRequest (ctx , tx , userID , requestID )
@@ -320,7 +325,7 @@ func (c *Client) FinalizeAgentInstanceCheckpoint(ctx context.Context, id, tagUID
320325 result .State = apiv1alpha1 .CheckpointState_CHECKPOINT_STATE_FAILED
321326 result .Failure = & apiv1alpha1.Failure {Reason : "SnapshotTagFailed" , Message : failure }
322327 }
323- data , err := marshalCheckpoint (result )
328+ data , err := proto . Marshal (result )
324329 if err != nil {
325330 return fmt .Errorf ("encode checkpoint: %w" , err )
326331 }
@@ -386,8 +391,8 @@ func checkpointSnapshot(row agentInstanceCheckpointRow) *AgentInstanceTaskSnapsh
386391func (c * Client ) ListAgentInstanceCheckpoints (ctx context.Context , instanceID , userID , afterID string , limit int ) ([]* apiv1alpha1.Checkpoint , error ) {
387392 rows , err := queryMany (ctx , c .db , `
388393 SELECT id, source_instance_id, user_id, request_id, head_task_id, history_sequence, snapshot_atespace,
389- snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision,
390- source_name FROM agent_instance_checkpoint
394+ snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision
395+ FROM agent_instance_checkpoint
391396 WHERE source_instance_id = $1
392397 AND user_id = $2
393398 AND state = 'READY'
@@ -428,7 +433,7 @@ func (c *Client) BeginDeleteAgentInstanceCheckpoint(ctx context.Context, id, use
428433 return err
429434 }
430435 checkpoint .State = apiv1alpha1 .CheckpointState_CHECKPOINT_STATE_DELETING
431- data , err := marshalCheckpoint (checkpoint )
436+ data , err := proto . Marshal (checkpoint )
432437 if err != nil {
433438 return fmt .Errorf ("encode checkpoint: %w" , err )
434439 }
@@ -469,15 +474,6 @@ func (c *Client) DeleteAgentInstanceCheckpoint(ctx context.Context, id, userID s
469474 return nil
470475}
471476
472- // marshalCheckpoint encodes a checkpoint for the data column with its name left out.
473- // The name lives in its own column so a rename is one UPDATE; a copy in the payload as
474- // well would disagree with that column the moment anybody renamed the checkpoint.
475- func marshalCheckpoint (checkpoint * apiv1alpha1.Checkpoint ) ([]byte , error ) {
476- stored := proto .Clone (checkpoint ).(* apiv1alpha1.Checkpoint )
477- stored .Name = ""
478- return proto .Marshal (stored )
479- }
480-
481477// defaultCheckpointName names a boundary by the conversation it was taken from and the
482478// turn it sits at, the two things that tell two boundaries apart. Callers pass the
483479// parsed id rather than what arrived on the wire, so that clearing a name restores the
@@ -499,23 +495,31 @@ func (c *Client) UpdateCheckpointName(ctx context.Context, id, userID, name stri
499495 if err != nil {
500496 return notFoundOr (err )
501497 }
498+ checkpoint , err := toAgentInstanceCheckpoint (row )
499+ if err != nil {
500+ return err
501+ }
502502 if name == "" {
503503 name = defaultCheckpointName (row .SourceInstanceID .String (), row .HeadTaskID )
504504 }
505+ checkpoint .Name = name
506+ data , err := proto .Marshal (checkpoint )
507+ if err != nil {
508+ return fmt .Errorf ("encode checkpoint: %w" , err )
509+ }
505510 tag , err := tx .Exec (ctx , `
506511 UPDATE agent_instance_checkpoint
507- SET source_name = $3
508- WHERE id = $1 AND user_id = $2
509- ` , row .ID , userID , name )
512+ SET data = $1
513+ WHERE id = $2 AND user_id = $3
514+ ` , data , row .ID , userID )
510515 if err != nil {
511516 return err
512517 }
513518 if tag .RowsAffected () != 1 {
514519 return ErrNotFound
515520 }
516- row .SourceName = name
517- result , err = toAgentInstanceCheckpoint (row )
518- return err
521+ result = checkpoint
522+ return nil
519523 })
520524 if err != nil {
521525 return nil , fmt .Errorf ("rename checkpoint %s: %w" , id , err )
@@ -535,9 +539,6 @@ func toAgentInstanceCheckpoint(row agentInstanceCheckpointRow) (*apiv1alpha1.Che
535539 strings .TrimPrefix (checkpoint .GetState ().String (), "CHECKPOINT_STATE_" ) != row .State {
536540 return nil , fmt .Errorf ("checkpoint %s payload disagrees with indexed columns" , row .ID )
537541 }
538- // The name lives in its own column so a rename stays one UPDATE, which also keeps
539- // it out of the payload cross-check above.
540- checkpoint .Name = row .SourceName
541542 return checkpoint , nil
542543}
543544
@@ -556,15 +557,6 @@ type agentInstanceCheckpointRow struct {
556557 Data []byte
557558 SourceHistoryID uuid.UUID
558559 PreparedRevision * string
559- /*
560- * The checkpoint's own display name, and the name its forks are given.
561- *
562- * The column predates the name being something a reader chooses — it held a copy of
563- * the source conversation's title so a fork could still be named after a conversation
564- * that had since been deleted. The job is the same one, so it was kept rather than a
565- * second column added beside it; only what fills it has changed.
566- */
567- SourceName string
568560}
569561
570562// lockCheckpoint locks a checkpoint until the caller's transaction ends, optionally
@@ -573,8 +565,8 @@ type agentInstanceCheckpointRow struct {
573565func lockCheckpoint (ctx context.Context , db pgx.Tx , id string , allUsers bool , userID string , state * string ) (agentInstanceCheckpointRow , error ) {
574566 return queryOne (ctx , db , `
575567 SELECT id, source_instance_id, user_id, request_id, head_task_id, history_sequence, snapshot_atespace,
576- snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision,
577- source_name FROM agent_instance_checkpoint
568+ snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision
569+ FROM agent_instance_checkpoint
578570 WHERE id = $1
579571 -- Only internal finalization explicitly opts out of owner filtering.
580572 AND ($2::boolean OR user_id = $3)
@@ -621,8 +613,8 @@ func insertReplayedTask(ctx context.Context, db dbExecutor, task agentInstanceTa
621613func readCheckpoint (ctx context.Context , db dbExecutor , id , userID string , state * string ) (agentInstanceCheckpointRow , error ) {
622614 return queryOne (ctx , db , `
623615 SELECT id, source_instance_id, user_id, request_id, head_task_id, history_sequence, snapshot_atespace,
624- snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision,
625- source_name FROM agent_instance_checkpoint
616+ snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision
617+ FROM agent_instance_checkpoint
626618 WHERE id = $1 AND user_id = $2
627619 -- Lifecycle work also reads creating and deleting checkpoints.
628620 AND ($3::text IS NULL OR state = $3)
@@ -634,8 +626,8 @@ func readCheckpoint(ctx context.Context, db dbExecutor, id, userID string, state
634626func readCheckpointRequest (ctx context.Context , db dbExecutor , userID , requestID string ) (agentInstanceCheckpointRow , error ) {
635627 return queryOne (ctx , db , `
636628 SELECT id, source_instance_id, user_id, request_id, head_task_id, history_sequence, snapshot_atespace,
637- snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision,
638- source_name FROM agent_instance_checkpoint
629+ snapshot_uri, snapshot_content_scope, tag_uid, state, data, source_history_id, prepared_revision
630+ FROM agent_instance_checkpoint
639631 WHERE user_id = $1 AND request_id = $2
640632 ` , pgx .RowToStructByName [agentInstanceCheckpointRow ], userID , requestID )
641633}
0 commit comments