Task comments & amendments
Migration: 0010_task_comments_amendments.sql Core module: packages/core/src/task/comments.ts MCP tools: apps/mcp/src/tools/comments/
Why typed, not free-form
Drobek's single-write-path invariant forbids unstructured commentary — otherwise people would "fix" tasks via Slack-style comments instead of an MCP call. Instead, agents pick a kind:
| Kind | Blocks task? | Rationale required | Typical actor |
|---|---|---|---|
question | no | no | agent before claim / human reviewer |
blocker | yes | yes | agent or human |
note | no | no | agent in_progress |
reference | no | no | anyone |
nack | proposes cancellation | yes | reviewer |
Open blocker comments hide a task from get_next_actionable regardless of its state. The claim_task tool consults the same flag.
Rate limit
Max 3 open blocker comments per agent per task. Enforced inside addComment before insert (a quick count(*) keyed by the partial index task_comments_open_blockers_idx). Over the cap → task.invariant_violated.
Shallow threading
parent_comment_id is allowed only when the parent is itself top-level (max 1 reply depth). Deeper threads = signal the discussion belongs in an amendment.
Amendments
task_amendments is a separate workflow: an agent proposes a structured patch (title / description / contracts / priority), older proposed amendments for the same task become superseded, a human (only) approves. On approve:
- Snapshot
tasksrow intoapplied_snapshot(rollback artifact). - Apply patch fields with an
UPDATE. - If the patch touches a contract and the task is claimed, the claim is
released so the agent stops work. The pub/sub event carries claimDropped: true.
Rejection requires a decision_note. Rejected/superseded states are read-only audit rows.
MCP tools (scopes)
| Tool | Scope |
|---|---|
comment_on_task | tasks:write |
resolve_comment | tasks:write |
list_task_comments | tasks:read |
propose_task_amendment | tasks:write |
list_pending_amendments | tasks:read |
Approvals (approveAmendment / rejectAmendment) are user-only and currently exposed only via the core helpers — the UI flow lands in task 38.
Pub/sub events
All events fire on pubsub:org:{orgId}:tasks after commit:
task.comment.added—{ taskId, commentId, kind, authorKind, authorId }task.comment.resolved—{ taskId, commentId, decision }task.amendment.proposed—{ taskId, amendmentId, patchKeys }task.amendment.applied—{ taskId, amendmentId, claimDropped }task.amendment.rejected—{ taskId, amendmentId }
Decision tree (when to use which kind)
Is the task spec broken / impossible? → `blocker` (with rationale)
Should the task not exist at all? → `nack`
You don't understand a requirement? → `question`
You found something useful while doing it? → `note`
Want to link to another task / RFC / knowledge? → `reference`
Need to actually CHANGE the spec? → `propose_task_amendment`If you're tempted to write more than two sentences in a body, the answer is probably an amendment, not a comment.