7 min read

Write Actions & Human-in-the-Loop

By default the AI assistant can read your data but not change it. The Write-Action Tools feature (shipped in v0.4.0) gives the assistant a set of team-admin tools that perform real mutations — inviting, removing, and re-roling members — with every call paused for explicit human approval before it runs. This is for owners and admins who want the assistant to do things, not just describe them, without ever handing it unsupervised write access.


What it does

The team-admin suite adds four write tools to the assistant. All four are gated to owner and admin roles — members never see them in the tool list, and the AI never offers them.

ToolActionButton variant
inviteTeamMemberSend a team invitationdefault
revokeTeamInvitationCancel a pending invitationdestructive
changeMemberRolePromote or demote a memberdefault
removeTeamMemberRemove a member from the teamdestructive

Each tool is declared with AI SDK v6’s needsApproval: true. When the model decides to call one, the turn pauses and an approval card is rendered inline in the chat instead of the action firing. Destructive tools (removeTeamMember, revokeTeamInvitation) render their Approve button with the destructive variant so a removal never looks like a routine confirm.

Every approved action is also tagged as AI-originated in the audit log, so you can always tell a manual change from an assistant-driven one.


How to use it

  1. Open the assistant as an owner or admin and ask for the change, e.g. “Invite jordan@acme.com as an admin” or “Remove the contractor account.”
  2. The model proposes the tool call and the chat shows an Approve / Deny card describing the exact arguments (who, what role, which invitation).
  3. Tap Approve to run it or Deny to skip it. For removeTeamMember and revokeTeamInvitation the Approve button is styled destructive.
  4. useChat auto-resumes the turn once your approvals resolve — no need to re-send a message. The assistant continues and reports the result.

If you ask as a member, the assistant simply won’t have these tools available and will say it can’t perform the action.


How it works

The pause/resume loop is pure AI SDK v6 wiring:

  • Tool side — each write tool sets needsApproval: true, so the SDK emits an approval request part instead of executing.
  • UI sideapproval-request-card.tsx renders that part as an inline card; chat-messages.tsx decides which calls get the destructive variant.
  • Resume sideuseChat is configured with:
useChat({
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
});

Once the last assistant message has a response for every pending approval, the turn is sent back automatically and the approved tools execute.

AI provenance on audit rows

When an approved write tool runs, it writes an audit row through the same service layer as a manual change, but with extra metadata so the action is traceable:

metadata: {
  triggered_by: 'ai',
  conversation_id,        // which chat
  tool_call_id,           // which specific tool call
  approved_by_user_id,    // the human who tapped Approve
}

The audit row’s userId is set to the human approver, not a bot identity — the person who clicked Approve owns the action. metadata.triggered_by === 'ai' is the flag you filter on to find assistant-driven changes.


Key files

PathResponsibility
src/lib/ai/tools.tsThe four write tools, their needsApproval flag, role gating, and auditContext wiring
src/components/ai-assistant/approval-request-card.tsxInline Approve / Deny card
src/components/ai-assistant/chat-messages.tsxRenders approval cards and picks the destructive button variant

How to extend

To add your own write tool, follow the same pattern rather than calling your database from the tool directly:

  1. Wrap an existing service function. Reuse the same function your routes call (e.g. a teamService method) so validation, RLS, and audit logging stay in one place.
  2. Set needsApproval: true. This is what forces the inline approval step. Skip it only for genuinely read-only tools.
  3. Gate by role. Only register the tool when the caller is an owner or admin, matching the team-admin suite, so members never receive it.
  4. Pass auditContext. Forward the conversation_id, tool_call_id, and approver’s user id so your audit row gets triggered_by: 'ai' and stays traceable.
  5. Pick the variant. If the action is destructive, mark it so chat-messages.tsx renders the destructive Approve button.

Keep tools thin: the tool decides what to do and asks for approval; the service function decides how, and the audit layer records who.

For the read-side counterparts — how the assistant retrieves context to decide on these actions — see Knowledge Base & RAG and Conversation Memory. For how tools are registered in the wider framework, see AI Development Framework.

Ready to build with VibeReady?

Get the full AI-native SaaS foundation with production infrastructure, AI development framework, and all integrations.

Get VibeReady — From $149