Files
Claude 2ce16d8842 Merge origin/main into claude/project-thread-qefpx3
Resolves the UpdateAgent conflict in handler/agent.go by keeping both the
connector_service_allowlist write and main's runtime-binding transaction,
renumbers the open-connector migrations to 590-596 because main took 583/584,
and regenerates sqlc.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
2026-10-21 05:56:59 +00:00

9259 lines
346 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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, acknowledged.runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
); 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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
)
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, task.runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, queued.runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
)
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, connector_service_allowlist
`
// 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,
&i.ConnectorServiceAllowlist,
)
return i, err
}
const clearAgentConnectorServiceAllowlist = `-- name: ClearAgentConnectorServiceAllowlist :one
UPDATE agent SET connector_service_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, connector_service_allowlist
`
// Explicit NULL-clear for connector_service_allowlist; see
// ClearAgentComposioToolkitAllowlist for why COALESCE cannot do this.
func (q *Queries) ClearAgentConnectorServiceAllowlist(ctx context.Context, id pgtype.UUID) (Agent, error) {
row := q.db.QueryRow(ctx, clearAgentConnectorServiceAllowlist, 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
// 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
// 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,
&i.ConnectorServiceAllowlist,
)
return i, err
}
const clearTaskConnectorHandles = `-- name: ClearTaskConnectorHandles :exec
UPDATE agent_task_queue SET runtime_connector_handles = NULL
WHERE id = $1
`
// Called after a task's per-provider resources were released.
func (q *Queries) ClearTaskConnectorHandles(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, clearTaskConnectorHandles, id)
return 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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,
connector_service_allowlist
) 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'),
$21::text[]
)
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, connector_service_allowlist
`
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"`
ConnectorServiceAllowlist []string `json:"connector_service_allowlist"`
}
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,
arg.ConnectorServiceAllowlist,
)
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, runtime_connector_handles,
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,
CASE
WHEN COALESCE($12::text, '') <> ''
THEN jsonb_build_object('head_sha', $12::text)
ELSE NULL
END,
$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, runtime_connector_handles
`
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"`
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"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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, preserving pre-TEN-356 behavior for
// issues with no linked PR. 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.OriginatorUserID,
arg.AccountableUserID,
arg.RuntimeMcpOverlay,
arg.RuntimeConnectedApps,
arg.RuntimeConnectorHandles,
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles,
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,
$24,
-- 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($25::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, runtime_connector_handles
`
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"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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.RuntimeConnectorHandles,
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles,
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, $4,
'direct_human', p.id, $5
FROM agent_task_queue p
WHERE p.id = $6
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, runtime_connector_handles
`
type CreateManualQuickCreateRetryTaskParams struct {
ActorUserID pgtype.UUID `json:"actor_user_id"`
RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"`
RuntimeConnectedApps []byte `json:"runtime_connected_apps"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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.RuntimeConnectorHandles,
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles,
originator_source, trigger_evidence_kind, trigger_evidence_ref_id,
id
)
SELECT
$1, $2, NULL, 'queued', $3, $4,
$5,
$6,
$7,
$8, $9,
$10,
$11,
$12,
COALESCE($13::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, runtime_connector_handles
`
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"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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.RuntimeConnectorHandles,
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles,
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, $6,
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($7::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, runtime_connector_handles
`
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"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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.RuntimeConnectorHandles,
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,
&i.RuntimeConnectorHandles,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, t.runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, retry.runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
)
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, atq.runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
)
return i, err
}
const getAgentTaskStatus = `-- name: GetAgentTaskStatus :one
SELECT atq.status, a.workspace_id
FROM agent_task_queue atq
JOIN agent a ON a.id = atq.agent_id
WHERE atq.id = $1
`
type GetAgentTaskStatusRow struct {
Status string `json:"status"`
WorkspaceID pgtype.UUID `json:"workspace_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.
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)
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')
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-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 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist FROM agent
WHERE runtime_id = $1
AND owner_id IS DISTINCT FROM $2
ORDER BY name ASC
`
type ListNonOwnerAgentsByRuntimeParams struct {
RuntimeID pgtype.UUID `json:"runtime_id"`
RuntimeOwnerID pgtype.UUID `json:"runtime_owner_id"`
}
// Returns every agent bound to a runtime whose owner differs from its owner,
// including hidden Builder execution carriers. This is the dependency plan shown before a public
// runtime is made private: owner-owned agents keep their binding, while every
// teammate-owned agent loses machine access.
func (q *Queries) ListNonOwnerAgentsByRuntime(ctx context.Context, arg ListNonOwnerAgentsByRuntimeParams) ([]Agent, error) {
rows, err := q.db.Query(ctx, listNonOwnerAgentsByRuntime, arg.RuntimeID, arg.RuntimeOwnerID)
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,
&i.ConnectorServiceAllowlist,
); 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, connector_service_allowlist FROM agent
WHERE runtime_id = $1
AND owner_id IS DISTINCT FROM $2
ORDER BY id
FOR UPDATE NOWAIT
`
type ListNonOwnerAgentsByRuntimeForUpdateParams struct {
RuntimeID pgtype.UUID `json:"runtime_id"`
RuntimeOwnerID pgtype.UUID `json:"runtime_owner_id"`
}
// 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)
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,
&i.ConnectorServiceAllowlist,
); 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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, atq.runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, atq.runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTerminalTasksWithConnectorHandles = `-- name: ListTerminalTasksWithConnectorHandles :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, runtime_connector_handles FROM agent_task_queue
WHERE runtime_connector_handles IS NOT NULL
AND status IN ('completed', 'failed', 'cancelled')
ORDER BY id
LIMIT 200
`
// Reaper input: terminal tasks whose connector resources were not released
// (best-effort release failed or the process died). Served by the partial
// index on runtime_connector_handles.
func (q *Queries) ListTerminalTasksWithConnectorHandles(ctx context.Context) ([]AgentTaskQueue, error) {
rows, err := q.db.Query(ctx, listTerminalTasksWithConnectorHandles)
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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
); 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, atq.runtime_connector_handles 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, latest.runtime_connector_handles 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, atq.runtime_connector_handles
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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist 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,
&i.ConnectorServiceAllowlist,
)
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, runtime_connector_handles 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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
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 StartAgentTask
// 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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
)
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,
runtime_connector_handles = $12
WHERE id = (
SELECT t.id FROM agent_task_queue t
WHERE t.context->>'wakeup_id' IS NULL AND t.issue_id = $13
AND t.agent_id = $14
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($15::text, '') = ''
OR t.context->>'head_sha' = $15::text
)
ORDER BY t.created_at DESC
LIMIT 1
)
RETURNING id, coalesced_comment_ids
`
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"`
NewRuntimeConnectorHandles []byte `json:"new_runtime_connector_handles"`
IssueID pgtype.UUID `json:"issue_id"`
AgentID pgtype.UUID `json:"agent_id"`
HeadSha pgtype.Text `json:"head_sha"`
}
type MergeCommentIntoPendingTaskRow struct {
ID pgtype.UUID `json:"id"`
CoalescedCommentIds []pgtype.UUID `json:"coalesced_comment_ids"`
}
// 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) (MergeCommentIntoPendingTaskRow, 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.NewRuntimeConnectorHandles,
arg.IssueID,
arg.AgentID,
arg.HeadSha,
)
var i MergeCommentIntoPendingTaskRow
err := row.Scan(&i.ID, &i.CoalescedCommentIds)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
); 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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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
)
)
)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
return i, err
}
const reclaimStaleDispatchedTasksForRuntimes = `-- name: ReclaimStaleDispatchedTasksForRuntimes :many
UPDATE agent_task_queue
SET dispatched_at = now(),
prepare_lease_expires_at = now() + make_interval(secs => $1::double precision)
WHERE id IN (
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
)
)
)
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
)
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, runtime_connector_handles
`
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.
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,
&i.RuntimeConnectorHandles,
); 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, runtime_connector_handles
`
// 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,
&i.RuntimeConnectorHandles,
); 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, a.connector_service_allowlist
`
// 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,
&i.ConnectorServiceAllowlist,
)
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, runtime_connector_handles
`
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,
&i.RuntimeConnectorHandles,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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 setDeferredChannelIssueTaskRuntimeOverlay = `-- name: SetDeferredChannelIssueTaskRuntimeOverlay :execrows
UPDATE agent_task_queue
SET runtime_mcp_overlay = $1,
runtime_connected_apps = $2,
runtime_connector_handles = $3
WHERE id = $4
AND status = 'deferred'
AND context->>'channel_issue_media_pending' = 'true'
AND trigger_comment_id IS NULL
AND originator_user_id IS NOT DISTINCT FROM $5::uuid
`
type SetDeferredChannelIssueTaskRuntimeOverlayParams struct {
RuntimeMcpOverlay []byte `json:"runtime_mcp_overlay"`
RuntimeConnectedApps []byte `json:"runtime_connected_apps"`
RuntimeConnectorHandles []byte `json:"runtime_connector_handles"`
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.RuntimeConnectorHandles,
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 startAgentTask = `-- name: StartAgentTask :one
UPDATE agent_task_queue
SET status = 'running',
started_at = now(),
wait_reason = NULL,
prepare_lease_expires_at = NULL
WHERE agent_task_queue.id = $1 AND agent_task_queue.status IN ('dispatched', '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, runtime_connector_handles
`
// Legacy single-winner transition; generation-aware callers lock the claim first.
// Transitions a task to running. Accepts either 'dispatched' (the normal
// claim → run flow) or 'waiting_local_directory' (the daemon held the row in
// a wait state while another task owned the local_directory path lock; once
// the lock was acquired the daemon flips here). wait_reason is cleared on
// the transition so a future read can't conflate "currently waiting" with
// "previously waited".
func (q *Queries) StartAgentTask(ctx context.Context, id pgtype.UUID) (AgentTaskQueue, error) {
row := q.db.QueryRow(ctx, startAgentTask, 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,
&i.RuntimeConnectorHandles,
)
return i, err
}
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),
connector_service_allowlist = COALESCE($21::text[], connector_service_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, connector_service_allowlist
`
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"`
ConnectorServiceAllowlist []string `json:"connector_service_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,
arg.ConnectorServiceAllowlist,
)
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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, connector_service_allowlist
`
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,
&i.ConnectorServiceAllowlist,
)
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
}