Skip to content

Withheld drafts are indistinguishable from delivered replies (replayed as assistant history, no DB marker) #148

Description

@NathanTarbert

When the groundedness gate withholds an ungrounded draft, the reporter gets safe replacement copy and a human is paged (#143). But nothing in the database records that the draft was withheld, and two downstream readers treat it as a delivered reply.

1. The withheld draft re-enters the model's context as an assistant turn

packages/outpost/queue/src/handlers/ai-response.ts persists pipelineResult.response — the raw draft — as a type: 'BOT' message. That is intentional: the human handling the escalation needs the draft to work from.

The consequence is not intentional. The same handler builds conversationHistory from prior messages, mapping BOT rows to assistant turns. So on the ticket's next AI response, the fabrication the reporter never saw is fed back to the model as something it apparently already said — and the model has no way to know it was withheld. A "bug confirmed" draft that was suppressed becomes prior context asserting the bug is confirmed.

2. suggestedResponse drops everything past the first Discord chunk

Same handler stores pipelineResult.formatted.text into ticket.suggestedResponse. For a long Discord reply, formatted.text is only parts[0] — the remaining parts live in formatted.parts. The human's editor and the shadow-mode record therefore get a truncated reply with no indication anything was dropped.

3. No column marks a message as withheld

Message carries confidenceScore and confidenceLevel; nothing records suppressed or the groundedness reasons. Suppression exists only in worker logs and the escalation job's reason string. So the dashboard cannot show "this draft was withheld for fabrication", analytics cannot count how often the gate fires, and #145 (the reply editor prefilling the disclaimer) cannot be fixed cleanly without it.

Suggested shape

  • Persist the suppression verdict — a suppressed boolean and the groundedness reasons on Message, which needs a Prisma migration.
  • Exclude withheld drafts from conversationHistory construction, or mark them so they are excluded.
  • Use formatted.parts where present rather than assuming text is the whole reply.

Relates to #145 (reply editor prefills the AI disclaimer instead of the draft) — both are consequences of suggestedResponse carrying published copy while response carries the draft, with nothing distinguishing the two states.

Found by the CR loop on #143 (round 3), four reviewers independently.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: aiAI response pipeline, groundedness, confidenceroadmapTracked on the Outpost roadmaproadmap: nextRoadmap horizon: after launch path clears

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions