From e0e81bbdaae37353803d47fa59a36d0472f8146d Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Sat, 14 Mar 2026 07:04:47 +0000 Subject: feat: persist agent assignment before task execution - Add UpdateTaskAgent to Store interface and DB implementation - Call UpdateTaskAgent in Pool.execute to persist assigned agent/model to database before the runner starts - Update runTask in app.js to pass selected agent as query param Co-Authored-By: Claude Sonnet 4.6 --- internal/executor/executor.go | 6 ++++++ internal/storage/db.go | 12 ++++++++++++ web/app.js | 11 ++++++++--- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/internal/executor/executor.go b/internal/executor/executor.go index bf209b7..475d150 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -28,6 +28,7 @@ type Store interface { UpdateTaskQuestion(taskID, questionJSON string) error UpdateTaskSummary(taskID, summary string) error AppendTaskInteraction(taskID string, interaction task.Interaction) error + UpdateTaskAgent(id string, agent task.AgentConfig) error } // LogPather is an optional interface runners can implement to provide the log @@ -435,6 +436,11 @@ func (p *Pool) execute(ctx context.Context, t *task.Task) { } } + // Persist the assigned agent (and model) to the database before running. + if err := p.store.UpdateTaskAgent(t.ID, t.Agent); err != nil { + p.logger.Error("failed to persist agent config", "error", err, "taskID", t.ID) + } + agentType := t.Agent.Type if agentType == "" { agentType = "claude" diff --git a/internal/storage/db.go b/internal/storage/db.go index b8a7085..043009c 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -251,6 +251,18 @@ func (s *DB) ResetTaskForRetry(id string) (*task.Task, error) { return t, nil } +// UpdateTaskAgent updates only the agent configuration of a task. +func (s *DB) UpdateTaskAgent(id string, agent task.AgentConfig) error { + configJSON, err := json.Marshal(agent) + if err != nil { + return fmt.Errorf("marshaling agent config: %w", err) + } + now := time.Now().UTC() + _, err = s.db.Exec(`UPDATE tasks SET config_json = ?, updated_at = ? WHERE id = ?`, + string(configJSON), now, id) + return err +} + // RejectTask sets a task's state to PENDING and stores the rejection comment. func (s *DB) RejectTask(id, comment string) error { now := time.Now().UTC() diff --git a/web/app.js b/web/app.js index 6bcdf57..bca41fa 100644 --- a/web/app.js +++ b/web/app.js @@ -456,8 +456,11 @@ function renderAllPanel(tasks) { // ── Run action ──────────────────────────────────────────────────────────────── -async function runTask(taskId) { - const res = await fetch(`${API_BASE}/api/tasks/${taskId}/run`, { method: 'POST' }); +async function runTask(taskId, agent) { + const url = agent && agent !== 'auto' + ? `${API_BASE}/api/tasks/${taskId}/run?agent=${agent}` + : `${API_BASE}/api/tasks/${taskId}/run`; + const res = await fetch(url, { method: 'POST' }); if (!res.ok) { let msg = `HTTP ${res.status}`; try { const body = await res.json(); msg = body.error || body.message || msg; } catch {} @@ -467,6 +470,8 @@ async function runTask(taskId) { } async function handleRun(taskId, btn, footer) { + const agentSelector = document.getElementById('select-agent'); + const agent = agentSelector ? agentSelector.value : 'auto'; btn.disabled = true; btn.textContent = 'Queuing…'; @@ -475,7 +480,7 @@ async function handleRun(taskId, btn, footer) { if (prev) prev.remove(); try { - await runTask(taskId); + await runTask(taskId, agent); // Refresh active panel so state flips to QUEUED await poll(); } catch (err) { -- cgit v1.2.3