// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: agent.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const acknowledgeExhaustedDelegatedFailureRecovery = `-- name: AcknowledgeExhaustedDelegatedFailureRecovery :one UPDATE agent_task_queue AS acknowledged SET delivered_comment_ids = ( SELECT COALESCE(array_agg(DISTINCT receipt.id), '{}')::uuid[] FROM unnest(array_append(acknowledged.delivered_comment_ids, $1::uuid)) AS receipt(id) ) WHERE acknowledged.id = ( SELECT attempt.id FROM agent_task_queue attempt WHERE attempt.trigger_evidence_kind = 'delegated_failure' AND attempt.trigger_evidence_ref_id = $2 ORDER BY attempt.created_at DESC, attempt.id DESC LIMIT 1 FOR UPDATE ) AND ( SELECT count(*) FROM agent_task_queue attempt_count WHERE attempt_count.trigger_evidence_kind = 'delegated_failure' AND attempt_count.trigger_evidence_ref_id = $2 ) >= $3::int RETURNING acknowledged.id, acknowledged.agent_id, acknowledged.issue_id, acknowledged.status, acknowledged.priority, acknowledged.dispatched_at, acknowledged.started_at, acknowledged.completed_at, acknowledged.result, acknowledged.error, acknowledged.created_at, acknowledged.context, acknowledged.runtime_id, acknowledged.session_id, acknowledged.work_dir, acknowledged.trigger_comment_id, acknowledged.chat_session_id, acknowledged.autopilot_run_id, acknowledged.attempt, acknowledged.max_attempts, acknowledged.parent_task_id, acknowledged.failure_reason, acknowledged.trigger_summary, acknowledged.force_fresh_session, acknowledged.is_leader_task, acknowledged.wait_reason, acknowledged.initiator_user_id, acknowledged.handoff_note, acknowledged.prepare_lease_expires_at, acknowledged.squad_id, acknowledged.runtime_mcp_overlay, acknowledged.escalation_for_task_id, acknowledged.fire_at, acknowledged.originator_user_id, acknowledged.runtime_connected_apps, acknowledged.coalesced_comment_ids, acknowledged.delivered_comment_ids, acknowledged.chat_input_task_id, acknowledged.chat_finalize_deferred_at, acknowledged.originator_source, acknowledged.delegated_from_task_id, acknowledged.retry_of_task_id, acknowledged.rerun_of_task_id, acknowledged.rule_version_id, acknowledged.trigger_evidence_kind, acknowledged.trigger_evidence_ref_id, acknowledged.accountable_user_id, acknowledged.session_rollout_missing, acknowledged.retired_session_id, acknowledged.quick_actions_disabled, acknowledged.regenerate_quick_actions_for, acknowledged.branch_name, acknowledged.durable_work_dir, acknowledged.channel_context_revision, acknowledged.comment_thread_id, acknowledged.cancelled_by_type, acknowledged.cancelled_by_id, acknowledged.cancelled_by_name, acknowledged.issue_snapshot, acknowledged.workflow_step ` type AcknowledgeExhaustedDelegatedFailureRecoveryParams struct { CommentID pgtype.UUID `json:"comment_id"` FailedTaskID pgtype.UUID `json:"failed_task_id"` MaxAttempts int32 `json:"max_attempts"` } // Once the bounded automatic attempts are exhausted, record a terminal // acknowledgement on the newest recovery task. The outbox treats this receipt // as settled, while the service writes a separate visible system comment that // tells the user why no further task will be generated. func (q *Queries) AcknowledgeExhaustedDelegatedFailureRecovery(ctx context.Context, arg AcknowledgeExhaustedDelegatedFailureRecoveryParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, acknowledgeExhaustedDelegatedFailureRecovery, arg.CommentID, arg.FailedTaskID, arg.MaxAttempts) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const archiveAgent = `-- name: ArchiveAgent :one UPDATE agent SET archived_at = now(), archived_by = $2, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type ArchiveAgentParams struct { ID pgtype.UUID `json:"id"` ArchivedBy pgtype.UUID `json:"archived_by"` } func (q *Queries) ArchiveAgent(ctx context.Context, arg ArchiveAgentParams) (Agent, error) { row := q.db.QueryRow(ctx, archiveAgent, arg.ID, arg.ArchivedBy) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const archiveAgentsByIDs = `-- name: ArchiveAgentsByIDs :many UPDATE agent SET archived_at = now(), archived_by = $1, updated_at = now() WHERE id = ANY($2::uuid[]) AND archived_at IS NULL RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type ArchiveAgentsByIDsParams struct { ArchivedBy pgtype.UUID `json:"archived_by"` AgentIds []pgtype.UUID `json:"agent_ids"` } // Narrow archive that only touches the explicit ID list. Used by the // cascade-delete endpoint so the user's expected_active_agent_ids list // is the authoritative bound on what gets archived: any agent that // appeared on the runtime after the user opened the dialog is filtered // out here so it can't be silently archived even in the (vanishingly // rare) case where a row-level race slips past the runtime FOR UPDATE // lock. Returns the affected rows so the caller can broadcast // agent:archived per agent. func (q *Queries) ArchiveAgentsByIDs(ctx context.Context, arg ArchiveAgentsByIDsParams) ([]Agent, error) { rows, err := q.db.Query(ctx, archiveAgentsByIDs, arg.ArchivedBy, arg.AgentIds) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const archiveAgentsByRuntime = `-- name: ArchiveAgentsByRuntime :many UPDATE agent SET archived_at = now(), archived_by = $1, updated_at = now() WHERE runtime_id = ANY($2::uuid[]) AND archived_at IS NULL AND (system_key IS NULL OR system_key = '') RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type ArchiveAgentsByRuntimeParams struct { ArchivedBy pgtype.UUID `json:"archived_by"` RuntimeIds []pgtype.UUID `json:"runtime_ids"` } // Bulk-archives every active agent bound to any runtime in the given set. // Used when revoking a leaving member's runtimes so agents pinned to those // runtimes can no longer be assigned new work. Returns the affected rows so // the caller can broadcast agent:archived per agent. // // System agents are exempt: they belong to the workspace rather than to the // member who happened to create them, and the workspace's entry point runs // through one. Archiving Mika because a colleague left would take the default // agent away from everyone. Its runtime does go offline with the departure, so // it needs rebinding — but it stays visible and recoverable instead of // vanishing. func (q *Queries) ArchiveAgentsByRuntime(ctx context.Context, arg ArchiveAgentsByRuntimeParams) ([]Agent, error) { rows, err := q.db.Query(ctx, archiveAgentsByRuntime, arg.ArchivedBy, arg.RuntimeIds) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelAgentTask = `-- name: CancelAgentTask :one UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Automatic cancellation without an explicit persisted failure reason. Unlike // CancelAgentTaskByUser, this deliberately leaves recovery inputs replayable. func (q *Queries) CancelAgentTask(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, cancelAgentTask, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const cancelAgentTaskByUser = `-- name: CancelAgentTaskByUser :one UPDATE agent_task_queue AS task SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = $2, cancelled_by_id = $3, cancelled_by_name = $4, delivered_comment_ids = CASE -- Chat and ordinary issue tasks almost never carry a delegated-failure -- recovery signal. Keep their high-frequency user-cancel path to a -- no-join update; only validate task lineage after the cheap comment -- shape probe finds a possible recovery signal. WHEN task.trigger_comment_id IS NULL AND COALESCE(cardinality(task.coalesced_comment_ids), 0) = 0 THEN task.delivered_comment_ids WHEN NOT EXISTS ( SELECT 1 FROM comment recovery_signal WHERE ( recovery_signal.id = task.trigger_comment_id OR recovery_signal.id = ANY(task.coalesced_comment_ids) ) AND recovery_signal.author_type = 'system' AND recovery_signal.type = 'progress_update' AND recovery_signal.source_task_id IS NOT NULL ) THEN task.delivered_comment_ids ELSE ( SELECT COALESCE(array_agg(DISTINCT receipt.id), '{}')::uuid[] FROM unnest(array_cat( task.delivered_comment_ids, ARRAY( SELECT recovery.id FROM comment recovery JOIN agent_task_queue failed ON failed.id = recovery.source_task_id JOIN agent_task_queue source ON source.id = failed.delegated_from_task_id WHERE ( recovery.id = task.trigger_comment_id OR recovery.id = ANY(task.coalesced_comment_ids) ) AND recovery.author_type = 'system' AND recovery.type = 'progress_update' AND recovery.source_task_id IS NOT NULL AND failed.status = 'failed' AND failed.delegated_from_task_id IS NOT NULL AND failed.autopilot_run_id IS NULL AND failed.trigger_evidence_kind IS DISTINCT FROM 'delegated_failure' AND source.autopilot_run_id IS NULL AND source.issue_id = task.issue_id AND source.agent_id = task.agent_id AND recovery.issue_id = source.issue_id ) )) AS receipt(id) ) END WHERE task.id = $1 AND task.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING task.id, task.agent_id, task.issue_id, task.status, task.priority, task.dispatched_at, task.started_at, task.completed_at, task.result, task.error, task.created_at, task.context, task.runtime_id, task.session_id, task.work_dir, task.trigger_comment_id, task.chat_session_id, task.autopilot_run_id, task.attempt, task.max_attempts, task.parent_task_id, task.failure_reason, task.trigger_summary, task.force_fresh_session, task.is_leader_task, task.wait_reason, task.initiator_user_id, task.handoff_note, task.prepare_lease_expires_at, task.squad_id, task.runtime_mcp_overlay, task.escalation_for_task_id, task.fire_at, task.originator_user_id, task.runtime_connected_apps, task.coalesced_comment_ids, task.delivered_comment_ids, task.chat_input_task_id, task.chat_finalize_deferred_at, task.originator_source, task.delegated_from_task_id, task.retry_of_task_id, task.rerun_of_task_id, task.rule_version_id, task.trigger_evidence_kind, task.trigger_evidence_ref_id, task.accountable_user_id, task.session_rollout_missing, task.retired_session_id, task.quick_actions_disabled, task.regenerate_quick_actions_for, task.branch_name, task.durable_work_dir, task.channel_context_revision, task.comment_thread_id, task.cancelled_by_type, task.cancelled_by_id, task.cancelled_by_name, task.issue_snapshot, task.workflow_step ` type CancelAgentTaskByUserParams struct { ID pgtype.UUID `json:"id"` CancelledByType pgtype.Text `json:"cancelled_by_type"` CancelledByID pgtype.UUID `json:"cancelled_by_id"` CancelledByName pgtype.Text `json:"cancelled_by_name"` } // An explicit user cancellation is a terminal acknowledgement for any // delegated-failure recovery signal planned into this task. Server-initiated // cancellations keep using CancelAgentTask / CancelAgentTaskWithReason so a // stale plan, claim failure, or other automatic repair can still be replayed. func (q *Queries) CancelAgentTaskByUser(ctx context.Context, arg CancelAgentTaskByUserParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, cancelAgentTaskByUser, arg.ID, arg.CancelledByType, arg.CancelledByID, arg.CancelledByName, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const cancelAgentTaskWithReason = `-- name: CancelAgentTaskWithReason :one UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), error = $1, failure_reason = $2, prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE id = $3 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CancelAgentTaskWithReasonParams struct { Error pgtype.Text `json:"error"` FailureReason pgtype.Text `json:"failure_reason"` ID pgtype.UUID `json:"id"` } // Cancels a task AND records why, for cancellations the user did not ask for. // // CancelAgentTaskByUser leaves error/failure_reason NULL because the user knows // why. A server-initiated refusal is the opposite: without a persisted reason // the run surfaces as an unexplained "cancelled", and the only trace is a 4xx // in a daemon log the user never sees. Retrying cannot help either (the task is // refused for a durable reason), so this terminal state carries its explanation. func (q *Queries) CancelAgentTaskWithReason(ctx context.Context, arg CancelAgentTaskWithReasonParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, cancelAgentTaskWithReason, arg.Error, arg.FailureReason, arg.ID) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const cancelAgentTasksByAgent = `-- name: CancelAgentTasksByAgent :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE agent_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Bulk-cancel every active (queued/dispatched/running) task for an agent. // Returns the affected rows so callers can broadcast task:cancelled events. // Mirrors the shape of CancelAgentTasksByIssue / CancelPendingTasksByIssueAndAgent // (also :many + RETURNING + completed_at) so the three sibling cancel paths // behave consistently. func (q *Queries) CancelAgentTasksByAgent(ctx context.Context, agentID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelAgentTasksByAgent, agentID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelAgentTasksByChatSession = `-- name: CancelAgentTasksByChatSession :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE chat_session_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Cancels active tasks belonging to a chat session. Called from // DeleteChatSession so the daemon doesn't keep running work whose result // has nowhere to land. Must run BEFORE the chat_session row is deleted — // the FK ON DELETE SET NULL would otherwise nullify chat_session_id and we // could no longer reach those tasks. func (q *Queries) CancelAgentTasksByChatSession(ctx context.Context, chatSessionID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelAgentTasksByChatSession, chatSessionID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelAgentTasksByIssue = `-- name: CancelAgentTasksByIssue :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE issue_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Cancels every active task on the issue and returns the affected rows so the // caller can reconcile each agent's status and broadcast task:cancelled events // (#1587). Prior :exec form silently dropped that info, leaving agents stuck at // status="working" with no self-correction. Only issue-deletion cleanup calls // this now; a status flip to cancelled/done no longer does (MUL-4465). func (q *Queries) CancelAgentTasksByIssue(ctx context.Context, issueID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelAgentTasksByIssue, issueID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelAgentTasksByTriggerComment = `-- name: CancelAgentTasksByTriggerComment :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL, context = COALESCE(context, '{}'::jsonb) || jsonb_build_object('comment_change_cancelled_task_id', id::text) WHERE (trigger_comment_id = $1 OR $1 = ANY(coalesced_comment_ids)) AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Cancels active tasks whose planned batch contains the edited/deleted comment. // The body may already have been embedded as either the primary trigger or a // coalesced input; cancellation prevents an agent from acting on a stale or // deleted version. Must run before deletion clears trigger_comment_id. func (q *Queries) CancelAgentTasksByTriggerComment(ctx context.Context, triggerCommentID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelAgentTasksByTriggerComment, triggerCommentID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelPendingTasksByIssueAndAgent = `-- name: CancelPendingTasksByIssueAndAgent :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE issue_id = $1 AND agent_id = $2 AND status IN ('queued', 'dispatched', 'deferred') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CancelPendingTasksByIssueAndAgentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` } // Cancels the not-yet-started tasks for a single (issue, agent) pair, so the // manual rerun flow can enqueue a replacement without colliding with // idx_one_pending_task_per_issue_agent_v2 and without dropping the rerun's own // attribution onto a row somebody else created. // // 'running' and 'waiting_local_directory' are deliberately NOT cancelled: an // agent is executing in them. Neither status appears in that unique index, so a // fresh queued row can be inserted alongside one, and ClaimAgentTask's // per-(issue, agent) serialization holds the new row until the active run // reaches a terminal state. Cancelling them made a manual rerun kill the pass // the agent was still working on; interrupting an in-flight run is CancelTask's // job, not rerun's. // // Everything that has NOT begun executing is still cleared, including deferred // escalations, so rerun keeps its prior "replace the pending plan" behaviour for // those rows. func (q *Queries) CancelPendingTasksByIssueAndAgent(ctx context.Context, arg CancelPendingTasksByIssueAndAgentParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelPendingTasksByIssueAndAgent, arg.IssueID, arg.AgentID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelPendingTasksByIssueAndAgentInThread = `-- name: CancelPendingTasksByIssueAndAgentInThread :many UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE issue_id = $1 AND agent_id = $2 AND status IN ('queued', 'dispatched', 'deferred') AND comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($3::uuid) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CancelPendingTasksByIssueAndAgentInThreadParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` ThreadCommentID pgtype.UUID `json:"thread_comment_id"` } // Cancel only the not-yet-started plan in the selected thread. Other threads // retain their queues; running tasks are stopped explicitly through CancelTask. func (q *Queries) CancelPendingTasksByIssueAndAgentInThread(ctx context.Context, arg CancelPendingTasksByIssueAndAgentInThreadParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelPendingTasksByIssueAndAgentInThread, arg.IssueID, arg.AgentID, arg.ThreadCommentID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelQueuedAgentTask = `-- name: CancelQueuedAgentTask :one UPDATE agent_task_queue SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = $1, cancelled_by_id = $2, cancelled_by_name = $3 WHERE id = $4 AND chat_session_id = $5 AND status = 'queued' RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CancelQueuedAgentTaskParams struct { CancelledByType pgtype.Text `json:"cancelled_by_type"` CancelledByID pgtype.UUID `json:"cancelled_by_id"` CancelledByName pgtype.Text `json:"cancelled_by_name"` ID pgtype.UUID `json:"id"` ChatSessionID pgtype.UUID `json:"chat_session_id"` } // Queue editing is a compare-and-set: never cancel a task that the daemon // promoted between the user's click and this statement. func (q *Queries) CancelQueuedAgentTask(ctx context.Context, arg CancelQueuedAgentTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, cancelQueuedAgentTask, arg.CancelledByType, arg.CancelledByID, arg.CancelledByName, arg.ID, arg.ChatSessionID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const cancelQueuedAgentTasksForSession = `-- name: CancelQueuedAgentTasksForSession :many WITH head AS MATERIALIZED ( SELECT candidate.id FROM agent_task_queue AS candidate WHERE candidate.chat_session_id = $1 AND candidate.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory', 'deferred') AND candidate.regenerate_quick_actions_for IS NULL ORDER BY CASE WHEN candidate.status IN ('dispatched', 'running', 'waiting_local_directory') THEN 0 WHEN candidate.status = 'deferred' THEN 1 ELSE 2 END, candidate.priority DESC, candidate.created_at ASC, candidate.id ASC LIMIT 1 ) UPDATE agent_task_queue AS queued SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE queued.chat_session_id = $1 AND queued.status = 'queued' AND queued.id IS DISTINCT FROM (SELECT id FROM head) RETURNING queued.id, queued.agent_id, queued.issue_id, queued.status, queued.priority, queued.dispatched_at, queued.started_at, queued.completed_at, queued.result, queued.error, queued.created_at, queued.context, queued.runtime_id, queued.session_id, queued.work_dir, queued.trigger_comment_id, queued.chat_session_id, queued.autopilot_run_id, queued.attempt, queued.max_attempts, queued.parent_task_id, queued.failure_reason, queued.trigger_summary, queued.force_fresh_session, queued.is_leader_task, queued.wait_reason, queued.initiator_user_id, queued.handoff_note, queued.prepare_lease_expires_at, queued.squad_id, queued.runtime_mcp_overlay, queued.escalation_for_task_id, queued.fire_at, queued.originator_user_id, queued.runtime_connected_apps, queued.coalesced_comment_ids, queued.delivered_comment_ids, queued.chat_input_task_id, queued.chat_finalize_deferred_at, queued.originator_source, queued.delegated_from_task_id, queued.retry_of_task_id, queued.rerun_of_task_id, queued.rule_version_id, queued.trigger_evidence_kind, queued.trigger_evidence_ref_id, queued.accountable_user_id, queued.session_rollout_missing, queued.retired_session_id, queued.quick_actions_disabled, queued.regenerate_quick_actions_for, queued.branch_name, queued.durable_work_dir, queued.channel_context_revision, queued.comment_thread_id, queued.cancelled_by_type, queued.cancelled_by_id, queued.cancelled_by_name, queued.issue_snapshot, queued.workflow_step ` // Clear only positional follow-ups. The first visible task is current even // when it has not been claimed yet, so an all-queued session must preserve its // priority/FIFO head. Keep this selector in lockstep with the visible-head // invariant documented above ListChatMessages in chat.sql. func (q *Queries) CancelQueuedAgentTasksForSession(ctx context.Context, chatSessionID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelQueuedAgentTasksForSession, chatSessionID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const cancelSupersededDeferredRetriesForRuntimes = `-- name: CancelSupersededDeferredRetriesForRuntimes :many UPDATE agent_task_queue r SET status = 'cancelled', completed_at = now(), prepare_lease_expires_at = NULL, cancelled_by_type = 'system', cancelled_by_id = NULL, cancelled_by_name = NULL WHERE r.runtime_id = ANY($1::uuid[]) AND r.status = 'deferred' AND r.issue_id IS NOT NULL AND r.retry_of_task_id IS NOT NULL AND COALESCE(r.context->>'channel_issue_media_pending', '') <> 'true' AND EXISTS ( SELECT 1 FROM agent_task_queue successor WHERE successor.issue_id = r.issue_id AND successor.agent_id = r.agent_id AND successor.comment_thread_id IS NOT DISTINCT FROM r.comment_thread_id AND successor.id <> r.id AND successor.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Cancels deferred auto-retry rows that a newer active task has already // superseded, so one rerun click still means exactly one more run. // // The narrow race: a manual rerun clears the pending slot, a concurrent FailTask // commits a deferred retry (runtime_offline / provider_network's final attempt // arm fire_at), and the rerun's own enqueue then succeeds because 'deferred' is // outside idx_one_pending_task_per_issue_agent_v2. Both rows now exist. Merely // refusing to promote the retry is not enough: once the rerun stops occupying the // slot the retry promotes and runs a SECOND time, duplicating the agent's // comments, side effects and cost with nothing to signal it. That contradicts // both "a manual rerun replaces the pending plan" and FailTask's own "a runnable // successor already exists, so no retry is needed". // // Scope is deliberately tight: // - retry_of_task_id IS NOT NULL — only auto-retry clones, never a fresh run. // - channel-media pending rows are excluded for the same reason: that deferred // row is the issue's own task waiting on media, not a superseded retry. // - issue_id IS NOT NULL — chat / quick-create tasks have no slot semantics. // // 'running' and 'waiting_local_directory' count as superseding: the duplicate // fires precisely when the rerun has STARTED, which is when the row would // otherwise no longer look blocked. func (q *Queries) CancelSupersededDeferredRetriesForRuntimes(ctx context.Context, runtimeIds []pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, cancelSupersededDeferredRetriesForRuntimes, runtimeIds) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const claimAgentTask = `-- name: ClaimAgentTask :one UPDATE agent_task_queue SET status = 'dispatched', dispatched_at = now(), prepare_lease_expires_at = now() + make_interval(secs => $1::double precision) WHERE id = ( SELECT atq.id FROM agent_task_queue atq WHERE atq.agent_id = $2 AND atq.runtime_id = $3 AND atq.status = 'queued' AND (atq.context->>'wakeup_id' IS NULL OR EXISTS (SELECT 1 FROM issue_wakeup w WHERE w.id=(atq.context->>'wakeup_id')::uuid AND w.disabled_at IS NULL AND w.revision=(atq.context->>'wakeup_revision')::bigint)) AND EXISTS ( SELECT 1 FROM agent a JOIN agent_runtime r ON r.id = atq.runtime_id WHERE a.id = atq.agent_id -- A task's persisted runtime is not authority after an agent rebind. AND a.runtime_id = atq.runtime_id -- Queued private-runtime rows are claimable so the handler can -- settle an owner mismatch through the existing FailTask path -- before daemon delivery. Public runtimes remain shareable across -- agent owners; dispatched reclaim keeps its owner fence below. AND ( r.visibility = 'public' OR r.visibility = 'private' ) AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $4::double precision) ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue active WHERE active.agent_id = atq.agent_id AND active.status IN ('dispatched', 'running', 'waiting_local_directory') AND ( (atq.issue_id IS NOT NULL AND active.issue_id = atq.issue_id) OR (atq.chat_session_id IS NOT NULL AND active.chat_session_id = atq.chat_session_id) OR ( atq.issue_id IS NULL AND atq.chat_session_id IS NULL AND atq.autopilot_run_id IS NULL AND active.issue_id IS NULL AND active.chat_session_id IS NULL AND active.autopilot_run_id IS NULL ) ) ) ORDER BY atq.priority DESC, atq.created_at ASC, atq.id ASC LIMIT 1 FOR UPDATE SKIP LOCKED ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type ClaimAgentTaskParams struct { PrepareLeaseSecs float64 `json:"prepare_lease_secs"` AgentID pgtype.UUID `json:"agent_id"` RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` } // Claims the next queued task for an agent on one healthy runtime, enforcing // per-(issue, agent) serialization: // a task is only claimable when no other task for the same issue AND same agent is // already dispatched or running. This allows different agents to work on the same // issue in parallel while preventing a single agent from running duplicate tasks. // Chat tasks (issue_id IS NULL) use chat_session_id for serialization instead. // Quick-create tasks have no issue / chat / autopilot link, so they serialize on // "any other quick-create-shaped task" (all four FKs NULL) for the same agent — // otherwise a user mashing the create button could fire concurrent quick-creates // whose completion lookup would race over "most recent issue by this agent". func (q *Queries) ClaimAgentTask(ctx context.Context, arg ClaimAgentTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, claimAgentTask, arg.PrepareLeaseSecs, arg.AgentID, arg.RuntimeID, arg.RuntimeStaleSecs, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const claimChatFinalizeDeferred = `-- name: ClaimChatFinalizeDeferred :one UPDATE agent_task_queue SET chat_finalize_deferred_at = NULL WHERE id = $1 AND chat_finalize_deferred_at IS NOT NULL RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Atomically claims the deferred marker so the daemon ack and the sweeper // cannot both finalize the same task (double-"Stopped." guard). func (q *Queries) ClaimChatFinalizeDeferred(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, claimChatFinalizeDeferred, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const clearAgentComposioToolkitAllowlist = `-- name: ClearAgentComposioToolkitAllowlist :one UPDATE agent SET composio_toolkit_allowlist = NULL, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` // Explicit NULL-clear for composio_toolkit_allowlist. The COALESCE-based // UpdateAgent cannot set the column back to NULL — sending an empty array // through there would persist `{}` (still a non-NULL, equivalent to "no // toolkits" but distinct from "field never configured"). The API uses this // dedicated query when the agent owner removes every toolkit; subsequent // dispatch decisions treat NULL identically to `{}` (both -> no overlay). func (q *Queries) ClearAgentComposioToolkitAllowlist(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, clearAgentComposioToolkitAllowlist, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const clearAgentMcpConfig = `-- name: ClearAgentMcpConfig :one UPDATE agent SET mcp_config = NULL, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` func (q *Queries) ClearAgentMcpConfig(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, clearAgentMcpConfig, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const clearAgentServiceTier = `-- name: ClearAgentServiceTier :one UPDATE agent SET service_tier = NULL, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` // Explicit NULL-clear for service_tier. COALESCE-based UpdateAgent cannot // set the column back to NULL, so the API routes "Runtime default" here. func (q *Queries) ClearAgentServiceTier(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, clearAgentServiceTier, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const clearAgentThinkingLevel = `-- name: ClearAgentThinkingLevel :one UPDATE agent SET thinking_level = NULL, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` // Explicit NULL-clear for thinking_level. COALESCE-based UpdateAgent cannot // set the column back to NULL, so the API layer routes "user picked Default" // through this dedicated query. func (q *Queries) ClearAgentThinkingLevel(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, clearAgentThinkingLevel, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const completeAgentTask = `-- name: CompleteAgentTask :one UPDATE agent_task_queue SET status = 'completed', completed_at = now(), result = $2, session_id = CASE WHEN $5 THEN NULL ELSE $3 END, work_dir = $4, durable_work_dir = COALESCE($6, durable_work_dir), branch_name = COALESCE($7, branch_name), session_rollout_missing = $5, retired_session_id = COALESCE($8, retired_session_id), prepare_lease_expires_at = NULL WHERE id = $1 AND status = 'running' RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CompleteAgentTaskParams struct { ID pgtype.UUID `json:"id"` Result []byte `json:"result"` SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` SessionRolloutMissing bool `json:"session_rollout_missing"` DurableWorkDir pgtype.Text `json:"durable_work_dir"` BranchName pgtype.Text `json:"branch_name"` RetiredSessionID pgtype.Text `json:"retired_session_id"` } // session_rollout_missing (MUL-5305): when true the daemon withheld this task's // Codex session because its rollout was never written to the store. Forcing // session_id NULL and flagging the row happen in THIS terminal transaction so an // auto-retry created and woken by the same commit can never observe the bad // pointer or a missing gap flag. // // retired_session_id (GH #6066): a session this run abandoned as unresumable. // Recorded even on the completed path, because that is exactly the case the // resume lookups could not see — a fresh-session retry that SUCCEEDS still has // to retire the transcript it retried away from, or an older completed row // pointing at the same id resurrects it on the next run. func (q *Queries) CompleteAgentTask(ctx context.Context, arg CompleteAgentTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, completeAgentTask, arg.ID, arg.Result, arg.SessionID, arg.WorkDir, arg.SessionRolloutMissing, arg.DurableWorkDir, arg.BranchName, arg.RetiredSessionID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const countDelegatedFailureRecoveryTasks = `-- name: CountDelegatedFailureRecoveryTasks :one SELECT count(*) FROM agent_task_queue WHERE trigger_evidence_kind = 'delegated_failure' AND trigger_evidence_ref_id = $1 ` // Counts dedicated coordinator wakeups for one failed delegated task. Merged // recovery signals do not create a new row and therefore do not consume an // automatic attempt until they need a standalone successor. func (q *Queries) CountDelegatedFailureRecoveryTasks(ctx context.Context, failedTaskID pgtype.UUID) (int64, error) { row := q.db.QueryRow(ctx, countDelegatedFailureRecoveryTasks, failedTaskID) var count int64 err := row.Scan(&count) return count, err } const countRunningTasks = `-- name: CountRunningTasks :one SELECT count(*) FROM agent_task_queue WHERE agent_id = $1 AND status IN ('dispatched', 'running', 'waiting_local_directory') ` // waiting_local_directory remains capacity-bearing until resume admission has // an atomic reservation/CAS gate. Consequently a waiter-only agent is reported // idle by RefreshAgentStatusFromTasks but still cannot claim additional work; // removing it here alone could exceed max_concurrent_tasks when it resumes. func (q *Queries) CountRunningTasks(ctx context.Context, agentID pgtype.UUID) (int64, error) { row := q.db.QueryRow(ctx, countRunningTasks, agentID) var count int64 err := row.Scan(&count) return count, err } const createAgent = `-- name: CreateAgent :one INSERT INTO agent ( workspace_id, name, description, avatar_url, runtime_mode, runtime_config, runtime_id, visibility, max_concurrent_tasks, owner_id, instructions, custom_env, custom_args, mcp_config, model, thinking_level, service_tier, conversation_starters, composio_toolkit_allowlist, permission_mode ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, COALESCE($18::jsonb, '[]'::jsonb), $19::text[], COALESCE($20, 'private') ) RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type CreateAgentParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Name string `json:"name"` Description string `json:"description"` AvatarUrl pgtype.Text `json:"avatar_url"` RuntimeMode string `json:"runtime_mode"` RuntimeConfig []byte `json:"runtime_config"` RuntimeID pgtype.UUID `json:"runtime_id"` Visibility string `json:"visibility"` MaxConcurrentTasks int32 `json:"max_concurrent_tasks"` OwnerID pgtype.UUID `json:"owner_id"` Instructions string `json:"instructions"` CustomEnv []byte `json:"custom_env"` CustomArgs []byte `json:"custom_args"` McpConfig []byte `json:"mcp_config"` Model pgtype.Text `json:"model"` ThinkingLevel pgtype.Text `json:"thinking_level"` ServiceTier pgtype.Text `json:"service_tier"` ConversationStarters []byte `json:"conversation_starters"` ComposioToolkitAllowlist []string `json:"composio_toolkit_allowlist"` PermissionMode interface{} `json:"permission_mode"` } func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) (Agent, error) { row := q.db.QueryRow(ctx, createAgent, arg.WorkspaceID, arg.Name, arg.Description, arg.AvatarUrl, arg.RuntimeMode, arg.RuntimeConfig, arg.RuntimeID, arg.Visibility, arg.MaxConcurrentTasks, arg.OwnerID, arg.Instructions, arg.CustomEnv, arg.CustomArgs, arg.McpConfig, arg.Model, arg.ThinkingLevel, arg.ServiceTier, arg.ConversationStarters, arg.ComposioToolkitAllowlist, arg.PermissionMode, ) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const createAgentBuilder = `-- name: CreateAgentBuilder :one INSERT INTO agent ( workspace_id, name, description, runtime_mode, runtime_config, runtime_id, visibility, permission_mode, max_concurrent_tasks, owner_id, instructions, custom_env, custom_args, model, kind, system_key ) VALUES ( $1, $2, '', $3, '{}'::jsonb, $4, 'private', 'private', 1, $5, $6, '{}'::jsonb, '[]'::jsonb, $7, 'system', $8 ) RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type CreateAgentBuilderParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Name string `json:"name"` RuntimeMode string `json:"runtime_mode"` RuntimeID pgtype.UUID `json:"runtime_id"` OwnerID pgtype.UUID `json:"owner_id"` Instructions string `json:"instructions"` Model pgtype.Text `json:"model"` SystemKey pgtype.Text `json:"system_key"` } // One hidden builder agent per creation session. Keeping the execution carrier // session-scoped freezes its model/runtime configuration when multiple builder // flows are open concurrently, while `kind = 'system'` keeps it out of normal // agent lists and assignment surfaces. func (q *Queries) CreateAgentBuilder(ctx context.Context, arg CreateAgentBuilderParams) (Agent, error) { row := q.db.QueryRow(ctx, createAgentBuilder, arg.WorkspaceID, arg.Name, arg.RuntimeMode, arg.RuntimeID, arg.OwnerID, arg.Instructions, arg.Model, arg.SystemKey, ) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const createAgentTask = `-- name: CreateAgentTask :one INSERT INTO agent_task_queue ( agent_id, runtime_id, issue_id, status, priority, trigger_comment_id, coalesced_comment_ids, trigger_summary, force_fresh_session, is_leader_task, handoff_note, squad_id, context, originator_user_id, accountable_user_id, runtime_mcp_overlay, runtime_connected_apps, originator_source, delegated_from_task_id, rule_version_id, rerun_of_task_id, trigger_evidence_kind, trigger_evidence_ref_id, workflow_step, id ) SELECT $1, $2, $3, 'queued', $4, $5, COALESCE($6::uuid[], '{}'), $7, COALESCE($8::boolean, FALSE), COALESCE($9::boolean, FALSE), $10, $11, NULLIF(CASE WHEN COALESCE($12::text, '') <> '' THEN jsonb_build_object('head_sha', $12::text) ELSE '{}'::jsonb END || CASE WHEN COALESCE($13::boolean, FALSE) THEN '{"fresh_rerun":true}'::jsonb ELSE '{}'::jsonb END, '{}'::jsonb), $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, -- The issue's status as this task is queued: the workflow step it works on (MUL-7420). (SELECT i.status FROM issue i WHERE i.id = $3), COALESCE($24::uuid, gen_random_uuid()) WHERE lock_task_owner_rows($1, $3, $2) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CreateAgentTaskParams struct { AgentID pgtype.UUID `json:"agent_id"` RuntimeID pgtype.UUID `json:"runtime_id"` IssueID pgtype.UUID `json:"issue_id"` Priority int32 `json:"priority"` TriggerCommentID pgtype.UUID `json:"trigger_comment_id"` CoalescedCommentIds []pgtype.UUID `json:"coalesced_comment_ids"` TriggerSummary pgtype.Text `json:"trigger_summary"` ForceFreshSession pgtype.Bool `json:"force_fresh_session"` IsLeaderTask pgtype.Bool `json:"is_leader_task"` HandoffNote pgtype.Text `json:"handoff_note"` SquadID pgtype.UUID `json:"squad_id"` HeadSha pgtype.Text `json:"head_sha"` FreshRerun pgtype.Bool `json:"fresh_rerun"` OriginatorUserID pgtype.UUID `json:"originator_user_id"` AccountableUserID pgtype.UUID `json:"accountable_user_id"` RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` OriginatorSource pgtype.Text `json:"originator_source"` DelegatedFromTaskID pgtype.UUID `json:"delegated_from_task_id"` RuleVersionID pgtype.UUID `json:"rule_version_id"` RerunOfTaskID pgtype.UUID `json:"rerun_of_task_id"` TriggerEvidenceKind pgtype.Text `json:"trigger_evidence_kind"` TriggerEvidenceRefID pgtype.UUID `json:"trigger_evidence_ref_id"` ID pgtype.UUID `json:"id"` } // Fenced against workspace teardown: lock_task_owner_rows (migration 284) // locks the owners' workspace rows in the writer's own transaction and returns // false once they are gone, so this statement writes no row instead of stranding // a task in a workspace that has just been deleted (MUL-5999). // head_sha stamps the commit under review into the task's context JSONB so the // reviewer-loop dedup (HasPendingTaskForIssueAndAgent) can tell a pending run // against an OLD head apart from a fresh request against a NEW head (TEN-356). // Empty/absent head_sha leaves context NULL unless fresh_rerun is requested. // fresh_rerun disables source session/workdir reuse without losing rerun lineage. // Issue-linked tasks never hit quick-create context // parsing (parseQuickCreateContext short-circuits on IssueID.Valid), so this // key rides harmlessly alongside. // id is minted by the application as a UUIDv7 (pkg/dbid) so consecutive // enqueues cluster in a narrow contiguous primary-key range instead of // scattering across the B-tree. On a table with existing v4 ids, that range // is not necessarily the tree's right edge. // COALESCE keeps the column's gen_random_uuid() default reachable, so a caller // that passes no id still inserts — it just gets a random v4, exactly as before. // The same pattern is used by every INSERT listed in pkg/dbid's write table. func (q *Queries) CreateAgentTask(ctx context.Context, arg CreateAgentTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createAgentTask, arg.AgentID, arg.RuntimeID, arg.IssueID, arg.Priority, arg.TriggerCommentID, arg.CoalescedCommentIds, arg.TriggerSummary, arg.ForceFreshSession, arg.IsLeaderTask, arg.HandoffNote, arg.SquadID, arg.HeadSha, arg.FreshRerun, arg.OriginatorUserID, arg.AccountableUserID, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.OriginatorSource, arg.DelegatedFromTaskID, arg.RuleVersionID, arg.RerunOfTaskID, arg.TriggerEvidenceKind, arg.TriggerEvidenceRefID, arg.ID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const createDeferredChannelIssueTask = `-- name: CreateDeferredChannelIssueTask :one INSERT INTO agent_task_queue ( agent_id, runtime_id, issue_id, status, priority, trigger_comment_id, coalesced_comment_ids, trigger_summary, force_fresh_session, is_leader_task, handoff_note, squad_id, context, originator_user_id, accountable_user_id, runtime_mcp_overlay, runtime_connected_apps, originator_source, delegated_from_task_id, rule_version_id, rerun_of_task_id, trigger_evidence_kind, trigger_evidence_ref_id, fire_at, workflow_step, id ) SELECT $1, $2, $3, 'deferred', $4, $5, COALESCE($6::uuid[], '{}'), $7, COALESCE($8::boolean, FALSE), COALESCE($9::boolean, FALSE), $10, $11, jsonb_strip_nulls(jsonb_build_object( 'head_sha', NULLIF(COALESCE($12::text, ''), ''), 'channel_issue_media_pending', TRUE )), $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, -- The issue's status as this task is queued: the workflow step it works on (MUL-7420). (SELECT i.status FROM issue i WHERE i.id = $3), COALESCE($24::uuid, gen_random_uuid()) WHERE lock_task_owner_rows($1, $3, $2) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CreateDeferredChannelIssueTaskParams struct { AgentID pgtype.UUID `json:"agent_id"` RuntimeID pgtype.UUID `json:"runtime_id"` IssueID pgtype.UUID `json:"issue_id"` Priority int32 `json:"priority"` TriggerCommentID pgtype.UUID `json:"trigger_comment_id"` CoalescedCommentIds []pgtype.UUID `json:"coalesced_comment_ids"` TriggerSummary pgtype.Text `json:"trigger_summary"` ForceFreshSession pgtype.Bool `json:"force_fresh_session"` IsLeaderTask pgtype.Bool `json:"is_leader_task"` HandoffNote pgtype.Text `json:"handoff_note"` SquadID pgtype.UUID `json:"squad_id"` HeadSha pgtype.Text `json:"head_sha"` OriginatorUserID pgtype.UUID `json:"originator_user_id"` AccountableUserID pgtype.UUID `json:"accountable_user_id"` RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` OriginatorSource pgtype.Text `json:"originator_source"` DelegatedFromTaskID pgtype.UUID `json:"delegated_from_task_id"` RuleVersionID pgtype.UUID `json:"rule_version_id"` RerunOfTaskID pgtype.UUID `json:"rerun_of_task_id"` TriggerEvidenceKind pgtype.Text `json:"trigger_evidence_kind"` TriggerEvidenceRefID pgtype.UUID `json:"trigger_evidence_ref_id"` FireAt pgtype.Timestamptz `json:"fire_at"` ID pgtype.UUID `json:"id"` } // Fenced against workspace teardown: lock_task_owner_rows (migration 284) // locks the owners' workspace rows in the writer's own transaction and returns // false once they are gone, so this statement writes no row instead of stranding // a task in a workspace that has just been deleted (MUL-5999). // Channel /issue media resolves after issue creation. Persist the assigned // issue task up front for crash safety, but keep it inert until attachment // binding settles or the fire_at fallback is promoted by the normal sweeper. func (q *Queries) CreateDeferredChannelIssueTask(ctx context.Context, arg CreateDeferredChannelIssueTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createDeferredChannelIssueTask, arg.AgentID, arg.RuntimeID, arg.IssueID, arg.Priority, arg.TriggerCommentID, arg.CoalescedCommentIds, arg.TriggerSummary, arg.ForceFreshSession, arg.IsLeaderTask, arg.HandoffNote, arg.SquadID, arg.HeadSha, arg.OriginatorUserID, arg.AccountableUserID, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.OriginatorSource, arg.DelegatedFromTaskID, arg.RuleVersionID, arg.RerunOfTaskID, arg.TriggerEvidenceKind, arg.TriggerEvidenceRefID, arg.FireAt, arg.ID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const createManualQuickCreateRetryTask = `-- name: CreateManualQuickCreateRetryTask :one INSERT INTO agent_task_queue ( agent_id, runtime_id, status, priority, context, force_fresh_session, is_leader_task, squad_id, originator_user_id, accountable_user_id, runtime_mcp_overlay, runtime_connected_apps, originator_source, rerun_of_task_id, id ) SELECT p.agent_id, p.runtime_id, 'queued', p.priority, p.context, TRUE, p.is_leader_task, p.squad_id, $1, $1, $2, $3, 'direct_human', p.id, $4 FROM agent_task_queue p WHERE p.id = $5 AND p.status = 'failed' AND p.issue_id IS NULL AND p.chat_session_id IS NULL AND p.autopilot_run_id IS NULL AND lock_task_owner_rows(p.agent_id, p.issue_id, p.runtime_id) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CreateManualQuickCreateRetryTaskParams struct { ActorUserID pgtype.UUID `json:"actor_user_id"` RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` NewTaskID pgtype.UUID `json:"new_task_id"` SourceTaskID pgtype.UUID `json:"source_task_id"` } // A human retry of an issue-less quick-create is a new direct_human run, not // an automatic retry. It preserves the immutable quick-create context JSON // (including source_context_id), but deliberately starts a fresh session and // records rerun_of_task_id so attribution/reporting can distinguish the human // action from CreateRetryTask's retry_of_task_id lineage. // // The caller locks and transfers the pending issue_source_context in the same // transaction. lock_task_owner_rows provides the usual workspace-teardown // fence; a deleted workspace therefore yields pgx.ErrNoRows and no task. func (q *Queries) CreateManualQuickCreateRetryTask(ctx context.Context, arg CreateManualQuickCreateRetryTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createManualQuickCreateRetryTask, arg.ActorUserID, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.NewTaskID, arg.SourceTaskID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const createQuickCreateTask = `-- name: CreateQuickCreateTask :one INSERT INTO agent_task_queue ( agent_id, runtime_id, issue_id, status, priority, context, originator_user_id, accountable_user_id, runtime_mcp_overlay, runtime_connected_apps, originator_source, trigger_evidence_kind, trigger_evidence_ref_id, id ) SELECT $1, $2, NULL, 'queued', $3, $4, $5, $6, $7, $8, $9, $10, $11, COALESCE($12::uuid, gen_random_uuid()) WHERE lock_task_owner_rows($1, NULL, $2) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CreateQuickCreateTaskParams struct { AgentID pgtype.UUID `json:"agent_id"` RuntimeID pgtype.UUID `json:"runtime_id"` Priority int32 `json:"priority"` Context []byte `json:"context"` OriginatorUserID pgtype.UUID `json:"originator_user_id"` AccountableUserID pgtype.UUID `json:"accountable_user_id"` RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` OriginatorSource pgtype.Text `json:"originator_source"` TriggerEvidenceKind pgtype.Text `json:"trigger_evidence_kind"` TriggerEvidenceRefID pgtype.UUID `json:"trigger_evidence_ref_id"` ID pgtype.UUID `json:"id"` } // Fenced against workspace teardown: lock_task_owner_rows (migration 284) // locks the owners' workspace rows in the writer's own transaction and returns // false once they are gone, so this statement writes no row instead of stranding // a task in a workspace that has just been deleted (MUL-5999). // Quick-create tasks have no issue / chat / autopilot link; the entire job // description (prompt, requester, workspace) lives in context JSONB. The // daemon detects this variant via context.type == "quick_create". // The requester who opened the quick-create modal is a direct_human originator // and accountable; attribution provenance is stamped so this path is not a // NULL-source enqueue bypass (MUL-4302 §2). func (q *Queries) CreateQuickCreateTask(ctx context.Context, arg CreateQuickCreateTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createQuickCreateTask, arg.AgentID, arg.RuntimeID, arg.Priority, arg.Context, arg.OriginatorUserID, arg.AccountableUserID, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.OriginatorSource, arg.TriggerEvidenceKind, arg.TriggerEvidenceRefID, arg.ID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const createRetryTask = `-- name: CreateRetryTask :one INSERT INTO agent_task_queue ( agent_id, runtime_id, issue_id, chat_session_id, autopilot_run_id, status, priority, trigger_comment_id, coalesced_comment_ids, trigger_summary, context, session_id, work_dir, attempt, max_attempts, parent_task_id, force_fresh_session, is_leader_task, squad_id, originator_user_id, accountable_user_id, runtime_mcp_overlay, runtime_connected_apps, originator_source, delegated_from_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, retry_of_task_id, chat_input_task_id, fire_at, channel_context_revision, handoff_note, workflow_step, id ) SELECT p.agent_id, p.runtime_id, p.issue_id, p.chat_session_id, p.autopilot_run_id, CASE WHEN $2::timestamptz IS NOT NULL THEN 'deferred' ELSE 'queued' END, CASE WHEN p.chat_session_id IS NOT NULL THEN GREATEST(p.priority, 3) ELSE p.priority END, p.trigger_comment_id, p.coalesced_comment_ids, p.trigger_summary, p.context, CASE WHEN p.failure_reason IS NOT DISTINCT FROM 'codex_semantic_inactivity' THEN NULL ELSE p.session_id END, p.work_dir, p.attempt + 1, COALESCE($3::int, p.max_attempts), p.id, p.failure_reason IS NOT DISTINCT FROM 'codex_semantic_inactivity', p.is_leader_task, p.squad_id, p.originator_user_id, p.accountable_user_id, $4, $5, p.originator_source, p.delegated_from_task_id, p.rule_version_id, p.trigger_evidence_kind, p.trigger_evidence_ref_id, p.id, p.chat_input_task_id, $2, p.channel_context_revision, -- Carried for a wakeup's prompt and a workflow handoff's brief (MUL-7420); -- rows from before workflow_step existed may hold a retired assignment note. CASE WHEN p.context->>'wakeup_id' IS NOT NULL OR p.workflow_step IS NOT NULL THEN p.handoff_note END, -- A retry continues its parent's work, so it keeps the parent's step (MUL-7420). p.workflow_step, -- Named new_task_id, not id: $1 above is the PARENT task's id. COALESCE($6::uuid, gen_random_uuid()) FROM agent_task_queue p WHERE p.id = $1 AND lock_task_owner_rows(p.agent_id, p.issue_id, p.runtime_id) ON CONFLICT (issue_id, agent_id, (COALESCE(comment_thread_id, '00000000-0000-0000-0000-000000000000'::uuid))) WHERE status IN ('queued', 'dispatched') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') DO NOTHING RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type CreateRetryTaskParams struct { ID pgtype.UUID `json:"id"` FireAt pgtype.Timestamptz `json:"fire_at"` MaxAttempts pgtype.Int4 `json:"max_attempts"` RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` NewTaskID pgtype.UUID `json:"new_task_id"` } // Clone an attempt with inherited attribution, planned comments, and input-batch // identity. The child earns its own delivered-comment receipt at claim time. // Preserve a NULL chat_input_task_id for legacy/channel parents rather than // promoting untagged history to the task-owned input path. // // Keep work_dir for every retry, but clear resume context for unsafe sessions. // Keep failure predicates aligned with resumeUnsafeFailureReason and resume lookups. // Composio uses the agent owner's connection; originator remains audit provenance. // // Owner-row locks fence workspace teardown. Slot conflicts yield no row instead // of aborting the caller's failure transaction; pgx.ErrNoRows means commit the // parent transition without a retry. Pending human reruns retain their slot. // // Chat retry priority keeps older input ahead of newer turns. A non-NULL fire_at // defers dispatch until promotion. max_attempts optionally persists the effective // ceiling; it does not re-enable tasks whose retry budget was disabled. func (q *Queries) CreateRetryTask(ctx context.Context, arg CreateRetryTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, createRetryTask, arg.ID, arg.FireAt, arg.MaxAttempts, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.NewTaskID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const createSystemUserAgent = `-- name: CreateSystemUserAgent :one INSERT INTO agent ( workspace_id, name, description, avatar_url, runtime_mode, runtime_config, runtime_id, model, visibility, permission_mode, max_concurrent_tasks, owner_id, instructions, custom_env, custom_args, kind, system_key ) VALUES ( $1, $2, $3, $4, $5, '{}'::jsonb, $6, $7, $8, $9, $10, $11, '', '{}'::jsonb, '[]'::jsonb, 'user', $12 ) RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type CreateSystemUserAgentParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Name string `json:"name"` Description string `json:"description"` AvatarUrl pgtype.Text `json:"avatar_url"` RuntimeMode string `json:"runtime_mode"` RuntimeID pgtype.UUID `json:"runtime_id"` Model pgtype.Text `json:"model"` Visibility string `json:"visibility"` PermissionMode string `json:"permission_mode"` MaxConcurrentTasks int32 `json:"max_concurrent_tasks"` OwnerID pgtype.UUID `json:"owner_id"` SystemKey pgtype.Text `json:"system_key"` } // Creates a product-defined agent that members can still see, chat with, and // assign issues to. Deliberately kind='user': kind='system' hides the row from // agent lists and assignment surfaces and hard deletes it with its runtime. // Every product-owned field is a server constant; instructions stays empty // because the system half ships with the binary and is layered in at claim // time, leaving this column free for the workspace's own notes. // // CONTRACT: call only while holding the per-workspace mika advisory lock. The // one-per-workspace invariant rests on a check inside that lock, not on a // unique index — migration 172's index keys on (workspace_id, owner_id, // runtime_id, system_key), so two different owners or runtimes are distinct // tuples and would both insert. func (q *Queries) CreateSystemUserAgent(ctx context.Context, arg CreateSystemUserAgentParams) (Agent, error) { row := q.db.QueryRow(ctx, createSystemUserAgent, arg.WorkspaceID, arg.Name, arg.Description, arg.AvatarUrl, arg.RuntimeMode, arg.RuntimeID, arg.Model, arg.Visibility, arg.PermissionMode, arg.MaxConcurrentTasks, arg.OwnerID, arg.SystemKey, ) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const deleteSystemAgentByID = `-- name: DeleteSystemAgentByID :exec DELETE FROM agent WHERE id = $1 AND kind = 'system' AND system_key LIKE 'agent_builder:%' ` // Builder sessions own their hidden execution agent. Deleting the session // removes that carrier and its task rows; the kind guard prevents this cleanup // path from ever deleting a user-authored agent. func (q *Queries) DeleteSystemAgentByID(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteSystemAgentByID, id) return err } const deleteUnstartedQuickCreateRetryTask = `-- name: DeleteUnstartedQuickCreateRetryTask :execrows DELETE FROM agent_task_queue WHERE id = $1 AND status IN ('queued', 'deferred') AND issue_id IS NULL AND chat_session_id IS NULL AND autopilot_run_id IS NULL ` // FailTask creates an automatic retry in the same transaction as the parent // terminal write. An issue-less source-context retry has no pending-slot // unique key, so a competing retry or an already-attached context can make // the subsequent attach-authority transfer lose after this row was inserted. // Remove only that still-uncommitted child and let the parent's failure commit. func (q *Queries) DeleteUnstartedQuickCreateRetryTask(ctx context.Context, taskID pgtype.UUID) (int64, error) { result, err := q.db.Exec(ctx, deleteUnstartedQuickCreateRetryTask, taskID) if err != nil { return 0, err } return result.RowsAffected(), nil } const expireStaleQueuedTasks = `-- name: ExpireStaleQueuedTasks :many WITH victims AS ( SELECT id FROM agent_task_queue WHERE status = 'queued' AND context->>'wakeup_id' IS NULL AND created_at < now() - make_interval(secs => $1::double precision) AND ( runtime_id IS NULL OR NOT EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = agent_task_queue.runtime_id ) OR EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = agent_task_queue.runtime_id AND COALESCE(r.last_seen_at, r.updated_at) < now() - make_interval(secs => $1::double precision) ) ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue retry_parent WHERE retry_parent.id = agent_task_queue.parent_task_id AND retry_parent.failure_reason = 'runtime_offline' ) ORDER BY created_at ASC LIMIT $2::int FOR UPDATE SKIP LOCKED ) UPDATE agent_task_queue t SET status = 'failed', completed_at = now(), error = 'runtime unavailable while task was queued', failure_reason = 'queued_expired', prepare_lease_expires_at = NULL FROM victims v WHERE t.id = v.id AND t.status = 'queued' AND t.created_at < now() - make_interval(secs => $1::double precision) AND ( t.runtime_id IS NULL OR NOT EXISTS (SELECT 1 FROM agent_runtime r WHERE r.id = t.runtime_id) OR EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = t.runtime_id AND COALESCE(r.last_seen_at, r.updated_at) < now() - make_interval(secs => $1::double precision) ) ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue retry_parent WHERE retry_parent.id = t.parent_task_id AND retry_parent.failure_reason = 'runtime_offline' ) RETURNING t.id, t.agent_id, t.issue_id, t.status, t.priority, t.dispatched_at, t.started_at, t.completed_at, t.result, t.error, t.created_at, t.context, t.runtime_id, t.session_id, t.work_dir, t.trigger_comment_id, t.chat_session_id, t.autopilot_run_id, t.attempt, t.max_attempts, t.parent_task_id, t.failure_reason, t.trigger_summary, t.force_fresh_session, t.is_leader_task, t.wait_reason, t.initiator_user_id, t.handoff_note, t.prepare_lease_expires_at, t.squad_id, t.runtime_mcp_overlay, t.escalation_for_task_id, t.fire_at, t.originator_user_id, t.runtime_connected_apps, t.coalesced_comment_ids, t.delivered_comment_ids, t.chat_input_task_id, t.chat_finalize_deferred_at, t.originator_source, t.delegated_from_task_id, t.retry_of_task_id, t.rerun_of_task_id, t.rule_version_id, t.trigger_evidence_kind, t.trigger_evidence_ref_id, t.accountable_user_id, t.session_rollout_missing, t.retired_session_id, t.quick_actions_disabled, t.regenerate_quick_actions_for, t.branch_name, t.durable_work_dir, t.channel_context_revision, t.comment_thread_id, t.cancelled_by_type, t.cancelled_by_id, t.cancelled_by_name, t.issue_snapshot, t.workflow_step ` type ExpireStaleQueuedTasksParams struct { ReconnectGraceSecs float64 `json:"reconnect_grace_secs"` MaxPerTick int32 `json:"max_per_tick"` } // Expire queued work only when both the task's waiting age and the runtime's // heartbeat age exceed reconnect grace. Busy but heartbeating runtimes keep their // queues regardless of length. Read heartbeat age directly so a stale online // status cannot strand tasks. Missing runtime references are handled defensively. // // runtime_offline retries have a separate reconnect expiry policy and are exempt. // Select bounded victims with SKIP LOCKED, then re-check status and liveness in // the update so concurrent claims cannot be overwritten. func (q *Queries) ExpireStaleQueuedTasks(ctx context.Context, arg ExpireStaleQueuedTasksParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, expireStaleQueuedTasks, arg.ReconnectGraceSecs, arg.MaxPerTick) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const extendAgentTaskPrepareLease = `-- name: ExtendAgentTaskPrepareLease :one UPDATE agent_task_queue SET prepare_lease_expires_at = now() + make_interval(secs => $3::double precision) WHERE id = $1 AND runtime_id = $2 AND status IN ('dispatched', 'waiting_local_directory') AND started_at IS NULL RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type ExtendAgentTaskPrepareLeaseParams struct { ID pgtype.UUID `json:"id"` RuntimeID pgtype.UUID `json:"runtime_id"` LeaseSecs float64 `json:"lease_secs"` } // Keeps a dispatched task protected while the daemon resolves/cache/materializes // startup inputs before StartTask. Once the daemon stops extending this short // lease, the stale-dispatched reclaim path can recover the task without waiting // for a long global recovery window. func (q *Queries) ExtendAgentTaskPrepareLease(ctx context.Context, arg ExtendAgentTaskPrepareLeaseParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, extendAgentTaskPrepareLease, arg.ID, arg.RuntimeID, arg.LeaseSecs) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const failAgentTask = `-- name: FailAgentTask :one UPDATE agent_task_queue SET status = 'failed', completed_at = now(), error = $2, failure_reason = COALESCE($3, 'agent_error'), session_id = CASE WHEN $4 THEN NULL ELSE COALESCE($5, session_id) END, work_dir = COALESCE($6, work_dir), durable_work_dir = COALESCE($7, durable_work_dir), branch_name = COALESCE($8, branch_name), session_rollout_missing = $4, retired_session_id = COALESCE($9, retired_session_id), prepare_lease_expires_at = NULL WHERE id = $1 AND status IN ('dispatched', 'running', 'waiting_local_directory') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type FailAgentTaskParams struct { ID pgtype.UUID `json:"id"` Error pgtype.Text `json:"error"` FailureReason pgtype.Text `json:"failure_reason"` SessionRolloutMissing bool `json:"session_rollout_missing"` SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` DurableWorkDir pgtype.Text `json:"durable_work_dir"` BranchName pgtype.Text `json:"branch_name"` RetiredSessionID pgtype.Text `json:"retired_session_id"` } // Marks a task as failed. session_id and work_dir are merged via COALESCE so // if the agent already established a real session before failing (e.g. it // crashed mid-conversation, was cancelled, or hit a tool error) the resume // pointer is preserved on the task row. The next chat task can then fall // back to GetLastChatTaskSession and continue the conversation instead of // silently starting over. // // failure_reason is a coarse classifier consumed by the auto-retry path; // 'agent_error' is the safe default when the daemon doesn't supply one. // // session_rollout_missing (MUL-5305): when true the daemon withheld this task's // Codex session (its rollout was missing). Force session_id NULL — overriding // the COALESCE that would otherwise preserve a stale mid-flight pin — and flag // the row, in the SAME transaction that creates and wakes the auto-retry, so the // retry can never claim the bad pointer or miss the continuity gap. func (q *Queries) FailAgentTask(ctx context.Context, arg FailAgentTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, failAgentTask, arg.ID, arg.Error, arg.FailureReason, arg.SessionRolloutMissing, arg.SessionID, arg.WorkDir, arg.DurableWorkDir, arg.BranchName, arg.RetiredSessionID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const failExpiredRuntimeReconnectRetries = `-- name: FailExpiredRuntimeReconnectRetries :many WITH victims AS ( SELECT retry.id FROM agent_task_queue retry JOIN agent_task_queue parent ON parent.id = retry.parent_task_id WHERE retry.status = 'deferred' AND retry.fire_at < now() - make_interval(secs => $1::double precision) AND parent.failure_reason = 'runtime_offline' AND NOT EXISTS ( SELECT 1 FROM agent_runtime runtime WHERE runtime.id = retry.runtime_id AND runtime.status = 'online' AND COALESCE(runtime.last_seen_at, runtime.updated_at) >= now() - make_interval(secs => $2::double precision) ) ORDER BY retry.fire_at, retry.created_at LIMIT $3::int FOR UPDATE OF retry SKIP LOCKED ) UPDATE agent_task_queue AS retry SET status = 'failed', completed_at = now(), error = 'runtime did not reconnect within the configured grace period', failure_reason = 'runtime_reconnect_timeout', wait_reason = NULL, prepare_lease_expires_at = NULL FROM victims WHERE retry.id = victims.id AND retry.status = 'deferred' AND retry.fire_at < now() - make_interval(secs => $1::double precision) AND EXISTS ( SELECT 1 FROM agent_task_queue parent WHERE parent.id = retry.parent_task_id AND parent.failure_reason = 'runtime_offline' ) AND NOT EXISTS ( SELECT 1 FROM agent_runtime runtime WHERE runtime.id = retry.runtime_id AND runtime.status = 'online' AND COALESCE(runtime.last_seen_at, runtime.updated_at) >= now() - make_interval(secs => $2::double precision) ) RETURNING retry.id, retry.agent_id, retry.issue_id, retry.status, retry.priority, retry.dispatched_at, retry.started_at, retry.completed_at, retry.result, retry.error, retry.created_at, retry.context, retry.runtime_id, retry.session_id, retry.work_dir, retry.trigger_comment_id, retry.chat_session_id, retry.autopilot_run_id, retry.attempt, retry.max_attempts, retry.parent_task_id, retry.failure_reason, retry.trigger_summary, retry.force_fresh_session, retry.is_leader_task, retry.wait_reason, retry.initiator_user_id, retry.handoff_note, retry.prepare_lease_expires_at, retry.squad_id, retry.runtime_mcp_overlay, retry.escalation_for_task_id, retry.fire_at, retry.originator_user_id, retry.runtime_connected_apps, retry.coalesced_comment_ids, retry.delivered_comment_ids, retry.chat_input_task_id, retry.chat_finalize_deferred_at, retry.originator_source, retry.delegated_from_task_id, retry.retry_of_task_id, retry.rerun_of_task_id, retry.rule_version_id, retry.trigger_evidence_kind, retry.trigger_evidence_ref_id, retry.accountable_user_id, retry.session_rollout_missing, retry.retired_session_id, retry.quick_actions_disabled, retry.regenerate_quick_actions_for, retry.branch_name, retry.durable_work_dir, retry.channel_context_revision, retry.comment_thread_id, retry.cancelled_by_type, retry.cancelled_by_id, retry.cancelled_by_name, retry.issue_snapshot, retry.workflow_step ` type FailExpiredRuntimeReconnectRetriesParams struct { ReconnectGraceSecs float64 `json:"reconnect_grace_secs"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` MaxPerTick int32 `json:"max_per_tick"` } // A runtime_offline retry waits in deferred until its runtime returns healthy. // Give that recovery state a bounded exit: after one full reconnect grace, // fail it with a non-retryable reason so issues, agents, and runtime GC can // converge. A runtime that is healthy when this query runs wins the race and // remains deferred for the next daemon poll to promote. The parent join is the // lineage discriminator; provider backoff and other deferred task types are // intentionally excluded. func (q *Queries) FailExpiredRuntimeReconnectRetries(ctx context.Context, arg FailExpiredRuntimeReconnectRetriesParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, failExpiredRuntimeReconnectRetries, arg.ReconnectGraceSecs, arg.RuntimeStaleSecs, arg.MaxPerTick) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const failStaleTasks = `-- name: FailStaleTasks :many UPDATE agent_task_queue SET status = 'failed', completed_at = now(), error = 'task timed out', failure_reason = 'timeout', prepare_lease_expires_at = NULL WHERE ( status = 'dispatched' AND dispatched_at < now() - make_interval(secs => $1::double precision) AND (prepare_lease_expires_at IS NULL OR prepare_lease_expires_at < now()) AND ( runtime_id IS NULL OR NOT EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = agent_task_queue.runtime_id ) OR EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = agent_task_queue.runtime_id AND ( ( r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $2::double precision) ) OR COALESCE(r.last_seen_at, r.updated_at) < now() - make_interval(secs => $3::double precision) ) ) ) ) OR ( status = 'running' AND started_at < now() - make_interval(secs => $4::double precision) AND ( runtime_id IS NULL OR NOT EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = agent_task_queue.runtime_id AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $3::double precision) ) ) ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type FailStaleTasksParams struct { DispatchTimeoutSecs float64 `json:"dispatch_timeout_secs"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` RuntimeReconnectGraceSecs float64 `json:"runtime_reconnect_grace_secs"` RunningTimeoutSecs float64 `json:"running_timeout_secs"` } // Fails tasks stuck in dispatched/running beyond the given thresholds. // // Each branch pairs a wall-clock deadline with a task-appropriate liveness // signal, so the sweeper only kills tasks whose owning daemon is no longer // proving it is alive: // // - Dispatched: `prepare_lease_expires_at` is refreshed every 15s by the // daemon between claim and StartTask (see startTaskPrepareLeaseExtender). // A live lease excludes the row. An expired lease may fail immediately on // a healthy runtime, but a disconnected runtime gets the same reconnect // grace as a running task. // // - Running: no per-task lease is renewed once StartTask fires, so we key // off the daemon-wide heartbeat instead — `agent_runtime.last_seen_at`, // which the daemon bumps every ~15s while it is up. A running task is not // killed until that heartbeat has been absent for the full reconnect // grace, even when its own wall-clock timeout elapsed earlier. This keeps // healthy multi-hour work alive through a network partition. // // The daemon-dead case is recovered immediately when that daemon restarts via // RecoverOrphanedTasksForRuntime. Until then, this query and // FailTasksForOfflineRuntimes share the same bounded reconnect grace. // // runtime_id IS NULL: a running row with no runtime is by definition not // proving liveness, so the wall clock is allowed to fire — same shape as // the legacy pure-wall-clock behavior for that (rare / historical) case. // // waiting_local_directory rows are intentionally excluded: the daemon owns // the wait (with its own ctx-driven timeout) and a legitimate queue ahead // of this task can exceed the dispatch / running timeouts without being // "stuck". If the daemon dies, RecoverOrphanedTasksForRuntime reclaims // those rows at restart. func (q *Queries) FailStaleTasks(ctx context.Context, arg FailStaleTasksParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, failStaleTasks, arg.DispatchTimeoutSecs, arg.RuntimeStaleSecs, arg.RuntimeReconnectGraceSecs, arg.RunningTimeoutSecs, ) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getAgent = `-- name: GetAgent :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE id = $1 ` func (q *Queries) GetAgent(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, getAgent, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const getAgentBySystemKey = `-- name: GetAgentBySystemKey :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE workspace_id = $1 AND system_key = $2 AND archived_at IS NULL ORDER BY created_at ASC, id ASC LIMIT 1 ` type GetAgentBySystemKeyParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` SystemKey pgtype.Text `json:"system_key"` } // Resolves a workspace's built-in agent by its stable system_key. This is the // identity lookup for system agents: their display name is owner-editable, so // nothing server-side may key off it. func (q *Queries) GetAgentBySystemKey(ctx context.Context, arg GetAgentBySystemKeyParams) (Agent, error) { row := q.db.QueryRow(ctx, getAgentBySystemKey, arg.WorkspaceID, arg.SystemKey) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const getAgentForClaimUpdate = `-- name: GetAgentForClaimUpdate :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE id = $1 FOR UPDATE ` func (q *Queries) GetAgentForClaimUpdate(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, getAgentForClaimUpdate, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const getAgentForUpdate = `-- name: GetAgentForUpdate :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE id = $1 FOR UPDATE ` // Serializes read-modify-write updates to disabled_runtime_skills so two // concurrent per-skill toggles cannot overwrite each other. func (q *Queries) GetAgentForUpdate(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, getAgentForUpdate, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const getAgentInWorkspace = `-- name: GetAgentInWorkspace :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE id = $1 AND workspace_id = $2 AND kind = 'user' ` type GetAgentInWorkspaceParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } func (q *Queries) GetAgentInWorkspace(ctx context.Context, arg GetAgentInWorkspaceParams) (Agent, error) { row := q.db.QueryRow(ctx, getAgentInWorkspace, arg.ID, arg.WorkspaceID) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const getAgentTask = `-- name: GetAgentTask :one SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE id = $1 ` func (q *Queries) GetAgentTask(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, getAgentTask, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const getAgentTaskForDelegatedFailureUpdate = `-- name: GetAgentTaskForDelegatedFailureUpdate :one SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE id = $1 FOR UPDATE ` // Serializes the idempotent delegated-failure recovery signal for one failed // task. FailTask and the stale-task sweepers can converge on the same row; the // lock ensures they cannot create two recovery comments/tasks for it. func (q *Queries) GetAgentTaskForDelegatedFailureUpdate(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, getAgentTaskForDelegatedFailureUpdate, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const getAgentTaskInWorkspace = `-- name: GetAgentTaskInWorkspace :one SELECT atq.id, atq.agent_id, atq.issue_id, atq.status, atq.priority, atq.dispatched_at, atq.started_at, atq.completed_at, atq.result, atq.error, atq.created_at, atq.context, atq.runtime_id, atq.session_id, atq.work_dir, atq.trigger_comment_id, atq.chat_session_id, atq.autopilot_run_id, atq.attempt, atq.max_attempts, atq.parent_task_id, atq.failure_reason, atq.trigger_summary, atq.force_fresh_session, atq.is_leader_task, atq.wait_reason, atq.initiator_user_id, atq.handoff_note, atq.prepare_lease_expires_at, atq.squad_id, atq.runtime_mcp_overlay, atq.escalation_for_task_id, atq.fire_at, atq.originator_user_id, atq.runtime_connected_apps, atq.coalesced_comment_ids, atq.delivered_comment_ids, atq.chat_input_task_id, atq.chat_finalize_deferred_at, atq.originator_source, atq.delegated_from_task_id, atq.retry_of_task_id, atq.rerun_of_task_id, atq.rule_version_id, atq.trigger_evidence_kind, atq.trigger_evidence_ref_id, atq.accountable_user_id, atq.session_rollout_missing, atq.retired_session_id, atq.quick_actions_disabled, atq.regenerate_quick_actions_for, atq.branch_name, atq.durable_work_dir, atq.channel_context_revision, atq.comment_thread_id, atq.cancelled_by_type, atq.cancelled_by_id, atq.cancelled_by_name, atq.issue_snapshot, atq.workflow_step FROM agent_task_queue atq JOIN agent a ON a.id = atq.agent_id WHERE atq.id = $1 AND a.workspace_id = $2 ` type GetAgentTaskInWorkspaceParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } // Loads a task only when its owning agent lives in the given workspace. // agent_id is NOT NULL on every task row (and ON DELETE CASCADE, so the agent // always exists), which makes this the universal tenant guard for // user-initiated cancellation — independent of which optional source FK // (issue / chat_session / autopilot_run) happens to be set. It is what lets // run_only autopilot tasks and quick_create tasks (whose issue does not exist // yet) be cancelled at all, instead of 404-ing on a missing source FK. func (q *Queries) GetAgentTaskInWorkspace(ctx context.Context, arg GetAgentTaskInWorkspaceParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, getAgentTaskInWorkspace, arg.ID, arg.WorkspaceID) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const getAgentTaskStatus = `-- name: GetAgentTaskStatus :one SELECT atq.status, a.workspace_id, atq.runtime_id, rt.workspace_id AS runtime_workspace_id, rt.daemon_id AS runtime_daemon_id, rt.owner_id AS runtime_owner_id FROM agent_task_queue atq JOIN agent a ON a.id = atq.agent_id LEFT JOIN agent_runtime rt ON rt.id = atq.runtime_id WHERE atq.id = $1 ` type GetAgentTaskStatusRow struct { Status string `json:"status"` WorkspaceID pgtype.UUID `json:"workspace_id"` RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeWorkspaceID pgtype.UUID `json:"runtime_workspace_id"` RuntimeDaemonID pgtype.Text `json:"runtime_daemon_id"` RuntimeOwnerID pgtype.UUID `json:"runtime_owner_id"` } // Hot-path status polling needs only the task status and the owning agent's // workspace for authorization. Keep this independent of optional source links // (issue, chat session, autopilot run) so it needs no source-entity lookup. // The bound runtime's workspace / daemon / owner ride along (LEFT JOIN: a // terminal task outlives a deleted runtime) so the daemon route can enforce // runtime ownership without a second round trip (MUL-8154). func (q *Queries) GetAgentTaskStatus(ctx context.Context, id pgtype.UUID) (GetAgentTaskStatusRow, error) { row := q.db.QueryRow(ctx, getAgentTaskStatus, id) var i GetAgentTaskStatusRow err := row.Scan( &i.Status, &i.WorkspaceID, &i.RuntimeID, &i.RuntimeWorkspaceID, &i.RuntimeDaemonID, &i.RuntimeOwnerID, ) return i, err } const getCommentThreadRootID = `-- name: GetCommentThreadRootID :one SELECT comment_thread_root_id($1::uuid)::uuid AS id ` func (q *Queries) GetCommentThreadRootID(ctx context.Context, commentID pgtype.UUID) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, getCommentThreadRootID, commentID) var id pgtype.UUID err := row.Scan(&id) return id, err } const getLastTaskSession = `-- name: GetLastTaskSession :one WITH retired_sessions AS ( SELECT DISTINCT r.retired_session_id AS session_id FROM agent_task_queue r WHERE r.agent_id = $1 AND r.issue_id = $2 AND COALESCE(r.context->>'type', '') <> 'triage' AND r.retired_session_id IS NOT NULL ), resume_overflow_at AS ( SELECT MAX(COALESCE(t.completed_at, t.started_at, t.dispatched_at, t.created_at)) AS at FROM agent_task_queue t WHERE t.agent_id = $1 AND t.issue_id = $2 AND COALESCE(t.context->>'type', '') <> 'triage' AND t.status = 'failed' AND ( COALESCE(t.failure_reason, '') = 'codex_resume_oversized' OR (COALESCE(t.error, '') ILIKE '%thread/resume failed%' AND COALESCE(t.error, '') ILIKE '%token too long%') ) ), latest_per_session AS ( SELECT DISTINCT ON (t.session_id) t.session_id, t.work_dir, t.runtime_id, t.status, t.failure_reason, t.error, t.started_at, t.issue_snapshot, COALESCE(t.completed_at, t.started_at, t.dispatched_at, t.created_at) AS terminal_at FROM agent_task_queue t WHERE t.agent_id = $1 AND t.issue_id = $2 AND COALESCE(t.context->>'type', '') <> 'triage' AND t.session_id IS NOT NULL AND t.status IN ('completed', 'failed', 'cancelled') ORDER BY t.session_id, COALESCE(t.completed_at, t.started_at, t.dispatched_at, t.created_at) DESC ) SELECT session_id, work_dir, runtime_id, status, started_at, issue_snapshot FROM latest_per_session WHERE session_id NOT IN (SELECT session_id FROM retired_sessions) AND ( status IN ('completed', 'cancelled') OR ( status = 'failed' AND COALESCE(failure_reason, '') NOT IN ('iteration_limit', 'agent_fallback_message', 'api_invalid_request', 'codex_semantic_inactivity', 'agent_error.context_overflow', 'codex_resume_oversized', 'opencode_clock_skew') AND NOT (COALESCE(error, '') ILIKE '%400%' AND COALESCE(error, '') ILIKE '%invalid_request_error%') AND NOT (COALESCE(error, '') ILIKE '%image dimensions exceed max allowed size%' AND COALESCE(error, '') ILIKE '%image.source.base64.data%') -- A provider credential-resolution failure ("Could not resolve -- authentication method...") is deterministic on resume: the missing -- api_key / auth_token / header is baked into the session's provider -- state, so replaying it reproduces the same provider error forever. It -- is classified agent_error.unknown (resume-safe), so this text guard is -- the only thing that keeps a wedged issue from resuming the same dead -- session on its next trigger — there is no daemon upgrade to wait for. -- Keep in sync with ResumeUnsafeFailure and GetLastChatTaskSession. -- The phrase itself lives in taskfailure.AuthMethodUnresolved, which the -- daemon's in-turn fresh-session retry reads (GH #6777). This guard stays -- because it is the only protection for rows an older daemon wrote. AND NOT (COALESCE(error, '') ILIKE '%could not resolve authentication method%') AND NOT (COALESCE(error, '') ~* 'must not be empty|must be non-?empty|must have non-?empty|non-?empty content|cannot be empty|should not be empty' AND COALESCE(error, '') ~* 'role[^a-z0-9]{0,2}assistant|assistant message|message at position|messages\.[0-9]|messages\[[0-9]') -- MUL-8188: a runtime that names its own transcript invalid (Kiro panics -- with "invalid conversation history received" on every prompt) replays -- the same failure on every resume. Mirrors taskfailure.UnresumableHistory. AND NOT (COALESCE(error, '') ILIKE '%invalid conversation history%') ) ) -- MUL-5722: a resume that overflowed the reader names no session, so it can -- only be excluded by time, not by matching the failed row. Drop every -- session whose last terminal activity predates the newest such failure: one -- of them IS the oversized thread, and the row that would tell us which is -- exactly the row that could not be written. Anything that terminated after -- the overflow is the fresh thread that replaced it, so this un-blocks itself -- as soon as one succeeds. AND ( (SELECT at FROM resume_overflow_at) IS NULL OR terminal_at > (SELECT at FROM resume_overflow_at) ) ORDER BY terminal_at DESC LIMIT 1 ` type GetLastTaskSessionParams struct { AgentID pgtype.UUID `json:"agent_id"` IssueID pgtype.UUID `json:"issue_id"` } type GetLastTaskSessionRow struct { SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` RuntimeID pgtype.UUID `json:"runtime_id"` Status string `json:"status"` StartedAt pgtype.Timestamptz `json:"started_at"` IssueSnapshot []byte `json:"issue_snapshot"` } // Find resume context for an agent/issue from completed, failed, or cancelled // execution tasks. Manual rerun uses its exact source task instead; its fresh // flag also protects older claim handlers during mixed-version deployment. // // Reduce to the newest terminal row per session before applying safety filters; // otherwise an older healthy row could revive the same poisoned session. // Exclude explicitly retired sessions even when clean older rows still name them. // Triage sessions must never cross into execution or vice versa. // // Keep failure-reason and text guards aligned with GetLastChatTaskSession and // resumeUnsafeFailureReason. Text guards protect legacy or unclassified rows. // Resume overflow can leave no session ID, so its timestamp excludes earlier // sessions until a later healthy terminal row restores eligibility. // status, started_at and issue_snapshot ride along because the row this query // picks IS the run whose context the next turn continues, and both of a claim's // deltas must be measured from THAT run rather than from whichever run started // last (MUL-7344). status is what says the run actually delivered its prompt to // the provider: this query deliberately accepts failed and cancelled rows so // their SESSION stays resumable, but such a row may have died before the agent // ever ran, and its snapshot would then describe an issue the session never // saw. The two are not always the same row: this query skips poisoned // and retired sessions, so it can legitimately return an OLDER run than the // newest one. Measuring against the newest one would then tell an agent whose // resumed memory predates an edit that the issue is unchanged. func (q *Queries) GetLastTaskSession(ctx context.Context, arg GetLastTaskSessionParams) (GetLastTaskSessionRow, error) { row := q.db.QueryRow(ctx, getLastTaskSession, arg.AgentID, arg.IssueID) var i GetLastTaskSessionRow err := row.Scan( &i.SessionID, &i.WorkDir, &i.RuntimeID, &i.Status, &i.StartedAt, &i.IssueSnapshot, ) return i, err } const getLatestChatTaskRolloutMissing = `-- name: GetLatestChatTaskRolloutMissing :one SELECT COALESCE(session_rollout_missing, FALSE) FROM agent_task_queue WHERE chat_session_id = $1 AND ( $2::bigint IS NULL OR COALESCE(channel_context_revision, 1) = $2::bigint ) AND status IN ('completed', 'failed') AND started_at IS NOT NULL ORDER BY COALESCE(completed_at, started_at, dispatched_at, created_at) DESC LIMIT 1 ` type GetLatestChatTaskRolloutMissingParams struct { ChatSessionID pgtype.UUID `json:"chat_session_id"` ChannelContextRevision pgtype.Int8 `json:"channel_context_revision"` } // Chat-session counterpart of GetLatestTaskRolloutMissing (MUL-5305): reports // whether the most recent terminal task on this chat session withheld its Codex // session because the rollout was missing. When true the next chat claim resumed // an older session (or none), so it must disclose the continuity gap. func (q *Queries) GetLatestChatTaskRolloutMissing(ctx context.Context, arg GetLatestChatTaskRolloutMissingParams) (bool, error) { row := q.db.QueryRow(ctx, getLatestChatTaskRolloutMissing, arg.ChatSessionID, arg.ChannelContextRevision) var session_rollout_missing bool err := row.Scan(&session_rollout_missing) return session_rollout_missing, err } const getLatestTaskRoleForIssueAndAgent = `-- name: GetLatestTaskRoleForIssueAndAgent :one SELECT is_leader_task, squad_id FROM agent_task_queue WHERE issue_id = $1 AND agent_id = $2 ORDER BY created_at DESC LIMIT 1 ` type GetLatestTaskRoleForIssueAndAgentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` } type GetLatestTaskRoleForIssueAndAgentRow struct { IsLeaderTask bool `json:"is_leader_task"` SquadID pgtype.UUID `json:"squad_id"` } // Returns the role markers from the agent's most recent task on this issue. // Used by the squad-leader self-trigger guard to tell apart leader tasks, // same-squad worker tasks, and generic agent tasks such as direct mentions or // thread-parent replies. func (q *Queries) GetLatestTaskRoleForIssueAndAgent(ctx context.Context, arg GetLatestTaskRoleForIssueAndAgentParams) (GetLatestTaskRoleForIssueAndAgentRow, error) { row := q.db.QueryRow(ctx, getLatestTaskRoleForIssueAndAgent, arg.IssueID, arg.AgentID) var i GetLatestTaskRoleForIssueAndAgentRow err := row.Scan(&i.IsLeaderTask, &i.SquadID) return i, err } const getLatestTaskRolloutMissing = `-- name: GetLatestTaskRolloutMissing :one SELECT COALESCE(session_rollout_missing, FALSE) FROM agent_task_queue WHERE agent_id = $1 AND issue_id = $2 AND COALESCE(context->>'type', '') <> 'triage' AND status IN ('completed', 'failed') AND started_at IS NOT NULL ORDER BY COALESCE(completed_at, started_at, dispatched_at, created_at) DESC LIMIT 1 ` type GetLatestTaskRolloutMissingParams struct { AgentID pgtype.UUID `json:"agent_id"` IssueID pgtype.UUID `json:"issue_id"` } // Reports whether the most recent terminal task for (agent_id, issue_id) // withheld its Codex session because the rollout was missing (MUL-5305). When // true, GetLastTaskSession fell back to an older session, so the next run must // disclose that the most recent turn's context could not be carried over. Any // later task that records a real session resets this to FALSE by being the new // most-recent row, so the disclosure fires once and then clears. // // Triage runs are excluded for the same reason GetLastTaskSession excludes them // (MUL-7189 §5.6), and the two must agree: this query only reports whether THAT // one fell back. A triage run is invisible to it, so a triage run reported here // would tell the first execution run after accept that the previous turn's // context could not be carried over — about a turn it was never entitled to. func (q *Queries) GetLatestTaskRolloutMissing(ctx context.Context, arg GetLatestTaskRolloutMissingParams) (bool, error) { row := q.db.QueryRow(ctx, getLatestTaskRolloutMissing, arg.AgentID, arg.IssueID) var session_rollout_missing bool err := row.Scan(&session_rollout_missing) return session_rollout_missing, err } const getWorkspaceAgentActivity30d = `-- name: GetWorkspaceAgentActivity30d :many SELECT atq.agent_id, DATE_TRUNC('day', atq.completed_at)::timestamptz AS bucket, COUNT(*)::int AS task_count, COUNT(*) FILTER (WHERE atq.status = 'failed')::int AS failed_count, COUNT(*) FILTER (WHERE atq.status = 'completed')::int AS completed_count, COUNT(*) FILTER (WHERE atq.status = 'cancelled')::int AS cancelled_count, COALESCE(SUM(EXTRACT(EPOCH FROM (atq.completed_at - atq.started_at)) * 1000) FILTER (WHERE atq.completed_at > atq.started_at), 0)::float8 AS duration_ms, COUNT(*) FILTER (WHERE atq.completed_at > atq.started_at)::int AS duration_count FROM agent_task_queue atq JOIN agent a ON a.id = atq.agent_id WHERE a.workspace_id = $1 AND atq.completed_at IS NOT NULL AND atq.completed_at > now() - INTERVAL '30 days' GROUP BY atq.agent_id, bucket ORDER BY atq.agent_id, bucket ` type GetWorkspaceAgentActivity30dRow struct { AgentID pgtype.UUID `json:"agent_id"` Bucket pgtype.Timestamptz `json:"bucket"` TaskCount int32 `json:"task_count"` FailedCount int32 `json:"failed_count"` CompletedCount int32 `json:"completed_count"` CancelledCount int32 `json:"cancelled_count"` DurationMs float64 `json:"duration_ms"` DurationCount int32 `json:"duration_count"` } // Returns per-agent daily activity buckets for the last 30 days. Single // workspace-wide read backs both surfaces: // - Agents list ACTIVITY column — uses only the trailing 7 buckets // - Agent detail "Last 30 days" panel — uses the full 30 // // 30 days contains 7 days, so one fetch + a client-side .slice(-7) wins // over fetching twice. Days with no completion produce no row; the // front-end zero-fills. // // Anchored on completed_at (not created_at) because the sparkline answers // "what did this agent produce?" not "what was queued at it?". A task that's // still in flight has no completed_at and contributes nothing here — that's // correct: in-flight tasks are surfaced via the live presence indicator, // not the historical trend. // Keep total activity separate from outcomes: cancelled runs belong in the // history, but success rate is completed / (completed + failed). func (q *Queries) GetWorkspaceAgentActivity30d(ctx context.Context, workspaceID pgtype.UUID) ([]GetWorkspaceAgentActivity30dRow, error) { rows, err := q.db.Query(ctx, getWorkspaceAgentActivity30d, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []GetWorkspaceAgentActivity30dRow{} for rows.Next() { var i GetWorkspaceAgentActivity30dRow if err := rows.Scan( &i.AgentID, &i.Bucket, &i.TaskCount, &i.FailedCount, &i.CompletedCount, &i.CancelledCount, &i.DurationMs, &i.DurationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getWorkspaceAgentRunCounts = `-- name: GetWorkspaceAgentRunCounts :many SELECT atq.agent_id, COUNT(*)::int AS run_count FROM agent_task_queue atq JOIN agent a ON a.id = atq.agent_id WHERE a.workspace_id = $1 AND atq.created_at > now() - INTERVAL '30 days' GROUP BY atq.agent_id ` type GetWorkspaceAgentRunCountsRow struct { AgentID pgtype.UUID `json:"agent_id"` RunCount int32 `json:"run_count"` } // Total task runs per agent over the trailing 30 days, used by the Agents // list RUNS column. 30-day window keeps the count meaningful (a long-dormant // agent shouldn't show "5,420 runs from 2 years ago") and keeps the scan // bounded as the workspace ages. func (q *Queries) GetWorkspaceAgentRunCounts(ctx context.Context, workspaceID pgtype.UUID) ([]GetWorkspaceAgentRunCountsRow, error) { rows, err := q.db.Query(ctx, getWorkspaceAgentRunCounts, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []GetWorkspaceAgentRunCountsRow{} for rows.Next() { var i GetWorkspaceAgentRunCountsRow if err := rows.Scan(&i.AgentID, &i.RunCount); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const hasActiveTaskForIssue = `-- name: HasActiveTaskForIssue :one SELECT count(*) > 0 AS has_active FROM agent_task_queue WHERE issue_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ` // Returns true if there is any queued, dispatched, waiting_local_directory, // or running task for the issue. func (q *Queries) HasActiveTaskForIssue(ctx context.Context, issueID pgtype.UUID) (bool, error) { row := q.db.QueryRow(ctx, hasActiveTaskForIssue, issueID) var has_active bool err := row.Scan(&has_active) return has_active, err } const hasActiveTaskForIssueAndAgent = `-- name: HasActiveTaskForIssueAndAgent :one SELECT count(*) > 0 AS has_active FROM agent_task_queue WHERE issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) ` type HasActiveTaskForIssueAndAgentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` } // MUL-4195: true when the (issue, agent) pair has any non-terminal task in a // state whose completion will run completion reconciliation — queued, // dispatched, running, waiting_local_directory, or the explicitly-marked // channel-media deferred state. Used by the comment enqueue // path: when a merge into a pre-claim task fails (the task is already // dispatched/running, or a mismatched pre-claim task exists), a fresh queued // INSERT would collide with idx_one_pending_task_per_issue_agent AND would risk // a duplicate run. Instead the caller relies on that active task's completion // reconcile to schedule the guaranteed follow-up, and only enqueues fresh when // NO active task exists. func (q *Queries) HasActiveTaskForIssueAndAgent(ctx context.Context, arg HasActiveTaskForIssueAndAgentParams) (bool, error) { row := q.db.QueryRow(ctx, hasActiveTaskForIssueAndAgent, arg.IssueID, arg.AgentID) var has_active bool err := row.Scan(&has_active) return has_active, err } const hasActiveTaskForIssueAndAgentInThread = `-- name: HasActiveTaskForIssueAndAgentInThread :one SELECT count(*) > 0 AS has_active FROM agent_task_queue WHERE issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($3::uuid) ` type HasActiveTaskForIssueAndAgentInThreadParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` ThreadCommentID pgtype.UUID `json:"thread_comment_id"` } // Active execution and pending work in this comment thread only. func (q *Queries) HasActiveTaskForIssueAndAgentInThread(ctx context.Context, arg HasActiveTaskForIssueAndAgentInThreadParams) (bool, error) { row := q.db.QueryRow(ctx, hasActiveTaskForIssueAndAgentInThread, arg.IssueID, arg.AgentID, arg.ThreadCommentID) var has_active bool err := row.Scan(&has_active) return has_active, err } const hasPendingTaskForIssue = `-- name: HasPendingTaskForIssue :one SELECT count(*) > 0 AS has_pending FROM agent_task_queue WHERE context->>'wakeup_id' IS NULL AND issue_id = $1 AND status IN ('queued', 'dispatched') ` // Returns true if there is a queued or dispatched (but not yet running) task for the issue. // Used by the coalescing queue: allow enqueue when a task is running (so // the agent picks up new comments on the next cycle) but skip if a pending // task already exists (natural dedup). func (q *Queries) HasPendingTaskForIssue(ctx context.Context, issueID pgtype.UUID) (bool, error) { row := q.db.QueryRow(ctx, hasPendingTaskForIssue, issueID) var has_pending bool err := row.Scan(&has_pending) return has_pending, err } const hasPendingTaskForIssueAndAgent = `-- name: HasPendingTaskForIssueAndAgent :one SELECT count(*) > 0 AS has_pending FROM agent_task_queue WHERE context->>'wakeup_id' IS NULL AND issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND ( COALESCE($3::text, '') = '' OR context->>'head_sha' = $3::text ) ` type HasPendingTaskForIssueAndAgentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` HeadSha pgtype.Text `json:"head_sha"` } // Returns true if a specific agent already has a queued or dispatched task // for the given issue, or the explicitly-marked deferred task whose channel // media must bind before the issue agent runs. Used by @mention trigger dedup. // // head_sha keys the dedup on the commit under review (TEN-356): when a caller // passes a non-empty head_sha, a pending task only dedups if it was stamped // with the SAME head_sha at enqueue time. If HEAD advanced since the pending // task's run began (its context head_sha differs, or predates the stamp and is // NULL), the dedup MISSES and a fresh review enqueues against the new HEAD. // When head_sha is empty/NULL (issue has no linked PR) the check falls back to // the pre-TEN-356 (issue_id, agent_id) key so non-PR issues keep coalescing. func (q *Queries) HasPendingTaskForIssueAndAgent(ctx context.Context, arg HasPendingTaskForIssueAndAgentParams) (bool, error) { row := q.db.QueryRow(ctx, hasPendingTaskForIssueAndAgent, arg.IssueID, arg.AgentID, arg.HeadSha) var has_pending bool err := row.Scan(&has_pending) return has_pending, err } const hasPendingTaskForIssueAndAgentExcludingTriggerComment = `-- name: HasPendingTaskForIssueAndAgentExcludingTriggerComment :one SELECT count(*) > 0 AS has_pending FROM agent_task_queue WHERE context->>'wakeup_id' IS NULL AND issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND trigger_comment_id IS DISTINCT FROM $3::uuid AND ( COALESCE($4::text, '') = '' OR context->>'head_sha' = $4::text ) ` type HasPendingTaskForIssueAndAgentExcludingTriggerCommentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` ExcludeTriggerCommentID pgtype.UUID `json:"exclude_trigger_comment_id"` HeadSha pgtype.Text `json:"head_sha"` } // Same as HasPendingTaskForIssueAndAgent, but ignores tasks triggered by the // current comment being edited. Edit preview needs this because save cancels // that comment's old queued/dispatched tasks before re-computing triggers. // Carries the same head_sha dedup key as HasPendingTaskForIssueAndAgent (TEN-356). func (q *Queries) HasPendingTaskForIssueAndAgentExcludingTriggerComment(ctx context.Context, arg HasPendingTaskForIssueAndAgentExcludingTriggerCommentParams) (bool, error) { row := q.db.QueryRow(ctx, hasPendingTaskForIssueAndAgentExcludingTriggerComment, arg.IssueID, arg.AgentID, arg.ExcludeTriggerCommentID, arg.HeadSha, ) var has_pending bool err := row.Scan(&has_pending) return has_pending, err } const hasPendingTaskForIssueAndAgentExcludingTriggerCommentInThread = `-- name: HasPendingTaskForIssueAndAgentExcludingTriggerCommentInThread :one SELECT count(*) > 0 AS has_pending FROM agent_task_queue WHERE context->>'wakeup_id' IS NULL AND issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND trigger_comment_id IS DISTINCT FROM $3::uuid AND ( COALESCE($4::text, '') = '' OR context->>'head_sha' = $4::text ) AND comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($5::uuid) ` type HasPendingTaskForIssueAndAgentExcludingTriggerCommentInThreadParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` ExcludeTriggerCommentID pgtype.UUID `json:"exclude_trigger_comment_id"` HeadSha pgtype.Text `json:"head_sha"` ThreadCommentID pgtype.UUID `json:"thread_comment_id"` } // Thread-scoped edit preview: ignore the comment whose old run save replaces. func (q *Queries) HasPendingTaskForIssueAndAgentExcludingTriggerCommentInThread(ctx context.Context, arg HasPendingTaskForIssueAndAgentExcludingTriggerCommentInThreadParams) (bool, error) { row := q.db.QueryRow(ctx, hasPendingTaskForIssueAndAgentExcludingTriggerCommentInThread, arg.IssueID, arg.AgentID, arg.ExcludeTriggerCommentID, arg.HeadSha, arg.ThreadCommentID, ) var has_pending bool err := row.Scan(&has_pending) return has_pending, err } const hasPendingTaskForIssueAndAgentInThread = `-- name: HasPendingTaskForIssueAndAgentInThread :one SELECT count(*) > 0 AS has_pending FROM agent_task_queue WHERE context->>'wakeup_id' IS NULL AND issue_id = $1 AND agent_id = $2 AND ( status IN ('queued', 'dispatched') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND ( COALESCE($3::text, '') = '' OR context->>'head_sha' = $3::text ) AND comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($4::uuid) ` type HasPendingTaskForIssueAndAgentInThreadParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` HeadSha pgtype.Text `json:"head_sha"` ThreadCommentID pgtype.UUID `json:"thread_comment_id"` } // Same pending/head rules as HasPendingTaskForIssueAndAgent, scoped to one // root comment and all descendants. NULL selects assignment-level work. func (q *Queries) HasPendingTaskForIssueAndAgentInThread(ctx context.Context, arg HasPendingTaskForIssueAndAgentInThreadParams) (bool, error) { row := q.db.QueryRow(ctx, hasPendingTaskForIssueAndAgentInThread, arg.IssueID, arg.AgentID, arg.HeadSha, arg.ThreadCommentID, ) var has_pending bool err := row.Scan(&has_pending) return has_pending, err } const hasRetryTaskForParent = `-- name: HasRetryTaskForParent :one SELECT count(*) > 0 FROM agent_task_queue WHERE parent_task_id = $1 AND status <> 'cancelled' ` // Defense-in-depth for the recovery path: a delegated failure with any retry // child is still intermediate and must not wake its coordinator. func (q *Queries) HasRetryTaskForParent(ctx context.Context, parentTaskID pgtype.UUID) (bool, error) { row := q.db.QueryRow(ctx, hasRetryTaskForParent, parentTaskID) var column_1 bool err := row.Scan(&column_1) return column_1, err } const hasTaskCoveringDelegatedFailureComment = `-- name: HasTaskCoveringDelegatedFailureComment :one SELECT count(*) > 0 AS covered FROM agent_task_queue WHERE issue_id = $1 AND agent_id = $2 AND ( $3::uuid = ANY(delivered_comment_ids) OR ( id IS DISTINCT FROM $4::uuid AND ( status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') OR (status = 'deferred' AND context->>'channel_issue_media_pending' = 'true') ) AND (trigger_comment_id = $3::uuid OR $3::uuid = ANY(coalesced_comment_ids)) ) ) ` type HasTaskCoveringDelegatedFailureCommentParams struct { IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` CommentID pgtype.UUID `json:"comment_id"` ExcludeTaskID pgtype.UUID `json:"exclude_task_id"` } // Durable idempotency check for a recovery comment. The completion reconciler // excludes its own just-completed task when replaying a planned-but-undelivered // signal. A planned comment is covered only while its task can still execute; // after a task becomes terminal, delivered_comment_ids is the sole durable // receipt. This prevents cancelled/failed pre-delivery tasks from swallowing // the recovery obligation while still avoiding a loop after real delivery. func (q *Queries) HasTaskCoveringDelegatedFailureComment(ctx context.Context, arg HasTaskCoveringDelegatedFailureCommentParams) (bool, error) { row := q.db.QueryRow(ctx, hasTaskCoveringDelegatedFailureComment, arg.IssueID, arg.AgentID, arg.CommentID, arg.ExcludeTaskID, ) var covered bool err := row.Scan(&covered) return covered, err } const hasTaskForIssue = `-- name: HasTaskForIssue :one SELECT EXISTS (SELECT 1 FROM agent_task_queue WHERE issue_id = $1) ` // Returns true if the issue has any task in any status. Webhook recovery // treats even a terminal task as proof that ownership moved downstream. func (q *Queries) HasTaskForIssue(ctx context.Context, issueID pgtype.UUID) (bool, error) { row := q.db.QueryRow(ctx, hasTaskForIssue, issueID) var exists bool err := row.Scan(&exists) return exists, err } const isTaskSessionRetired = `-- name: IsTaskSessionRetired :one SELECT EXISTS ( SELECT 1 FROM agent_task_queue r WHERE r.agent_id = $1 AND r.retired_session_id = $2::text ) AS retired ` type IsTaskSessionRetiredParams struct { AgentID pgtype.UUID `json:"agent_id"` SessionID string `json:"session_id"` } // Whether any run of this agent explicitly retired session_id. The // manual-retry claim path resumes from its exact source task instead of // GetLastTaskSession / the chat pointer, so it must consult the retired set // itself; without this a retry of an older, resume-safe failed run revives a // session a later run already proved undrivable (MUL-8188). Session ids are // opaque runtime-issued ids, so (agent, session) identifies the transcript // whether the runs were issue or chat tasks. func (q *Queries) IsTaskSessionRetired(ctx context.Context, arg IsTaskSessionRetiredParams) (bool, error) { row := q.db.QueryRow(ctx, isTaskSessionRetired, arg.AgentID, arg.SessionID) var retired bool err := row.Scan(&retired) return retired, err } const linkTaskToIssue = `-- name: LinkTaskToIssue :exec UPDATE agent_task_queue SET issue_id = $2 WHERE id = $1 AND issue_id IS NULL AND lock_task_owner_rows(NULL, $2, NULL) ` type LinkTaskToIssueParams struct { ID pgtype.UUID `json:"id"` IssueID pgtype.UUID `json:"issue_id"` } // Fenced against workspace teardown: lock_task_owner_rows (migration 284) // locks the owners' workspace rows in the writer's own transaction and returns // false once they are gone, so this statement writes no row instead of stranding // a task in a workspace that has just been deleted (MUL-5999). // Attaches the issue a quick-create task produced back to the task row, once // the agent has finished and the issue exists. Guarded by `issue_id IS NULL` // so this never overwrites an issue id that was set at task creation (only // quick-create tasks land here unset). Fixes the activity row staying on // "Creating issue" forever after completion. func (q *Queries) LinkTaskToIssue(ctx context.Context, arg LinkTaskToIssueParams) error { _, err := q.db.Exec(ctx, linkTaskToIssue, arg.ID, arg.IssueID) return err } const listActiveAgentsByRuntime = `-- name: ListActiveAgentsByRuntime :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE runtime_id = $1 AND archived_at IS NULL AND kind = 'user' ORDER BY name ASC ` // Returns every non-archived agent bound to a runtime. Backs the cascade // delete dialog: when DELETE /api/runtimes/:id refuses with // runtime_has_active_agents, the response carries this list so the front-end // can render exactly the agents that will be unbound if the user confirms, // and so the cascade endpoint's expected_active_agent_ids check has a stable // snapshot to compare against. Ordered by name for a deterministic display. func (q *Queries) ListActiveAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listActiveAgentsByRuntime, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listActiveAgentsByRuntimeForUpdate = `-- name: ListActiveAgentsByRuntimeForUpdate :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE runtime_id = $1 AND archived_at IS NULL AND kind = 'user' ORDER BY name ASC FOR UPDATE ` // FOR UPDATE variant used inside the cascade-delete transaction. Locks // each currently-active agent row so a concurrent archive/move of one // of those rows blocks until our transaction commits. Pair with // LockAgentRuntime, which holds the runtime row exclusively to also // block FK-validated INSERTs / runtime_id updates that would otherwise // add a new agent to the runtime mid-cascade. Together they guarantee // that the set we compared against expected_active_agent_ids is stable. func (q *Queries) ListActiveAgentsByRuntimeForUpdate(ctx context.Context, runtimeID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listActiveAgentsByRuntimeForUpdate, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listActiveTasksByIssue = `-- name: ListActiveTasksByIssue :many SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE issue_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ORDER BY created_at DESC ` // Backs the issue-detail "agent live" banner. Includes 'queued' so the // banner shows up the moment a task is enqueued — not only after a runtime // claims it. The queued window can be long when the runtime is offline or // busy on a prior task, and a silent UI during that window looks like the // platform never received the trigger. func (q *Queries) ListActiveTasksByIssue(ctx context.Context, issueID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listActiveTasksByIssue, issueID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listActiveTasksByIssueFamily = `-- name: ListActiveTasksByIssueFamily :many SELECT atq.id AS task_id, atq.agent_id, atq.issue_id, atq.status, atq.created_at, atq.started_at, w.issue_prefix, i.number AS issue_number, i.title AS issue_title FROM agent_task_queue atq JOIN issue i ON i.id = atq.issue_id JOIN workspace w ON w.id = i.workspace_id WHERE i.workspace_id = $1 AND (i.id = $2::uuid OR i.parent_issue_id = $2::uuid) AND atq.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') ORDER BY CASE atq.status WHEN 'running' THEN 0 WHEN 'dispatched' THEN 1 WHEN 'waiting_local_directory' THEN 2 ELSE 3 END, atq.created_at DESC LIMIT $3 ` type ListActiveTasksByIssueFamilyParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` RootIssueID pgtype.UUID `json:"root_issue_id"` RowLimit int32 `json:"row_limit"` } type ListActiveTasksByIssueFamilyRow struct { TaskID pgtype.UUID `json:"task_id"` AgentID pgtype.UUID `json:"agent_id"` IssueID pgtype.UUID `json:"issue_id"` Status string `json:"status"` CreatedAt pgtype.Timestamptz `json:"created_at"` StartedAt pgtype.Timestamptz `json:"started_at"` IssuePrefix string `json:"issue_prefix"` IssueNumber int32 `json:"issue_number"` IssueTitle string `json:"issue_title"` } // Cross-issue coordination read for parallel sub-issue work (#7768). Given a // family root — the target issue's parent, or the target itself when it has // none — return every in-flight task on the root and on all of its children, // so a run can see who else is already working in the family before it starts // overlapping work. Advisory only: nothing here gates, queues, or serialises // anything. // // Same active set as ListActiveTasksByIssue, including 'queued': a queued // sibling cannot answer you yet, but it is about to touch the same code, which // is exactly what the caller is trying to find out. The status column tells the // two apart. // // Issue identity is joined in because the caller renders runs from several // issues in one list and cannot label a row from the task alone. agent_id is // here for the same reason: this read spans agents, and which one is on a // sibling is the answer, not a detail. // // Columns are named rather than embedded. This is the coordination question, // not the execution log: result and context are JSONB blobs, and work_dir / // trigger_summary / the attribution ids are all execution-log fields that a // caller asking "who else is here?" never reads. Selecting them would make // Postgres detoast and ship roughly 5x the bytes per row for nothing. // // Ordered running-first so the truncation the LIMIT may impose drops the least // interesting rows, and bounded because a parent with hundreds of children must // not turn one coordination read into an unbounded scan. func (q *Queries) ListActiveTasksByIssueFamily(ctx context.Context, arg ListActiveTasksByIssueFamilyParams) ([]ListActiveTasksByIssueFamilyRow, error) { rows, err := q.db.Query(ctx, listActiveTasksByIssueFamily, arg.WorkspaceID, arg.RootIssueID, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []ListActiveTasksByIssueFamilyRow{} for rows.Next() { var i ListActiveTasksByIssueFamilyRow if err := rows.Scan( &i.TaskID, &i.AgentID, &i.IssueID, &i.Status, &i.CreatedAt, &i.StartedAt, &i.IssuePrefix, &i.IssueNumber, &i.IssueTitle, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAgentTasks = `-- name: ListAgentTasks :many SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE agent_id = $1 -- Apply visibility before LIMIT so hidden fallbacks cannot end a page early. -- Keep this predicate in sync with handler.visibleTaskHistory. AND NOT (escalation_for_task_id IS NOT NULL AND started_at IS NULL AND status IN ('deferred', 'cancelled')) AND (created_at, id) < ($2::timestamptz, $3::uuid) ORDER BY created_at DESC, id DESC LIMIT $4 ` type ListAgentTasksParams struct { AgentID pgtype.UUID `json:"agent_id"` BeforeCreatedAt pgtype.Timestamptz `json:"before_created_at"` BeforeID pgtype.UUID `json:"before_id"` PageLimit int32 `json:"page_limit"` } func (q *Queries) ListAgentTasks(ctx context.Context, arg ListAgentTasksParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listAgentTasks, arg.AgentID, arg.BeforeCreatedAt, arg.BeforeID, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAgents = `-- name: ListAgents :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE workspace_id = $1 AND archived_at IS NULL AND kind = 'user' ORDER BY created_at ASC ` func (q *Queries) ListAgents(ctx context.Context, workspaceID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listAgents, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAllAgents = `-- name: ListAllAgents :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE workspace_id = $1 AND kind = 'user' ORDER BY created_at ASC ` func (q *Queries) ListAllAgents(ctx context.Context, workspaceID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listAllAgents, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAllAgentsAnyKind = `-- name: ListAllAgentsAnyKind :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC ` // Every agent row in the workspace, INCLUDING `kind = 'system'` execution // carriers (agent builder sessions) that ListAllAgents deliberately hides. // // Only for surfaces that aggregate over raw `agent_task_queue` / task_usage // rows, which carry no kind filter of their own: a system carrier runs real // tasks and books real usage, so a per-agent rollup returns it whether or not // any list endpoint will ever name it. Those surfaces need the full population // to decide what to hide (MUL-5409). Do NOT use this to build a user-facing // agent list — `kind = 'system'` must stay out of every picker and assignee // surface. func (q *Queries) ListAllAgentsAnyKind(ctx context.Context, workspaceID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listAllAgentsAnyKind, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChatFinalizeDeferredExpired = `-- name: ListChatFinalizeDeferredExpired :many SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE chat_finalize_deferred_at IS NOT NULL AND chat_finalize_deferred_at < now() - make_interval(secs => $1::double precision) ORDER BY chat_finalize_deferred_at LIMIT $2::int ` type ListChatFinalizeDeferredExpiredParams struct { GraceSecs float64 `json:"grace_secs"` MaxPerTick int32 `json:"max_per_tick"` } // Deferred chat finalizations whose grace period elapsed without a daemon // ack (dead or partitioned daemon). Batch-capped like the other sweeper // queries so one tick can't monopolise the DB. func (q *Queries) ListChatFinalizeDeferredExpired(ctx context.Context, arg ListChatFinalizeDeferredExpiredParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listChatFinalizeDeferredExpired, arg.GraceSecs, arg.MaxPerTick) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNonOwnerAgentsByRuntime = `-- name: ListNonOwnerAgentsByRuntime :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE runtime_id = $1 AND owner_id IS DISTINCT FROM $2 AND NOT COALESCE(owner_id = ANY($3::uuid[]), false) ORDER BY name ASC ` type ListNonOwnerAgentsByRuntimeParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeOwnerID pgtype.UUID `json:"runtime_owner_id"` SharedWith []pgtype.UUID `json:"shared_with"` } // Returns bound agents whose owners lose access under the desired shared_with // list, including hidden Builder execution carriers. Runtime-owner agents keep // their binding. func (q *Queries) ListNonOwnerAgentsByRuntime(ctx context.Context, arg ListNonOwnerAgentsByRuntimeParams) ([]Agent, error) { rows, err := q.db.Query(ctx, listNonOwnerAgentsByRuntime, arg.RuntimeID, arg.RuntimeOwnerID, arg.SharedWith) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNonOwnerAgentsByRuntimeForUpdate = `-- name: ListNonOwnerAgentsByRuntimeForUpdate :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE runtime_id = $1 AND owner_id IS DISTINCT FROM $2 AND NOT COALESCE(owner_id = ANY($3::uuid[]), false) ORDER BY id FOR UPDATE NOWAIT ` type ListNonOwnerAgentsByRuntimeForUpdateParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeOwnerID pgtype.UUID `json:"runtime_owner_id"` SharedWith []pgtype.UUID `json:"shared_with"` } // Runtime access-change locking serializes new bindings. NOWAIT avoids waiting // behind a writer that already holds an agent and needs the runtime row. func (q *Queries) ListNonOwnerAgentsByRuntimeForUpdate(ctx context.Context, arg ListNonOwnerAgentsByRuntimeForUpdateParams) ([]Agent, error) { rows, err := q.db.Query(ctx, listNonOwnerAgentsByRuntimeForUpdate, arg.RuntimeID, arg.RuntimeOwnerID, arg.SharedWith) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPendingDelegatedFailureRecoveries = `-- name: ListPendingDelegatedFailureRecoveries :many SELECT recovery.id, recovery.issue_id, recovery.author_type, recovery.author_id, recovery.content, recovery.type, recovery.created_at, recovery.updated_at, recovery.parent_id, recovery.workspace_id, recovery.resolved_at, recovery.resolved_by_type, recovery.resolved_by_id, recovery.source_task_id, recovery.quick_action_id, recovery.via_plugin_id, recovery.revision, recovery.recovery_settled_at, recovery.deleted_at, recovery.suppressed_agent_ids FROM comment recovery JOIN agent_task_queue failed ON failed.id = recovery.source_task_id JOIN agent_task_queue source ON source.id = failed.delegated_from_task_id JOIN issue source_issue ON source_issue.id = source.issue_id JOIN agent source_agent ON source_agent.id = source.agent_id LEFT JOIN issue_status source_status ON source_status.workspace_id = source_issue.workspace_id AND source_status.key = source_issue.status WHERE recovery.author_type = 'system' AND recovery.type = 'progress_update' AND recovery.source_task_id IS NOT NULL AND recovery.recovery_settled_at IS NULL -- A deleted recovery signal is withdrawn, even when replies keep its row. AND recovery.deleted_at IS NULL AND recovery.issue_id = source_issue.id AND recovery.workspace_id = source_issue.workspace_id AND failed.status = 'failed' AND failed.delegated_from_task_id IS NOT NULL AND failed.autopilot_run_id IS NULL AND failed.trigger_evidence_kind IS DISTINCT FROM 'delegated_failure' AND source.autopilot_run_id IS NULL AND source.issue_id IS NOT NULL AND source.agent_id <> failed.agent_id -- Match canDispatchDelegatedFailureRecovery: lifecycle permits recovery only -- for open work; parking belongs exclusively to the fixed Backlog status. -- Built-ins resolve without catalog rows; unknown custom states stay pending. AND source_issue.status <> 'backlog' -- A coordinator waiting in Triage is the entry's proposed owner, not its -- owner, so a worker failure must not wake it (MUL-7189 §2.3). Triage is not -- a status, so it is a predicate of its own rather than a CASE arm. AND source_issue.triage_state IS NULL AND CASE WHEN source_issue.status IN ('backlog', 'todo') THEN 'unstarted' WHEN source_issue.status IN ('in_progress', 'in_review', 'blocked') THEN 'started' WHEN source_issue.status = 'done' THEN 'done' WHEN source_issue.status = 'cancelled' THEN 'closed' ELSE source_status.category END IN ('unstarted', 'started') AND source_agent.archived_at IS NULL AND source_agent.runtime_id IS NOT NULL AND source_agent.workspace_id = source_issue.workspace_id AND NOT EXISTS ( SELECT 1 FROM agent_task_queue retry WHERE retry.parent_task_id = failed.id AND retry.status <> 'cancelled' ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue covering WHERE covering.issue_id = source_issue.id AND covering.agent_id = source.agent_id AND ( recovery.id = ANY(covering.delivered_comment_ids) OR ( ( covering.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') OR ( covering.status = 'deferred' AND covering.context->>'channel_issue_media_pending' = 'true' ) ) AND ( covering.trigger_comment_id = recovery.id OR recovery.id = ANY(covering.coalesced_comment_ids) ) ) ) ) ORDER BY recovery.created_at ASC, recovery.id ASC LIMIT $1 ` // Durable outbox scan for platform recovery comments that are not yet owned by // an executable task and have no terminal delivery receipt. Starting from the // explicit recovery signal avoids retroactively waking unrelated historical // delegated failures. A bounded runtime sweeper replays these comments after a // transient dispatch error or process restart. // // recovery_settled_at is what keeps this scan from growing with history. Every // other exclusion below is reversible — an issue can leave 'done', a cancelled // retry can be superseded — so none of them can be frozen into the index // predicate. A delivery receipt on a terminal task cannot be taken back, so // that one condition is recorded as durable state instead of being re-proven // through four joins and two NOT EXISTS subqueries on every tick. The predicate // of idx_comment_delegated_failure_unsettled matches the first four conditions, // narrowing the scan to unsettled signals. Reversible eligibility must still // be checked before LIMIT so paused signals cannot starve executable ones. func (q *Queries) ListPendingDelegatedFailureRecoveries(ctx context.Context, maxPerTick int32) ([]Comment, error) { rows, err := q.db.Query(ctx, listPendingDelegatedFailureRecoveries, maxPerTick) if err != nil { return nil, err } defer rows.Close() items := []Comment{} for rows.Next() { var i Comment if err := rows.Scan( &i.ID, &i.IssueID, &i.AuthorType, &i.AuthorID, &i.Content, &i.Type, &i.CreatedAt, &i.UpdatedAt, &i.ParentID, &i.WorkspaceID, &i.ResolvedAt, &i.ResolvedByType, &i.ResolvedByID, &i.SourceTaskID, &i.QuickActionID, &i.ViaPluginID, &i.Revision, &i.RecoverySettledAt, &i.DeletedAt, &i.SuppressedAgentIds, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPendingTasksByRuntime = `-- name: ListPendingTasksByRuntime :many SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE runtime_id = $1 AND status IN ('queued', 'dispatched') ORDER BY priority DESC, created_at ASC ` func (q *Queries) ListPendingTasksByRuntime(ctx context.Context, runtimeID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listPendingTasksByRuntime, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listQueuedClaimCandidatesByRuntime = `-- name: ListQueuedClaimCandidatesByRuntime :many SELECT atq.id, atq.agent_id, atq.issue_id, atq.status, atq.priority, atq.dispatched_at, atq.started_at, atq.completed_at, atq.result, atq.error, atq.created_at, atq.context, atq.runtime_id, atq.session_id, atq.work_dir, atq.trigger_comment_id, atq.chat_session_id, atq.autopilot_run_id, atq.attempt, atq.max_attempts, atq.parent_task_id, atq.failure_reason, atq.trigger_summary, atq.force_fresh_session, atq.is_leader_task, atq.wait_reason, atq.initiator_user_id, atq.handoff_note, atq.prepare_lease_expires_at, atq.squad_id, atq.runtime_mcp_overlay, atq.escalation_for_task_id, atq.fire_at, atq.originator_user_id, atq.runtime_connected_apps, atq.coalesced_comment_ids, atq.delivered_comment_ids, atq.chat_input_task_id, atq.chat_finalize_deferred_at, atq.originator_source, atq.delegated_from_task_id, atq.retry_of_task_id, atq.rerun_of_task_id, atq.rule_version_id, atq.trigger_evidence_kind, atq.trigger_evidence_ref_id, atq.accountable_user_id, atq.session_rollout_missing, atq.retired_session_id, atq.quick_actions_disabled, atq.regenerate_quick_actions_for, atq.branch_name, atq.durable_work_dir, atq.channel_context_revision, atq.comment_thread_id, atq.cancelled_by_type, atq.cancelled_by_id, atq.cancelled_by_name, atq.issue_snapshot, atq.workflow_step FROM agent_task_queue atq WHERE atq.runtime_id = $1 AND atq.status = 'queued' AND (atq.context->>'wakeup_id' IS NULL OR EXISTS (SELECT 1 FROM issue_wakeup w WHERE w.id=(atq.context->>'wakeup_id')::uuid AND w.disabled_at IS NULL AND w.revision=(atq.context->>'wakeup_revision')::bigint)) AND EXISTS ( -- Keep this authorization fence in sync with ClaimAgentTask. SELECT 1 FROM agent a JOIN agent_runtime r ON r.id = atq.runtime_id WHERE a.id = atq.agent_id AND a.runtime_id = atq.runtime_id AND ( r.visibility = 'public' OR r.visibility = 'private' ) ) ORDER BY atq.priority DESC, atq.created_at ASC ` // Returns rows the runtime is authorized to attempt to claim. Status is restricted to // 'queued' (in contrast to ListPendingTasksByRuntime which also includes // 'dispatched') because dispatched rows are by definition already owned // and cannot be re-claimed — including them in the candidate list pads // the result with rows that always lose the per-(issue, agent) race in // ClaimAgentTask, wasting CPU and a SELECT every poll cycle when the // runtime is busy on a long-running task. Backed by the partial index // idx_agent_task_queue_claim_candidates so the warm path is cheap. func (q *Queries) ListQueuedClaimCandidatesByRuntime(ctx context.Context, runtimeID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listQueuedClaimCandidatesByRuntime, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listQueuedClaimCandidatesByRuntimes = `-- name: ListQueuedClaimCandidatesByRuntimes :many SELECT atq.id, atq.agent_id, atq.issue_id, atq.status, atq.priority, atq.dispatched_at, atq.started_at, atq.completed_at, atq.result, atq.error, atq.created_at, atq.context, atq.runtime_id, atq.session_id, atq.work_dir, atq.trigger_comment_id, atq.chat_session_id, atq.autopilot_run_id, atq.attempt, atq.max_attempts, atq.parent_task_id, atq.failure_reason, atq.trigger_summary, atq.force_fresh_session, atq.is_leader_task, atq.wait_reason, atq.initiator_user_id, atq.handoff_note, atq.prepare_lease_expires_at, atq.squad_id, atq.runtime_mcp_overlay, atq.escalation_for_task_id, atq.fire_at, atq.originator_user_id, atq.runtime_connected_apps, atq.coalesced_comment_ids, atq.delivered_comment_ids, atq.chat_input_task_id, atq.chat_finalize_deferred_at, atq.originator_source, atq.delegated_from_task_id, atq.retry_of_task_id, atq.rerun_of_task_id, atq.rule_version_id, atq.trigger_evidence_kind, atq.trigger_evidence_ref_id, atq.accountable_user_id, atq.session_rollout_missing, atq.retired_session_id, atq.quick_actions_disabled, atq.regenerate_quick_actions_for, atq.branch_name, atq.durable_work_dir, atq.channel_context_revision, atq.comment_thread_id, atq.cancelled_by_type, atq.cancelled_by_id, atq.cancelled_by_name, atq.issue_snapshot, atq.workflow_step FROM agent_task_queue atq WHERE atq.runtime_id = ANY($1::uuid[]) AND atq.status = 'queued' AND (atq.context->>'wakeup_id' IS NULL OR EXISTS (SELECT 1 FROM issue_wakeup w WHERE w.id=(atq.context->>'wakeup_id')::uuid AND w.disabled_at IS NULL AND w.revision=(atq.context->>'wakeup_revision')::bigint)) AND EXISTS ( -- Keep this authorization fence in sync with ClaimAgentTask. SELECT 1 FROM agent a JOIN agent_runtime r ON r.id = atq.runtime_id WHERE a.id = atq.agent_id AND a.runtime_id = atq.runtime_id AND ( r.visibility = 'public' OR r.visibility = 'private' ) ) ORDER BY atq.priority DESC, atq.created_at ASC ` // Batch variant of ListQueuedClaimCandidatesByRuntime (MUL-4257): returns // queued claim candidates across every runtime_id in the input set in ONE round // trip, so a daemon can list candidates for all of its runtimes with a single // query instead of one per runtime. Ordering matches the singular query // (priority, then FIFO) so the batch claim loop keeps the same fairness. The // runtime_id filter is served by the partial index // idx_agent_task_queue_claim_candidates; the cross-runtime ORDER BY still needs // a sort step (each runtime's slice is index-ordered, but merging several // runtimes' rows into one priority/FIFO order is not). The per-machine // candidate set is small, so this is cheap in practice. func (q *Queries) ListQueuedClaimCandidatesByRuntimes(ctx context.Context, runtimeIds []pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listQueuedClaimCandidatesByRuntimes, runtimeIds) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTasksByIssue = `-- name: ListTasksByIssue :many SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE issue_id = $1 ORDER BY created_at DESC ` func (q *Queries) ListTasksByIssue(ctx context.Context, issueID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listTasksByIssue, issueID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listUserAgentIDsByRuntime = `-- name: ListUserAgentIDsByRuntime :many SELECT id FROM agent WHERE runtime_id = $1 AND kind = 'user' ORDER BY id ` // Non-locking companion to ListUserAgentsByRuntimeForUpdate, for callers that // must reason about retention GC without taking the teardown's locks. // // Archived rows are included deliberately, and that is the whole point: an // archived agent can still own a non-terminal task, and gcRuntime counts those // before it will delete a runtime. A read that filtered them would report a // runtime as reclaimable when the sweeper is going to skip it. func (q *Queries) ListUserAgentIDsByRuntime(ctx context.Context, runtimeID pgtype.UUID) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listUserAgentIDsByRuntime, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listUserAgentsByRuntimeForUpdate = `-- name: ListUserAgentsByRuntimeForUpdate :many SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE runtime_id = $1 AND kind = 'user' ORDER BY id FOR UPDATE ` // Locks active AND archived user agents before a runtime teardown. Locking only // the active snapshot leaves a restore race: an archived row can become active // after confirmation and then be silently unbound. Runtime/profile delete // locks the parent runtime first (blocking new FK references), then calls this // in stable ID order (blocking restore/archive/move on existing rows). func (q *Queries) ListUserAgentsByRuntimeForUpdate(ctx context.Context, runtimeID pgtype.UUID) ([]Agent, error) { rows, err := q.db.Query(ctx, listUserAgentsByRuntimeForUpdate, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []Agent{} for rows.Next() { var i Agent if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceAgentTaskSnapshot = `-- name: ListWorkspaceAgentTaskSnapshot :many SELECT atq.id, atq.agent_id, atq.issue_id, atq.status, atq.priority, atq.dispatched_at, atq.started_at, atq.completed_at, atq.result, atq.error, atq.created_at, atq.context, atq.runtime_id, atq.session_id, atq.work_dir, atq.trigger_comment_id, atq.chat_session_id, atq.autopilot_run_id, atq.attempt, atq.max_attempts, atq.parent_task_id, atq.failure_reason, atq.trigger_summary, atq.force_fresh_session, atq.is_leader_task, atq.wait_reason, atq.initiator_user_id, atq.handoff_note, atq.prepare_lease_expires_at, atq.squad_id, atq.runtime_mcp_overlay, atq.escalation_for_task_id, atq.fire_at, atq.originator_user_id, atq.runtime_connected_apps, atq.coalesced_comment_ids, atq.delivered_comment_ids, atq.chat_input_task_id, atq.chat_finalize_deferred_at, atq.originator_source, atq.delegated_from_task_id, atq.retry_of_task_id, atq.rerun_of_task_id, atq.rule_version_id, atq.trigger_evidence_kind, atq.trigger_evidence_ref_id, atq.accountable_user_id, atq.session_rollout_missing, atq.retired_session_id, atq.quick_actions_disabled, atq.regenerate_quick_actions_for, atq.branch_name, atq.durable_work_dir, atq.channel_context_revision, atq.comment_thread_id, atq.cancelled_by_type, atq.cancelled_by_id, atq.cancelled_by_name, atq.issue_snapshot, atq.workflow_step FROM agent_task_queue atq JOIN agent a ON a.id = atq.agent_id WHERE a.workspace_id = $1 AND (atq.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory') OR (atq.status='deferred' AND atq.context->>'wakeup_id' IS NOT NULL)) UNION ALL SELECT latest.id, latest.agent_id, latest.issue_id, latest.status, latest.priority, latest.dispatched_at, latest.started_at, latest.completed_at, latest.result, latest.error, latest.created_at, latest.context, latest.runtime_id, latest.session_id, latest.work_dir, latest.trigger_comment_id, latest.chat_session_id, latest.autopilot_run_id, latest.attempt, latest.max_attempts, latest.parent_task_id, latest.failure_reason, latest.trigger_summary, latest.force_fresh_session, latest.is_leader_task, latest.wait_reason, latest.initiator_user_id, latest.handoff_note, latest.prepare_lease_expires_at, latest.squad_id, latest.runtime_mcp_overlay, latest.escalation_for_task_id, latest.fire_at, latest.originator_user_id, latest.runtime_connected_apps, latest.coalesced_comment_ids, latest.delivered_comment_ids, latest.chat_input_task_id, latest.chat_finalize_deferred_at, latest.originator_source, latest.delegated_from_task_id, latest.retry_of_task_id, latest.rerun_of_task_id, latest.rule_version_id, latest.trigger_evidence_kind, latest.trigger_evidence_ref_id, latest.accountable_user_id, latest.session_rollout_missing, latest.retired_session_id, latest.quick_actions_disabled, latest.regenerate_quick_actions_for, latest.branch_name, latest.durable_work_dir, latest.channel_context_revision, latest.comment_thread_id, latest.cancelled_by_type, latest.cancelled_by_id, latest.cancelled_by_name, latest.issue_snapshot, latest.workflow_step FROM agent a JOIN LATERAL ( SELECT atq.id, atq.agent_id, atq.issue_id, atq.status, atq.priority, atq.dispatched_at, atq.started_at, atq.completed_at, atq.result, atq.error, atq.created_at, atq.context, atq.runtime_id, atq.session_id, atq.work_dir, atq.trigger_comment_id, atq.chat_session_id, atq.autopilot_run_id, atq.attempt, atq.max_attempts, atq.parent_task_id, atq.failure_reason, atq.trigger_summary, atq.force_fresh_session, atq.is_leader_task, atq.wait_reason, atq.initiator_user_id, atq.handoff_note, atq.prepare_lease_expires_at, atq.squad_id, atq.runtime_mcp_overlay, atq.escalation_for_task_id, atq.fire_at, atq.originator_user_id, atq.runtime_connected_apps, atq.coalesced_comment_ids, atq.delivered_comment_ids, atq.chat_input_task_id, atq.chat_finalize_deferred_at, atq.originator_source, atq.delegated_from_task_id, atq.retry_of_task_id, atq.rerun_of_task_id, atq.rule_version_id, atq.trigger_evidence_kind, atq.trigger_evidence_ref_id, atq.accountable_user_id, atq.session_rollout_missing, atq.retired_session_id, atq.quick_actions_disabled, atq.regenerate_quick_actions_for, atq.branch_name, atq.durable_work_dir, atq.channel_context_revision, atq.comment_thread_id, atq.cancelled_by_type, atq.cancelled_by_id, atq.cancelled_by_name, atq.issue_snapshot, atq.workflow_step FROM agent_task_queue atq WHERE atq.agent_id = a.id AND atq.status IN ('completed', 'failed') ORDER BY atq.completed_at DESC NULLS LAST, atq.created_at DESC, atq.id DESC LIMIT 1 ) latest ON TRUE WHERE a.workspace_id = $1 ` // Returns the tasks the front-end reads off one workspace-wide snapshot: // - All active tasks (queued / dispatched / running / waiting_local_directory) // — the current workload: working signal + counts (see derive-presence.ts). // - Each agent's most recent OUTCOME task (completed / failed) — NOT part of // presence since #1823; it is the "last activity" line the Squad hover card // renders (agent-live-peek-card.tsx). Kept in this response because shipped // desktop builds read it from here; see MUL-5436 for the plan to move it to // a dedicated lazy endpoint. // // Cancelled tasks are excluded from the outcome half on purpose: cancel is a // procedural signal ("attempt aborted"), not an outcome. It tells us nothing // about whether the agent works, so it must NOT be allowed to mask a prior // failure. Concretely: if an agent fails and then the user cancels the queued // retry (or the parent issue closes and cascades cancels), the failure stays // the agent's last real outcome. // // The outcome half is a per-agent LATERAL Top-1 rather than a workspace-wide // DISTINCT ON: with idx_agent_task_queue_agent_terminal_latest (migration 233) // each agent costs one ordered index scan + LIMIT 1, so the cost no longer // grows with how much terminal history the workspace has accumulated. The // (created_at, id) tie-break makes the pick deterministic when completed_at // ties or is NULL — plain completed_at DESC returned an arbitrary row there. // Deferred wakeup retries also remain visible as queued work. // // Both halves JOIN / scan agent because agent_task_queue has no workspace_id. func (q *Queries) ListWorkspaceAgentTaskSnapshot(ctx context.Context, workspaceID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, listWorkspaceAgentTaskSnapshot, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceWorkingAgents = `-- name: ListWorkspaceWorkingAgents :many SELECT a.id, a.name, a.avatar_url, COUNT(*)::int AS running_task_count, COALESCE( ARRAY_AGG(DISTINCT atq.issue_id ORDER BY atq.issue_id) FILTER (WHERE atq.issue_id IS NOT NULL), ARRAY[]::uuid[] )::uuid[] AS issue_ids FROM agent a JOIN agent_task_queue atq ON atq.agent_id = a.id WHERE a.workspace_id = $1 AND a.kind = 'user' AND a.archived_at IS NULL AND atq.status = 'running' AND ( $2::text = '' OR ($2::text = 'chat' AND atq.chat_session_id IS NOT NULL) OR ( $2::text = 'autopilot' AND atq.chat_session_id IS NULL AND atq.autopilot_run_id IS NOT NULL ) OR ( $2::text = 'issue' AND atq.chat_session_id IS NULL AND atq.autopilot_run_id IS NULL AND atq.issue_id IS NOT NULL ) ) AND ( $3::text = '' OR EXISTS ( SELECT 1 FROM issue i WHERE i.id = atq.issue_id AND i.workspace_id = a.workspace_id AND ( ( $3::text IN ('assigned', 'any') AND i.assignee_type = 'member' AND i.assignee_id = $4::uuid ) OR ( $3::text IN ('created', 'any') AND i.creator_type = 'member' AND i.creator_id = $4::uuid ) OR ( $3::text IN ('involved', 'any') AND ( ( i.assignee_type = 'agent' AND EXISTS ( SELECT 1 FROM agent owned_agent WHERE owned_agent.id = i.assignee_id AND owned_agent.workspace_id = a.workspace_id AND owned_agent.owner_id = $4::uuid ) ) OR ( i.assignee_type = 'squad' AND EXISTS ( SELECT 1 FROM squad s WHERE s.id = i.assignee_id AND s.workspace_id = a.workspace_id AND ( EXISTS ( SELECT 1 FROM squad_member sm WHERE sm.squad_id = s.id AND sm.member_type = 'member' AND sm.member_id = $4::uuid ) OR EXISTS ( SELECT 1 FROM agent leader WHERE leader.id = s.leader_id AND leader.workspace_id = a.workspace_id AND leader.owner_id = $4::uuid ) OR EXISTS ( SELECT 1 FROM squad_member sm JOIN agent owned_member ON owned_member.id = sm.member_id WHERE sm.squad_id = s.id AND sm.member_type = 'agent' AND owned_member.workspace_id = a.workspace_id AND owned_member.owner_id = $4::uuid ) ) ) ) ) ) ) ) ) AND ( $5::uuid IS NULL OR EXISTS ( SELECT 1 FROM issue child WHERE child.id = atq.issue_id AND child.workspace_id = a.workspace_id AND child.parent_issue_id = $5::uuid ) ) GROUP BY a.id, a.name, a.avatar_url, a.created_at ORDER BY a.created_at ASC ` type ListWorkspaceWorkingAgentsParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` WorkType string `json:"work_type"` MineRelation string `json:"mine_relation"` MemberID pgtype.UUID `json:"member_id"` ParentIssueID pgtype.UUID `json:"parent_issue_id"` } type ListWorkspaceWorkingAgentsRow struct { ID pgtype.UUID `json:"id"` Name string `json:"name"` AvatarUrl pgtype.Text `json:"avatar_url"` RunningTaskCount int32 `json:"running_task_count"` IssueIds []pgtype.UUID `json:"issue_ids"` } // Workspace-level source for consumers that show currently working agents. // One row per visible, user-authored agent with at least one task that has // actually started running. work_type is optional (empty = every source); // source-specific reads use the same precedence as computeTaskKind: // chat > autopilot > issue. "issue" intentionally groups direct and // comment-triggered issue work. Quick-create work is present only in the // unfiltered projection because it has no source FK yet. mine_relation is // optional (empty = workspace); when set it narrows issue work to the // authenticated member's My Issues relation. parent_issue_id is optional // (NULL = workspace); when set it narrows issue work to the direct children // of that issue, so an issue detail's sub-issue header reads the same // projection as the Issues list header instead of deriving its own count. func (q *Queries) ListWorkspaceWorkingAgents(ctx context.Context, arg ListWorkspaceWorkingAgentsParams) ([]ListWorkspaceWorkingAgentsRow, error) { rows, err := q.db.Query(ctx, listWorkspaceWorkingAgents, arg.WorkspaceID, arg.WorkType, arg.MineRelation, arg.MemberID, arg.ParentIssueID, ) if err != nil { return nil, err } defer rows.Close() items := []ListWorkspaceWorkingAgentsRow{} for rows.Next() { var i ListWorkspaceWorkingAgentsRow if err := rows.Scan( &i.ID, &i.Name, &i.AvatarUrl, &i.RunningTaskCount, &i.IssueIds, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const lockAgentForAutopilotAssignment = `-- name: LockAgentForAutopilotAssignment :one SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters FROM agent WHERE id = $1 AND workspace_id = $2 AND kind = 'user' FOR SHARE ` type LockAgentForAutopilotAssignmentParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } // Serializes creating, retargeting, or resuming an active Autopilot with // Runtime teardown. Teardown takes FOR UPDATE on this same Agent row before it // clears runtime_id and pauses matching Autopilots. FOR SHARE also stabilizes // runtime_id and archived_at against concurrent ordinary Agent updates. The // shared lock makes the // two outcomes exhaustive: // - assignment commits first, then teardown sees and pauses it; or // - teardown commits first, then assignment re-reads runtime_id=NULL and // rejects the active Autopilot. func (q *Queries) LockAgentForAutopilotAssignment(ctx context.Context, arg LockAgentForAutopilotAssignmentParams) (Agent, error) { row := q.db.QueryRow(ctx, lockAgentForAutopilotAssignment, arg.ID, arg.WorkspaceID) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const lockAgentTaskStartClaim = `-- name: LockAgentTaskStartClaim :one SELECT id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step FROM agent_task_queue WHERE id = $1 AND runtime_id = $2 AND dispatched_at = $3 AND status IN ('dispatched', 'waiting_local_directory', 'running') FOR UPDATE ` type LockAgentTaskStartClaimParams struct { ID pgtype.UUID `json:"id"` RuntimeID pgtype.UUID `json:"runtime_id"` DispatchedAt pgtype.Timestamptz `json:"dispatched_at"` } // Serialize start/replay with reclaim and cancellation. A stale delivery must // never start or acknowledge a newer claim, even on the same runtime. func (q *Queries) LockAgentTaskStartClaim(ctx context.Context, arg LockAgentTaskStartClaimParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, lockAgentTaskStartClaim, arg.ID, arg.RuntimeID, arg.DispatchedAt) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const markAgentTaskWaitingLocalDirectory = `-- name: MarkAgentTaskWaitingLocalDirectory :one UPDATE agent_task_queue SET status = 'waiting_local_directory', wait_reason = $2, prepare_lease_expires_at = now() + make_interval(secs => $3::double precision) WHERE id = $1 AND status = 'dispatched' RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type MarkAgentTaskWaitingLocalDirectoryParams struct { ID pgtype.UUID `json:"id"` WaitReason pgtype.Text `json:"wait_reason"` PrepareLeaseSecs float64 `json:"prepare_lease_secs"` } // Transitions a freshly-dispatched task into 'waiting_local_directory' while // the daemon waits for another in-flight task to release the path lock on a // project_resource of type local_directory. wait_reason carries a short // human-readable hint (typically the contested path) that the UI surfaces // alongside the status. // // The CHECK only allows the transition from 'dispatched' so a daemon can't // mark an already-running or terminal task as waiting; the // StartAgentTaskWithSupplement mutation handles the reverse transition once // the lock is acquired. func (q *Queries) MarkAgentTaskWaitingLocalDirectory(ctx context.Context, arg MarkAgentTaskWaitingLocalDirectoryParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, markAgentTaskWaitingLocalDirectory, arg.ID, arg.WaitReason, arg.PrepareLeaseSecs) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const markChatFinalizeDeferred = `-- name: MarkChatFinalizeDeferred :one UPDATE agent_task_queue SET chat_finalize_deferred_at = now() WHERE id = $1 RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Arms the deferred chat-finalize marker for a cancelled chat task whose // empty-transcript judgment must wait for the daemon's flush ack (#5219). func (q *Queries) MarkChatFinalizeDeferred(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, markChatFinalizeDeferred, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const mergeCommentIntoPendingTask = `-- name: MergeCommentIntoPendingTask :one UPDATE agent_task_queue SET coalesced_comment_ids = ( SELECT COALESCE(array_agg(DISTINCT e), '{}') FROM unnest(array_append(coalesced_comment_ids, trigger_comment_id)) AS e WHERE e IS NOT NULL AND e <> $1::uuid ), trigger_comment_id = $1::uuid, trigger_summary = COALESCE($2, trigger_summary), -- Re-attribution is ATOMIC (MUL-4302): folding a newly-arrived comment moves the -- WHOLE attribution snapshot to that comment's human — person columns, source -- label, delegation lineage, rule version, and evidence — computed by the caller -- as one attribution.Result. Re-stamping only the person columns would leave a -- run showing B accountable while still pointing at A's stale source / evidence / -- level. accountable comes from the resolved Result (finalizeAttribution already -- guaranteed originator ⟹ accountable == originator; the cross-column CHECK backs it). originator_user_id = $3::uuid, accountable_user_id = $4::uuid, originator_source = $5, delegated_from_task_id = $6::uuid, rule_version_id = $7::uuid, trigger_evidence_kind = $8, trigger_evidence_ref_id = $9::uuid, runtime_mcp_overlay = $10, runtime_connected_apps = $11 WHERE id = ( SELECT t.id FROM agent_task_queue t WHERE t.context->>'wakeup_id' IS NULL AND t.issue_id = $12 AND t.agent_id = $13 AND t.comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($1::uuid) AND ( t.status = 'queued' OR (t.status = 'deferred' AND t.context->>'channel_issue_media_pending' = 'true') ) -- Head-scoped (TEN-356, #5914): never fold across HEADs. The physical -- unique index is per (issue_id, agent_id, thread), so an insert-race loser can -- collide with a pending task stamped for a DIFFERENT head_sha; merging -- into it would give a new-HEAD comment old-HEAD review coverage. Empty/ -- absent head_sha (no linked PR) matches any task, preserving coalescing. AND ( COALESCE($14::text, '') = '' OR t.context->>'head_sha' = $14::text ) ORDER BY t.created_at DESC LIMIT 1 ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type MergeCommentIntoPendingTaskParams struct { NewTriggerCommentID pgtype.UUID `json:"new_trigger_comment_id"` NewTriggerSummary pgtype.Text `json:"new_trigger_summary"` NewOriginatorUserID pgtype.UUID `json:"new_originator_user_id"` NewAccountableUserID pgtype.UUID `json:"new_accountable_user_id"` NewOriginatorSource pgtype.Text `json:"new_originator_source"` NewDelegatedFromTaskID pgtype.UUID `json:"new_delegated_from_task_id"` NewRuleVersionID pgtype.UUID `json:"new_rule_version_id"` NewTriggerEvidenceKind pgtype.Text `json:"new_trigger_evidence_kind"` NewTriggerEvidenceRefID pgtype.UUID `json:"new_trigger_evidence_ref_id"` NewRuntimeMcpOverlay []byte `json:"new_runtime_mcp_overlay"` NewRuntimeConnectedApps []byte `json:"new_runtime_connected_apps"` IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` HeadSha pgtype.Text `json:"head_sha"` } // MUL-4195: fold a newly-arrived comment into an existing task for (issue, // agent) that has NOT yet been claimed, instead of letting the // HasPendingTaskForIssueAndAgent dedup silently DROP it. The task's prior // trigger_comment_id becomes a coalesced ("also cover") comment and // @new_trigger_comment_id becomes the new trigger, so the injected prompt shows // the latest deliberate instruction while the single run is still told to // address every folded comment. // // Target is restricted to a pre-claim task on purpose (MUL-4195 review rounds // 2–4). This merge is reached when HasPendingTaskForIssueAndAgent matched a // 'queued'/'dispatched' task, or the channel-media deferred task described // below. 'dispatched' is deliberately NOT a // target: a dispatched / waiting_local_directory / running task has already had // its claim response built. Folding afterward would add a planned id that is // absent from that response's delivered_comment_ids receipt; completion // reconciliation handles it instead. Ordinary 'deferred' rows remain excluded: // assignee-fallback escalations have their own fire_at lifecycle and may coexist // with a queued primary. The one exception is a task explicitly marked // channel_issue_media_pending. It is the issue's sole assigned-agent task and // has not been claimable yet, so a new comment must merge into it instead of // creating a competing queued task. coalesced_comment_ids remains the pre-claim // plan; the claim path records the actual embedded subset in // delivered_comment_ids. // // Recompute-on-merge (MUL-4195 review must-fix #1): originator_user_id, // runtime_mcp_overlay and runtime_connected_apps are re-stamped to the NEW // trigger comment's originator (computed by the caller). Earlier this only // repointed the trigger and kept the old originator's overlay/attribution, so a // run answering user B's comment could execute under user A's connected-app // capabilities and audit identity. Re-stamping means the single coalescing run // carries the latest deliberate instruction's originator and the matching // overlay — no cross-user capability bleed, no stale attribution. This also // removes the previous originator gate + fresh-enqueue fallback, which could not // create a second task anyway (the idx_one_pending_task_per_issue_agent unique // index allows only one queued/dispatched task per (issue, agent)) and therefore // silently dropped the mismatched-originator comment. // // Returns pgx.ErrNoRows when no queued task exists (it was claimed/started // between the dedup check and this call, or the only task is already // dispatched/running). The caller must NOT blindly enqueue a fresh task in that // case — a dispatched sibling would trip the unique index — it defers to // completion reconciliation unless no active task exists at all. func (q *Queries) MergeCommentIntoPendingTask(ctx context.Context, arg MergeCommentIntoPendingTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, mergeCommentIntoPendingTask, arg.NewTriggerCommentID, arg.NewTriggerSummary, arg.NewOriginatorUserID, arg.NewAccountableUserID, arg.NewOriginatorSource, arg.NewDelegatedFromTaskID, arg.NewRuleVersionID, arg.NewTriggerEvidenceKind, arg.NewTriggerEvidenceRefID, arg.NewRuntimeMcpOverlay, arg.NewRuntimeConnectedApps, arg.IssueID, arg.AgentID, arg.HeadSha, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const mergeDelegatedFailureCommentIntoPendingTask = `-- name: MergeDelegatedFailureCommentIntoPendingTask :one UPDATE agent_task_queue SET coalesced_comment_ids = ( SELECT COALESCE(array_agg(DISTINCT e), '{}') FROM unnest(array_append(coalesced_comment_ids, trigger_comment_id)) AS e WHERE e IS NOT NULL AND e <> $1::uuid ), trigger_comment_id = $1::uuid, trigger_summary = $2 WHERE id = ( SELECT t.id FROM agent_task_queue t WHERE t.context->>'wakeup_id' IS NULL AND t.issue_id = $3 AND t.agent_id = $4 AND t.comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($1::uuid) AND ( t.status = 'queued' OR (t.status = 'deferred' AND t.context->>'channel_issue_media_pending' = 'true') ) AND t.trigger_comment_id IS DISTINCT FROM $1::uuid AND NOT ($1::uuid = ANY(t.coalesced_comment_ids)) ORDER BY t.created_at DESC LIMIT 1 ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type MergeDelegatedFailureCommentIntoPendingTaskParams struct { CommentID pgtype.UUID `json:"comment_id"` TriggerSummary pgtype.Text `json:"trigger_summary"` IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` } // A delegated failure is a platform-owned input, not a new human instruction: // fold it into the coordinator's pre-claim task without replacing that task's // attribution snapshot. The recovery comment is the newest input, so it becomes // the trigger and the prior trigger joins the coalesced plan. This preserves the // claim/prompt invariant that trigger_comment_id names the newest comment while // retaining the pending task's original human authority and connected apps. func (q *Queries) MergeDelegatedFailureCommentIntoPendingTask(ctx context.Context, arg MergeDelegatedFailureCommentIntoPendingTaskParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, mergeDelegatedFailureCommentIntoPendingTask, arg.CommentID, arg.TriggerSummary, arg.IssueID, arg.AgentID, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const nextDeferredTaskFireAtForRuntimes = `-- name: NextDeferredTaskFireAtForRuntimes :one SELECT MIN(fire_at)::timestamptz FROM agent_task_queue t WHERE t.runtime_id = ANY($1::uuid[]) AND t.status = 'deferred' AND EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = t.runtime_id AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $2::double precision) ) AND ( t.fire_at > now() OR NOT EXISTS ( SELECT 1 FROM agent_task_queue occupant WHERE occupant.issue_id = t.issue_id AND occupant.agent_id = t.agent_id AND occupant.comment_thread_id IS NOT DISTINCT FROM t.comment_thread_id AND occupant.id <> t.id AND ( occupant.status IN ('queued', 'dispatched') OR (occupant.status = 'deferred' AND occupant.context->>'channel_issue_media_pending' = 'true') ) ) ) ` type NextDeferredTaskFireAtForRuntimesParams struct { RuntimeIds []pgtype.UUID `json:"runtime_ids"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` } // Returns the next future deferred task for a daemon's authorized runtime set, // or an eligible task that crossed fire_at during this claim. Overdue tasks // whose runtime is offline/stale or that are blocked by an existing issue+agent // occupant are omitted so they cannot cause a tight poll loop. Keep both fences // in sync with PromoteDueDeferredTasksForRuntimes: a task that cannot be // promoted must not advertise an immediate follow-up claim. The response // converts the timestamp to a relative delay, avoiding any dependency on // daemon/server clock synchronization. func (q *Queries) NextDeferredTaskFireAtForRuntimes(ctx context.Context, arg NextDeferredTaskFireAtForRuntimesParams) (pgtype.Timestamptz, error) { row := q.db.QueryRow(ctx, nextDeferredTaskFireAtForRuntimes, arg.RuntimeIds, arg.RuntimeStaleSecs) var column_1 pgtype.Timestamptz err := row.Scan(&column_1) return column_1, err } const promoteDeferredChannelIssueTask = `-- name: PromoteDeferredChannelIssueTask :one UPDATE agent_task_queue SET status = 'queued', fire_at = NULL WHERE id = $1 AND issue_id IS NOT NULL AND status = 'deferred' RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Early promotion is idempotent at the service layer: a task already promoted // by the fire_at sweeper no longer matches and is treated as settled. func (q *Queries) PromoteDeferredChannelIssueTask(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, promoteDeferredChannelIssueTask, id) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const promoteDueDeferredTasksForRuntime = `-- name: PromoteDueDeferredTasksForRuntime :many WITH due AS ( SELECT t.id, t.issue_id, row_number() OVER ( PARTITION BY t.issue_id, t.agent_id, t.comment_thread_id ORDER BY t.priority DESC, t.created_at ASC, t.id ) AS rn FROM agent_task_queue t WHERE t.runtime_id = $1 AND t.status = 'deferred' AND t.fire_at <= now() AND EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = t.runtime_id AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $2::double precision) ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue occupant WHERE occupant.issue_id = t.issue_id AND occupant.agent_id = t.agent_id AND occupant.comment_thread_id IS NOT DISTINCT FROM t.comment_thread_id AND occupant.id <> t.id AND ( occupant.status IN ('queued', 'dispatched') OR (occupant.status = 'deferred' AND occupant.context->>'channel_issue_media_pending' = 'true') ) ) ) UPDATE agent_task_queue SET status = 'queued' WHERE id IN (SELECT id FROM due WHERE issue_id IS NULL OR rn = 1) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type PromoteDueDeferredTasksForRuntimeParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` } // Promotion is fenced against the single queued/dispatched slot // idx_one_pending_task_per_issue_agent_thread allows per (issue, agent, thread). A deferred // row is NOT covered by that index, so it can legitimately coexist with a queued // one — a manual rerun enqueued behind a running task, plus the deferred retry // that task's failure armed (runtime_offline, provider_network's final attempt). // Flipping such a row to 'queued' unconditionally violates the index, and because // the claim loop promotes before it claims, that error blocked every claim on the // runtime — including the rerun the operator was waiting for. // // Two fences: skip a row whose (issue, agent, thread) slot is already occupied, and // promote at most ONE row per (issue, agent, thread) so a single statement cannot collide // with itself. A skipped row stays deferred with fire_at in the past and is // promoted by a later tick once the slot frees, so nothing is lost — the human's // rerun simply goes first. Chat / quick-create rows (issue_id NULL) are outside // the index and bypass both fences. func (q *Queries) PromoteDueDeferredTasksForRuntime(ctx context.Context, arg PromoteDueDeferredTasksForRuntimeParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, promoteDueDeferredTasksForRuntime, arg.RuntimeID, arg.RuntimeStaleSecs) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const promoteDueDeferredTasksForRuntimes = `-- name: PromoteDueDeferredTasksForRuntimes :many WITH due AS ( SELECT t.id, t.issue_id, row_number() OVER ( PARTITION BY t.issue_id, t.agent_id, t.comment_thread_id ORDER BY t.priority DESC, t.created_at ASC, t.id ) AS rn FROM agent_task_queue t WHERE t.runtime_id = ANY($1::uuid[]) AND t.status = 'deferred' AND t.fire_at <= now() AND EXISTS ( SELECT 1 FROM agent_runtime r WHERE r.id = t.runtime_id AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $2::double precision) ) AND NOT EXISTS ( SELECT 1 FROM agent_task_queue occupant WHERE occupant.issue_id = t.issue_id AND occupant.agent_id = t.agent_id AND occupant.comment_thread_id IS NOT DISTINCT FROM t.comment_thread_id AND occupant.id <> t.id AND ( occupant.status IN ('queued', 'dispatched') OR (occupant.status = 'deferred' AND occupant.context->>'channel_issue_media_pending' = 'true') ) ) ) UPDATE agent_task_queue SET status = 'queued' WHERE id IN (SELECT id FROM due WHERE issue_id IS NULL OR rn = 1) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type PromoteDueDeferredTasksForRuntimesParams struct { RuntimeIds []pgtype.UUID `json:"runtime_ids"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` } // Batch variant of PromoteDueDeferredTasksForRuntime (MUL-4257): promotes all // due deferred tasks across the runtime set in one UPDATE. Carries the same two // fences as the singular query; see its comment for why. func (q *Queries) PromoteDueDeferredTasksForRuntimes(ctx context.Context, arg PromoteDueDeferredTasksForRuntimesParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, promoteDueDeferredTasksForRuntimes, arg.RuntimeIds, arg.RuntimeStaleSecs) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const rebindAgentBuilderRuntime = `-- name: RebindAgentBuilderRuntime :one UPDATE agent SET runtime_id = $1, runtime_mode = $2, model = $3, updated_at = now() WHERE id = $4 AND kind = 'system' AND system_key LIKE 'agent_builder:%' RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type RebindAgentBuilderRuntimeParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` RuntimeMode string `json:"runtime_mode"` Model pgtype.Text `json:"model"` ID pgtype.UUID `json:"id"` } // Re-points a builder carrier at another runtime mid-conversation. The carrier // is what SendDirectChatMessage reads to stamp a chat task's runtime_id, so this // UPDATE is the only thing that actually moves subsequent replies; the live-draft // picker alone never did. model is reset wholesale because model ids are // per-runtime — the new runtime resolves its own default instead of inheriting an // id it may not serve. The kind/system_key guard mirrors DeleteSystemAgentByID so // this path can never touch a user-authored agent. // // Callers MUST hold LockChatSessionForRuntimeBind on the owning chat_session for // the whole transaction, otherwise a concurrent send can stamp a task with the // pre-switch runtime after the pending-task check has already passed (MUL-5163). // // chat_session.runtime_id is deliberately left untouched: the daemon only resumes // a stored provider session when chat_session.runtime_id matches the claiming // task's runtime (see the chat claim path), so leaving the old pointer in place is // exactly what makes the new runtime start a fresh provider session. func (q *Queries) RebindAgentBuilderRuntime(ctx context.Context, arg RebindAgentBuilderRuntimeParams) (Agent, error) { row := q.db.QueryRow(ctx, rebindAgentBuilderRuntime, arg.RuntimeID, arg.RuntimeMode, arg.Model, arg.ID, ) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const reclaimStaleDispatchedTaskForRuntime = `-- name: ReclaimStaleDispatchedTaskForRuntime :one UPDATE agent_task_queue SET dispatched_at = now(), prepare_lease_expires_at = now() + make_interval(secs => $2::double precision) WHERE id = ( SELECT atq.id FROM agent_task_queue atq WHERE atq.runtime_id = $1 AND atq.status = 'dispatched' AND atq.started_at IS NULL AND atq.dispatched_at < now() - make_interval(secs => $3::double precision) AND (atq.prepare_lease_expires_at IS NULL OR atq.prepare_lease_expires_at < now()) AND EXISTS ( -- Keep the dispatched-reclaim owner fence intentionally stricter -- than the queued claim carve-out below. SELECT 1 FROM agent a JOIN agent_runtime r ON r.id = atq.runtime_id WHERE a.id = atq.agent_id AND a.runtime_id = atq.runtime_id AND ( r.visibility = 'public' OR ( r.visibility = 'private' AND ( r.owner_id IS NULL OR a.owner_id IS NULL OR r.owner_id = a.owner_id OR a.owner_id = ANY(r.shared_with) ) ) ) AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $4::double precision) ) ORDER BY atq.priority DESC, atq.dispatched_at ASC LIMIT 1 FOR UPDATE SKIP LOCKED ) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type ReclaimStaleDispatchedTaskForRuntimeParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` PrepareLeaseSecs float64 `json:"prepare_lease_secs"` ClaimRecoverySecs float64 `json:"claim_recovery_secs"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` } // Re-delivers a task whose previous claim likely succeeded server-side but // whose response never reached the daemon. The task is still in `dispatched` // with no `started_at`, so the daemon has not acknowledged it via StartTask. // Refresh dispatched_at so the server-side dispatch timeout measures from the // recovered delivery attempt. func (q *Queries) ReclaimStaleDispatchedTaskForRuntime(ctx context.Context, arg ReclaimStaleDispatchedTaskForRuntimeParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, reclaimStaleDispatchedTaskForRuntime, arg.RuntimeID, arg.PrepareLeaseSecs, arg.ClaimRecoverySecs, arg.RuntimeStaleSecs, ) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const reclaimStaleDispatchedTasksForRuntimes = `-- name: ReclaimStaleDispatchedTasksForRuntimes :many WITH candidates AS MATERIALIZED ( SELECT atq.id FROM agent_task_queue atq WHERE atq.runtime_id = ANY($2::uuid[]) AND atq.status = 'dispatched' AND atq.started_at IS NULL AND atq.dispatched_at < now() - make_interval(secs => $3::double precision) AND (atq.prepare_lease_expires_at IS NULL OR atq.prepare_lease_expires_at < now()) AND EXISTS ( -- Keep the dispatched-reclaim owner fence intentionally stricter -- than the queued claim carve-out below. SELECT 1 FROM agent a JOIN agent_runtime r ON r.id = atq.runtime_id WHERE a.id = atq.agent_id AND a.runtime_id = atq.runtime_id AND ( r.visibility = 'public' OR ( r.visibility = 'private' AND ( r.owner_id IS NULL OR a.owner_id IS NULL OR r.owner_id = a.owner_id OR a.owner_id = ANY(r.shared_with) ) ) ) AND r.status = 'online' AND COALESCE(r.last_seen_at, r.updated_at) >= now() - make_interval(secs => $4::double precision) ) ORDER BY atq.priority DESC, atq.dispatched_at ASC LIMIT $5::int FOR UPDATE SKIP LOCKED ) UPDATE agent_task_queue SET dispatched_at = now(), prepare_lease_expires_at = now() + make_interval(secs => $1::double precision) WHERE id IN (SELECT id FROM candidates) RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type ReclaimStaleDispatchedTasksForRuntimesParams struct { PrepareLeaseSecs float64 `json:"prepare_lease_secs"` RuntimeIds []pgtype.UUID `json:"runtime_ids"` ClaimRecoverySecs float64 `json:"claim_recovery_secs"` RuntimeStaleSecs float64 `json:"runtime_stale_secs"` MaxTasks int32 `json:"max_tasks"` } // Batch variant of ReclaimStaleDispatchedTaskForRuntime (MUL-4257): re-delivers // up to @max_tasks tasks across the whole runtime set in one round trip, so a // machine-level batch claim recovers lost-response dispatches for every runtime // it hosts without one query per runtime. Same eligibility as the singular // query (dispatched, never started, past the recovery window, expired/absent // prepare lease) and the same dispatched_at refresh; only the runtime filter // (= ANY) and the LIMIT (max_tasks instead of 1) differ. // MATERIALIZED pins the candidate set so nested-loop rescans cannot exceed // max_tasks. func (q *Queries) ReclaimStaleDispatchedTasksForRuntimes(ctx context.Context, arg ReclaimStaleDispatchedTasksForRuntimesParams) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, reclaimStaleDispatchedTasksForRuntimes, arg.PrepareLeaseSecs, arg.RuntimeIds, arg.ClaimRecoverySecs, arg.RuntimeStaleSecs, arg.MaxTasks, ) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const recoverOrphanedTasksForRuntime = `-- name: RecoverOrphanedTasksForRuntime :many UPDATE agent_task_queue SET status = 'failed', completed_at = now(), error = 'daemon restarted while task was in flight', failure_reason = 'runtime_recovery', wait_reason = NULL, prepare_lease_expires_at = NULL WHERE runtime_id = $1 AND status IN ('dispatched', 'running', 'waiting_local_directory') RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` // Called by the daemon at startup. Atomically fails any dispatched/running/ // waiting_local_directory task that the prior incarnation of this runtime // owned but did not finalize. Returns the failed rows so callers can hand // them to the auto-retry path. waiting_local_directory rows are included // because the daemon holding the path lock is the same process that just // died — without us, the row would sit waiting forever. func (q *Queries) RecoverOrphanedTasksForRuntime(ctx context.Context, runtimeID pgtype.UUID) ([]AgentTaskQueue, error) { rows, err := q.db.Query(ctx, recoverOrphanedTasksForRuntime, runtimeID) if err != nil { return nil, err } defer rows.Close() items := []AgentTaskQueue{} for rows.Next() { var i AgentTaskQueue if err := rows.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const refreshAgentStatusFromTasks = `-- name: RefreshAgentStatusFromTasks :one WITH desired AS ( SELECT CASE WHEN EXISTS ( SELECT 1 FROM agent_task_queue q WHERE q.agent_id = $1 AND q.status IN ('dispatched', 'running') ) THEN 'working' ELSE 'idle' END AS status ) UPDATE agent AS a SET status = desired.status, updated_at = now() FROM desired WHERE a.id = $1 AND a.status IS DISTINCT FROM desired.status RETURNING a.id, a.workspace_id, a.name, a.avatar_url, a.runtime_mode, a.runtime_config, a.visibility, a.status, a.max_concurrent_tasks, a.owner_id, a.created_at, a.updated_at, a.description, a.runtime_id, a.instructions, a.archived_at, a.archived_by, a.custom_env, a.custom_args, a.mcp_config, a.model, a.thinking_level, a.composio_toolkit_allowlist, a.permission_mode, a.kind, a.system_key, a.disabled_runtime_skills, a.service_tier, a.conversation_starters ` // Persisted agent.status has no queued/resource-wait bucket. Keep dispatched // as working because the daemon is actively preparing that task, but do not // let a task parked on a local_directory mutex masquerade as executing work. // The status guard makes a correct status a no-op: :one returns no rows, so // callers neither rewrite updated_at nor publish a redundant status event. func (q *Queries) RefreshAgentStatusFromTasks(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, refreshAgentStatusFromTasks, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const registerPlannedCommentForActiveTask = `-- name: RegisterPlannedCommentForActiveTask :one UPDATE agent_task_queue SET coalesced_comment_ids = ( SELECT COALESCE(array_agg(DISTINCT e), '{}') FROM unnest(array_append(coalesced_comment_ids, $1::uuid)) AS e WHERE e IS NOT NULL ) WHERE id = ( SELECT t.id FROM agent_task_queue t WHERE t.context->>'wakeup_id' IS NULL AND t.issue_id = $2 AND t.agent_id = $3 AND t.comment_thread_id IS NOT DISTINCT FROM comment_thread_root_id($1::uuid) AND t.status IN ('dispatched', 'running', 'waiting_local_directory') AND ( COALESCE($4::text, '') = '' OR t.context->>'head_sha' = $4::text ) ORDER BY t.created_at DESC LIMIT 1 ) AND status IN ('dispatched', 'running', 'waiting_local_directory') RETURNING id, coalesced_comment_ids ` type RegisterPlannedCommentForActiveTaskParams struct { CommentID pgtype.UUID `json:"comment_id"` IssueID pgtype.UUID `json:"issue_id"` AgentID pgtype.UUID `json:"agent_id"` HeadSha pgtype.Text `json:"head_sha"` } type RegisterPlannedCommentForActiveTaskRow struct { ID pgtype.UUID `json:"id"` CoalescedCommentIds []pgtype.UUID `json:"coalesced_comment_ids"` } // #5914: durably register a comment that lost an enqueue race as a PLANNED // (undelivered) input on the same-(issue, agent) ACTIVE task whose queued row // MergeCommentIntoPendingTask could no longer target (it was claimed → // dispatched/running). The losing comment can predate that task's created_at, // so completion reconciliation's `created_at > since` window cannot see it; // appending it to coalesced_comment_ids (the planned set) WITHOUT touching // delivered_comment_ids makes reconcileCommentsOnCompletion replay it as a // single bounded follow-up (planned-but-not-delivered ⇒ follow-up). // // Head-scoped (TEN-356): only a task stamped with the SAME head_sha is a target, // so a new-HEAD comment is never attached to an old-HEAD run. Returns // pgx.ErrNoRows when no same-head active task exists (different HEAD, or the task // just terminated) so the caller falls back to the active-task decision. // // 'queued' is DELIBERATELY EXCLUDED (#5914, Elon round 3): a not-yet-claimed // task has no claim receipt, so a comment merged into it WILL be recorded as // delivered at claim time and never earns a completion follow-up under its own // attribution. A queued target must therefore go through the ATOMIC // MergeCommentIntoPendingTask (which re-stamps trigger/originator/accountable/ // overlay), never a bare planned append — otherwise a second member's comment // could execute under the first member's identity/connected-apps (MUL-4302). // Only claim-receipt statuses (already-built delivered set) are safe planned-id // targets. // Recheck status on the UPDATE target after a concurrent row-lock wait. The // subquery can see an active snapshot while completion commits; appending to // that completed row would be too late for its completion replay to see it. func (q *Queries) RegisterPlannedCommentForActiveTask(ctx context.Context, arg RegisterPlannedCommentForActiveTaskParams) (RegisterPlannedCommentForActiveTaskRow, error) { row := q.db.QueryRow(ctx, registerPlannedCommentForActiveTask, arg.CommentID, arg.IssueID, arg.AgentID, arg.HeadSha, ) var i RegisterPlannedCommentForActiveTaskRow err := row.Scan(&i.ID, &i.CoalescedCommentIds) return i, err } const requeueAgentTaskAfterClaimFailure = `-- name: RequeueAgentTaskAfterClaimFailure :one UPDATE agent_task_queue SET status = 'queued', dispatched_at = NULL, prepare_lease_expires_at = NULL, delivered_comment_ids = '{}' WHERE id = $1 AND runtime_id = $2 AND status = 'dispatched' AND started_at IS NULL AND dispatched_at = $3 RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step ` type RequeueAgentTaskAfterClaimFailureParams struct { TaskID pgtype.UUID `json:"task_id"` RuntimeID pgtype.UUID `json:"runtime_id"` DispatchedAt pgtype.Timestamptz `json:"dispatched_at"` } // Claim finalization (task token + optional comment receipt) failed before any // response bytes were written. Return only that exact claim generation to the // queue so another poll can retry immediately instead of waiting for stale // dispatch recovery. The dispatched_at CAS prevents an old handler from // rolling back a newer reclaim. func (q *Queries) RequeueAgentTaskAfterClaimFailure(ctx context.Context, arg RequeueAgentTaskAfterClaimFailureParams) (AgentTaskQueue, error) { row := q.db.QueryRow(ctx, requeueAgentTaskAfterClaimFailure, arg.TaskID, arg.RuntimeID, arg.DispatchedAt) var i AgentTaskQueue err := row.Scan( &i.ID, &i.AgentID, &i.IssueID, &i.Status, &i.Priority, &i.DispatchedAt, &i.StartedAt, &i.CompletedAt, &i.Result, &i.Error, &i.CreatedAt, &i.Context, &i.RuntimeID, &i.SessionID, &i.WorkDir, &i.TriggerCommentID, &i.ChatSessionID, &i.AutopilotRunID, &i.Attempt, &i.MaxAttempts, &i.ParentTaskID, &i.FailureReason, &i.TriggerSummary, &i.ForceFreshSession, &i.IsLeaderTask, &i.WaitReason, &i.InitiatorUserID, &i.HandoffNote, &i.PrepareLeaseExpiresAt, &i.SquadID, &i.RuntimeMcpOverlay, &i.EscalationForTaskID, &i.FireAt, &i.OriginatorUserID, &i.RuntimeConnectedApps, &i.CoalescedCommentIds, &i.DeliveredCommentIds, &i.ChatInputTaskID, &i.ChatFinalizeDeferredAt, &i.OriginatorSource, &i.DelegatedFromTaskID, &i.RetryOfTaskID, &i.RerunOfTaskID, &i.RuleVersionID, &i.TriggerEvidenceKind, &i.TriggerEvidenceRefID, &i.AccountableUserID, &i.SessionRolloutMissing, &i.RetiredSessionID, &i.QuickActionsDisabled, &i.RegenerateQuickActionsFor, &i.BranchName, &i.DurableWorkDir, &i.ChannelContextRevision, &i.CommentThreadID, &i.CancelledByType, &i.CancelledByID, &i.CancelledByName, &i.IssueSnapshot, &i.WorkflowStep, ) return i, err } const restoreAgent = `-- name: RestoreAgent :one UPDATE agent SET archived_at = NULL, archived_by = NULL, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` func (q *Queries) RestoreAgent(ctx context.Context, id pgtype.UUID) (Agent, error) { row := q.db.QueryRow(ctx, restoreAgent, id) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const setAgentTaskBranchName = `-- name: SetAgentTaskBranchName :exec UPDATE agent_task_queue SET branch_name = COALESCE(branch_name, $1) WHERE id = $2 AND status = 'cancelled' ` type SetAgentTaskBranchNameParams struct { BranchName pgtype.Text `json:"branch_name"` ID pgtype.UUID `json:"id"` } // Records the delivered branch on a CANCELLED task. Needed because the daemon // finalizes its worktree (committing whatever the agent produced) BEFORE it // learns the task was cancelled, so the branch exists but the cancel path has // no result payload to carry it. Without this the branch is real and // completely undiscoverable from the UI. // // Never overwrites a name already recorded by a complete/fail callback, and // never touches any OTHER terminal state: the daemon acks on every terminal // status it observes, so a late or replayed ack from a stale run could // otherwise write its branch into a completed/failed row whose own callback — // the authoritative channel for those states — recorded nothing. status is // stable once terminal, so this CAS cannot race a legitimate write. func (q *Queries) SetAgentTaskBranchName(ctx context.Context, arg SetAgentTaskBranchNameParams) error { _, err := q.db.Exec(ctx, setAgentTaskBranchName, arg.BranchName, arg.ID) return err } const setAgentTaskDurableWorkDir = `-- name: SetAgentTaskDurableWorkDir :exec UPDATE agent_task_queue SET durable_work_dir = COALESCE(durable_work_dir, $1) WHERE id = $2 AND status = 'cancelled' ` type SetAgentTaskDurableWorkDirParams struct { DurableWorkDir pgtype.Text `json:"durable_work_dir"` ID pgtype.UUID `json:"id"` } // Records the durable replacement for a disposable worktree on a CANCELLED // task. The daemon only sends this after Finalize confirms the task worktree // is gone. As with branch_name, the status CAS rejects stale acknowledgements // and COALESCE makes replays idempotent. func (q *Queries) SetAgentTaskDurableWorkDir(ctx context.Context, arg SetAgentTaskDurableWorkDirParams) error { _, err := q.db.Exec(ctx, setAgentTaskDurableWorkDir, arg.DurableWorkDir, arg.ID) return err } const setAgentTaskErrorIfEmpty = `-- name: SetAgentTaskErrorIfEmpty :exec UPDATE agent_task_queue SET error = $1, failure_reason = COALESCE(failure_reason, $2) WHERE id = $3 AND (error IS NULL OR error = '') AND status = 'cancelled' ` type SetAgentTaskErrorIfEmptyParams struct { Error pgtype.Text `json:"error"` FailureReason pgtype.Text `json:"failure_reason"` ID pgtype.UUID `json:"id"` } // Companion to SetAgentTaskBranchName for the cancel-ack path. A cancelled // worktree task whose Finalize ABORTED has no branch to deliver — the error // text carrying the preserved-worktree path is the only pointer to the agent's // work, and the cancel flow discarded the rest of the result. A user-initiated // cancel leaves error NULL, so filling it here never overwrites a reason // recorded by a fail callback or the claim gate; the status CAS keeps a late // ack from stamping an error onto a completed/failed row (see // SetAgentTaskBranchName above). func (q *Queries) SetAgentTaskErrorIfEmpty(ctx context.Context, arg SetAgentTaskErrorIfEmptyParams) error { _, err := q.db.Exec(ctx, setAgentTaskErrorIfEmpty, arg.Error, arg.FailureReason, arg.ID) return err } const setAgentTaskRetiredSessionIDIfCancelled = `-- name: SetAgentTaskRetiredSessionIDIfCancelled :execrows UPDATE agent_task_queue SET retired_session_id = COALESCE(retired_session_id, $1) WHERE id = $2 AND status = 'cancelled' ` type SetAgentTaskRetiredSessionIDIfCancelledParams struct { RetiredSessionID pgtype.Text `json:"retired_session_id"` ID pgtype.UUID `json:"id"` } // Cancel-ack companion to CompleteAgentTask/FailAgentTask's retired_session_id // (MUL-8188). A resumed run that produced no output at all before it was // cancelled is the signature of a session the runtime cannot drive (Kiro // loaded the transcript, then hung on the first prompt). Without this the // cancelled row kept the session resumable and every later trigger re-entered // the same hang. Same status CAS and never-overwrite rule as the other // cancel-ack writes; the row count tells the caller whether it applied. func (q *Queries) SetAgentTaskRetiredSessionIDIfCancelled(ctx context.Context, arg SetAgentTaskRetiredSessionIDIfCancelledParams) (int64, error) { result, err := q.db.Exec(ctx, setAgentTaskRetiredSessionIDIfCancelled, arg.RetiredSessionID, arg.ID) if err != nil { return 0, err } return result.RowsAffected(), nil } const setDeferredChannelIssueTaskRuntimeOverlay = `-- name: SetDeferredChannelIssueTaskRuntimeOverlay :execrows UPDATE agent_task_queue SET runtime_mcp_overlay = $1, runtime_connected_apps = $2 WHERE id = $3 AND status = 'deferred' AND context->>'channel_issue_media_pending' = 'true' AND trigger_comment_id IS NULL AND originator_user_id IS NOT DISTINCT FROM $4::uuid ` type SetDeferredChannelIssueTaskRuntimeOverlayParams struct { RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"` RuntimeConnectedApps []byte `json:"runtime_connected_apps"` ID pgtype.UUID `json:"id"` ExpectedOriginatorUserID pgtype.UUID `json:"expected_originator_user_id"` } // The issue and its inert media-gated task commit atomically before the optional // external overlay is built. Only hydrate the untouched original plan: a comment // merge may already have replaced the trigger, attribution, and matching overlay. func (q *Queries) SetDeferredChannelIssueTaskRuntimeOverlay(ctx context.Context, arg SetDeferredChannelIssueTaskRuntimeOverlayParams) (int64, error) { result, err := q.db.Exec(ctx, setDeferredChannelIssueTaskRuntimeOverlay, arg.RuntimeMcpOverlay, arg.RuntimeConnectedApps, arg.ID, arg.ExpectedOriginatorUserID, ) if err != nil { return 0, err } return result.RowsAffected(), nil } const setTaskDeliveredCommentIDs = `-- name: SetTaskDeliveredCommentIDs :one UPDATE agent_task_queue SET delivered_comment_ids = $1::uuid[] WHERE id = $2 AND runtime_id = $3 AND status = 'dispatched' AND started_at IS NULL AND dispatched_at = $4 AND trigger_comment_id IS NOT DISTINCT FROM $5::uuid AND NOT EXISTS ( SELECT 1 FROM unnest($1::uuid[]) AS delivered(id) WHERE delivered.id IS NULL OR ( delivered.id IS DISTINCT FROM trigger_comment_id AND NOT (delivered.id = ANY(coalesced_comment_ids)) ) ) RETURNING delivered_comment_ids ` type SetTaskDeliveredCommentIDsParams struct { DeliveredCommentIds []pgtype.UUID `json:"delivered_comment_ids"` TaskID pgtype.UUID `json:"task_id"` RuntimeID pgtype.UUID `json:"runtime_id"` DispatchedAt pgtype.Timestamptz `json:"dispatched_at"` ExpectedTriggerCommentID pgtype.UUID `json:"expected_trigger_comment_id"` } // Replace (rather than append to) the delivery receipt for this claim. A stale // dispatched task may be reclaimed by a daemon with different capabilities, // so only the ids embedded in the newest response count as delivered. The CAS // keeps a stale handler from writing after execution starts, and the subset // guard prevents acknowledging an id outside the task's enqueue-time plan. func (q *Queries) SetTaskDeliveredCommentIDs(ctx context.Context, arg SetTaskDeliveredCommentIDsParams) ([]pgtype.UUID, error) { row := q.db.QueryRow(ctx, setTaskDeliveredCommentIDs, arg.DeliveredCommentIds, arg.TaskID, arg.RuntimeID, arg.DispatchedAt, arg.ExpectedTriggerCommentID, ) var delivered_comment_ids []pgtype.UUID err := row.Scan(&delivered_comment_ids) return delivered_comment_ids, err } const setTaskIssueSnapshot = `-- name: SetTaskIssueSnapshot :exec UPDATE agent_task_queue SET issue_snapshot = $1 WHERE id = $2 AND runtime_id = $3 AND status = 'dispatched' AND started_at IS NULL AND dispatched_at = $4 ` type SetTaskIssueSnapshotParams struct { IssueSnapshot []byte `json:"issue_snapshot"` TaskID pgtype.UUID `json:"task_id"` RuntimeID pgtype.UUID `json:"runtime_id"` DispatchedAt pgtype.Timestamptz `json:"dispatched_at"` } // Record the comparable issue state this claim's payload was built from, so the // NEXT run this agent takes on the issue can be told whether the issue itself // moved. Written for every issue-bound claim, not just comment-backed ones: an // assignment run that skips this leaves the following run with no baseline to // compare against, which reads as "not compared" and costs an extra issue read. // Same CAS as SetTaskDeliveredCommentIDs so a stale handler cannot overwrite a // newer reclaim's snapshot, or write one after execution has started. func (q *Queries) SetTaskIssueSnapshot(ctx context.Context, arg SetTaskIssueSnapshotParams) error { _, err := q.db.Exec(ctx, setTaskIssueSnapshot, arg.IssueSnapshot, arg.TaskID, arg.RuntimeID, arg.DispatchedAt, ) return err } const setTaskWorkflowStep = `-- name: SetTaskWorkflowStep :exec UPDATE agent_task_queue SET workflow_step = $1 WHERE id = $2 ` type SetTaskWorkflowStepParams struct { WorkflowStep pgtype.Text `json:"workflow_step"` ID pgtype.UUID `json:"id"` } // Moves a run's workflow step along with a status change the run itself made // (MUL-7420). See migration 557. func (q *Queries) SetTaskWorkflowStep(ctx context.Context, arg SetTaskWorkflowStepParams) error { _, err := q.db.Exec(ctx, setTaskWorkflowStep, arg.WorkflowStep, arg.ID) return err } const settleDelegatedFailureRecoveriesForTask = `-- name: SettleDelegatedFailureRecoveriesForTask :execrows UPDATE comment recovery SET recovery_settled_at = now() FROM agent_task_queue task WHERE task.id = $1 AND task.status IN ('completed', 'failed', 'cancelled') AND recovery.id = ANY(task.delivered_comment_ids) AND recovery.author_type = 'system' AND recovery.type = 'progress_update' AND recovery.source_task_id IS NOT NULL AND recovery.recovery_settled_at IS NULL ` // Retire every delegated-failure recovery comment this task actually received. // // Callers MUST run this inside the same transaction that makes the task // terminal, and only there. While a task is still dispatched its receipt is // REPLACED rather than appended (SetTaskDeliveredCommentIDs), because a reclaim // by a differently-capable daemon must be able to drop an id it will not // deliver. Settling at receipt-write time would freeze that legitimate // transient window into a permanently lost recovery. Once the task is terminal // the receipt is final, which is exactly the guarantee this marker records. // // A comment that was only planned into the task and never delivered is absent // from delivered_comment_ids, so an automatic cancellation correctly leaves it // pending for the sweeper to replay. // // The terminal-status predicate is the guarantee itself, not a caller // convention. Settling a dispatched or running task's receipt would freeze the // reclaim window into a permanently lost recovery, and this marker is // monotonic — there is no later pass that could undo it. A caller that passes a // non-terminal task therefore updates nothing rather than silently destroying // the obligation. func (q *Queries) SettleDelegatedFailureRecoveriesForTask(ctx context.Context, taskID pgtype.UUID) (int64, error) { result, err := q.db.Exec(ctx, settleDelegatedFailureRecoveriesForTask, taskID) if err != nil { return 0, err } return result.RowsAffected(), nil } const settleDelegatedFailureRecoveryComment = `-- name: SettleDelegatedFailureRecoveryComment :execrows UPDATE comment SET recovery_settled_at = now() WHERE id = $1 AND author_type = 'system' AND type = 'progress_update' AND source_task_id IS NOT NULL AND recovery_settled_at IS NULL ` // Retire one recovery comment by id, for the terminal outcomes that are not a // task reaching a terminal status: exhaustion of the bounded automatic attempts // writes its receipt onto an attempt row that may still be running, and the // user-visible explanation comment is what closes the obligation. Callers run // this in the same transaction that records that outcome. func (q *Queries) SettleDelegatedFailureRecoveryComment(ctx context.Context, commentID pgtype.UUID) (int64, error) { result, err := q.db.Exec(ctx, settleDelegatedFailureRecoveryComment, commentID) if err != nil { return 0, err } return result.RowsAffected(), nil } const updateAgent = `-- name: UpdateAgent :one UPDATE agent SET name = COALESCE($2, name), description = COALESCE($3, description), avatar_url = COALESCE($4, avatar_url), runtime_config = COALESCE($5, runtime_config), runtime_mode = COALESCE($6, runtime_mode), runtime_id = COALESCE($7, runtime_id), visibility = COALESCE($8, visibility), permission_mode = COALESCE($9, permission_mode), status = COALESCE($10, status), max_concurrent_tasks = COALESCE($11, max_concurrent_tasks), instructions = COALESCE($12, instructions), custom_env = COALESCE($13, custom_env), custom_args = COALESCE($14, custom_args), mcp_config = COALESCE($15, mcp_config), model = COALESCE($16, model), thinking_level = COALESCE($17, thinking_level), service_tier = COALESCE($18, service_tier), conversation_starters = COALESCE($19, conversation_starters), composio_toolkit_allowlist = COALESCE($20::text[], composio_toolkit_allowlist), updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type UpdateAgentParams struct { ID pgtype.UUID `json:"id"` Name pgtype.Text `json:"name"` Description pgtype.Text `json:"description"` AvatarUrl pgtype.Text `json:"avatar_url"` RuntimeConfig []byte `json:"runtime_config"` RuntimeMode pgtype.Text `json:"runtime_mode"` RuntimeID pgtype.UUID `json:"runtime_id"` Visibility pgtype.Text `json:"visibility"` PermissionMode pgtype.Text `json:"permission_mode"` Status pgtype.Text `json:"status"` MaxConcurrentTasks pgtype.Int4 `json:"max_concurrent_tasks"` Instructions pgtype.Text `json:"instructions"` CustomEnv []byte `json:"custom_env"` CustomArgs []byte `json:"custom_args"` McpConfig []byte `json:"mcp_config"` Model pgtype.Text `json:"model"` ThinkingLevel pgtype.Text `json:"thinking_level"` ServiceTier pgtype.Text `json:"service_tier"` ConversationStarters []byte `json:"conversation_starters"` ComposioToolkitAllowlist []string `json:"composio_toolkit_allowlist"` } // composio_toolkit_allowlist is set wholesale: the API layer is responsible // for normalising the request payload to either (a) the new slug list — sent // here verbatim — or (b) an empty array to explicitly disable Composio. // Distinguish "field omitted" (preserve) from "explicit clear" via // ClearAgentComposioToolkitAllowlist below, mirroring the // thinking_level / mcp_config two-query pattern: COALESCE can't restore NULL. func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent, error) { row := q.db.QueryRow(ctx, updateAgent, arg.ID, arg.Name, arg.Description, arg.AvatarUrl, arg.RuntimeConfig, arg.RuntimeMode, arg.RuntimeID, arg.Visibility, arg.PermissionMode, arg.Status, arg.MaxConcurrentTasks, arg.Instructions, arg.CustomEnv, arg.CustomArgs, arg.McpConfig, arg.Model, arg.ThinkingLevel, arg.ServiceTier, arg.ConversationStarters, arg.ComposioToolkitAllowlist, ) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const updateAgentCustomEnv = `-- name: UpdateAgentCustomEnv :one UPDATE agent SET custom_env = $2, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type UpdateAgentCustomEnvParams struct { ID pgtype.UUID `json:"id"` CustomEnv []byte `json:"custom_env"` } // Replaces an agent's custom_env map wholesale. Used by the dedicated // env-management endpoint (POST/PUT /api/agents/{id}/env), which is the // only post-creation write path for env. UpdateAgent has been stripped // of custom_env handling so all env mutations flow through here and the // handler's audit-log + **** sentinel guard. func (q *Queries) UpdateAgentCustomEnv(ctx context.Context, arg UpdateAgentCustomEnvParams) (Agent, error) { row := q.db.QueryRow(ctx, updateAgentCustomEnv, arg.ID, arg.CustomEnv) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const updateAgentDisabledRuntimeSkills = `-- name: UpdateAgentDisabledRuntimeSkills :one UPDATE agent SET disabled_runtime_skills = $2, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type UpdateAgentDisabledRuntimeSkillsParams struct { ID pgtype.UUID `json:"id"` DisabledRuntimeSkills []byte `json:"disabled_runtime_skills"` } func (q *Queries) UpdateAgentDisabledRuntimeSkills(ctx context.Context, arg UpdateAgentDisabledRuntimeSkillsParams) (Agent, error) { row := q.db.QueryRow(ctx, updateAgentDisabledRuntimeSkills, arg.ID, arg.DisabledRuntimeSkills) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const updateAgentStatus = `-- name: UpdateAgentStatus :one UPDATE agent SET status = $2, updated_at = now() WHERE id = $1 RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by, custom_env, custom_args, mcp_config, model, thinking_level, composio_toolkit_allowlist, permission_mode, kind, system_key, disabled_runtime_skills, service_tier, conversation_starters ` type UpdateAgentStatusParams struct { ID pgtype.UUID `json:"id"` Status string `json:"status"` } func (q *Queries) UpdateAgentStatus(ctx context.Context, arg UpdateAgentStatusParams) (Agent, error) { row := q.db.QueryRow(ctx, updateAgentStatus, arg.ID, arg.Status) var i Agent err := row.Scan( &i.ID, &i.WorkspaceID, &i.Name, &i.AvatarUrl, &i.RuntimeMode, &i.RuntimeConfig, &i.Visibility, &i.Status, &i.MaxConcurrentTasks, &i.OwnerID, &i.CreatedAt, &i.UpdatedAt, &i.Description, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, &i.ArchivedBy, &i.CustomEnv, &i.CustomArgs, &i.McpConfig, &i.Model, &i.ThinkingLevel, &i.ComposioToolkitAllowlist, &i.PermissionMode, &i.Kind, &i.SystemKey, &i.DisabledRuntimeSkills, &i.ServiceTier, &i.ConversationStarters, ) return i, err } const updateAgentTaskSession = `-- name: UpdateAgentTaskSession :exec UPDATE agent_task_queue SET session_id = COALESCE($2, session_id), work_dir = COALESCE($3, work_dir) WHERE id = $1 AND ( status IN ('dispatched', 'running') OR (status = 'cancelled' AND session_id IS NULL) ) ` type UpdateAgentTaskSessionParams struct { ID pgtype.UUID `json:"id"` SessionID pgtype.Text `json:"session_id"` WorkDir pgtype.Text `json:"work_dir"` } // Pins the resume pointer mid-flight so a daemon crash leaves a usable // session_id/work_dir on the task row. No-op if the task is no longer // in dispatched/running. waiting_local_directory tasks have no session yet // so this query intentionally skips them. // // A row the user just cancelled accepts the pin too, but only while its // session slot is still empty (GH #6340). The pin is asynchronous — for Codex // it waits for the rollout to reach the store — so a cancel landing in that // window used to drop the session id for good, and the cancelled turn's // context with it. Filling an EMPTY slot on the run's own row is exactly what // the mid-flight pin is for; the `session_id IS NULL` guard is what keeps this // from being an overwrite, and completed/failed rows stay untouchable so a // straggler goroutine can never contradict a terminal report. func (q *Queries) UpdateAgentTaskSession(ctx context.Context, arg UpdateAgentTaskSessionParams) error { _, err := q.db.Exec(ctx, updateAgentTaskSession, arg.ID, arg.SessionID, arg.WorkDir) return err }