Orchestrator: route open questions back to originator before approval (#11)#15
Conversation
- Architect emits a machine-parseable open_questions_json block; parsed and persisted to a new task_questions table (migration 0005). - New awaiting_answers lifecycle state: tasks with open questions wait for the originator instead of going straight to awaiting_approval. - POST /api/tasks/:id/questions records answers and enqueues a forge:answers re-plan job; the worker re-runs the architect with answers in context and then moves to awaiting_approval (or back to awaiting_answers on new questions). - Task page renders a QuestionsPanel (wired to initial load + SSE) and gates Approve until questions are resolved; integrates with the Approve/Change plan/Restart actions. - Fix: extend worker QueueName with 'answers'. Add unit tests for the parser and guard tests for the questions route. Closes #11 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 824c58419d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await updateTaskStatus(taskId, openQuestionCount > 0 ? 'awaiting_answers' : 'awaiting_approval') | ||
| } catch (err) { | ||
| const message = errorMessage(err) | ||
| await updateTaskStatus(taskId, 'failed', message) |
There was a problem hiding this comment.
Preserve retryable status for answer re-plans
When runArchitect throws during an answered-question re-plan, this catch always marks the task failed before rethrowing. The answers queue in runtime.ts will schedule a retry when attempts remain, but the retried job immediately exits at the task.status !== 'awaiting_answers' guard above because the task is now failed, so transient provider/network errors after answers are submitted are never actually retried. Keep the task retryable, e.g. restore awaiting_answers for non-final attempts or pass final-attempt state into this path.
Useful? React with 👍 / 👎.
| // previous round should not linger. | ||
| await db.delete(taskQuestions).where(eq(taskQuestions.taskId, taskId)) | ||
|
|
||
| if (questions.length === 0) return 0 |
There was a problem hiding this comment.
Notify clients when questions are cleared
When the follow-up architect run resolves all answered questions, this branch deletes the existing task_questions rows and returns without publishing any empty questions update. Clients that already received the previous questions:created event, or get it replayed from SSE history, keep rendering that stale question set because useTaskStream only clears questions when it receives a replacement event; the UI only matches the database after a full reload without replay. Publish a questions:created event with questions: [] after clearing the rows.
Useful? React with 👍 / 👎.
Closes #11.
What
Open questions raised by the architect are now sent back to the task originator and resolved before the plan can be approved.
open_questions_jsonfenced block;web/worker/open-questions.tsparses it (tolerant of malformed JSON), and questions are persisted to a newtask_questionstable (migration0005).awaiting_answersstate — a plan with unanswered questions moves the task toawaiting_answersinstead ofawaiting_approval(web/worker/orchestrator.ts,web/worker/task-state.ts).POST /api/tasks/[id]/questionsrecords answers and enqueues aforge:answersjob; the worker (AnswersQueue→processAnsweredQuestions) re-runs the architect with the answers in context, then moves toawaiting_approval(or back toawaiting_answersif new questions arise).QuestionsPanel(wired to initial load + the SSEquestions:created/questions:answeredevents) with answer inputs and resolved Q&A history; Approve stays gated until questions are resolved.Finishing work done on top of the WIP
The background subagent completed the backend/worker/migration and the
QuestionsPanelcomponent but stopped before wiring it up. This PR:QuestionsPanelinto the page (consumequestionsfromuseTaskStream, load initial questions from the task GET, render +onAnsweredrefresh);QueueNamewas missing'answers');Verification
npx tsc --noEmit✓npx eslint .✓npx vitest run→ 76 passed ✓0005_add_task_questions.sqlverified consistent with the Drizzle schema (columns, FKs, indexes).db:migratenot run here (no DB in this environment).Integration note
This branch and the already-merged item #5 (Approve / Change plan / Restart + prompt display) both touch
web/app/dashboard/tasks/[id]/page.tsx; the merge was reconciled here so both feature sets coexist.🤖 Generated with Claude Code