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.
| Tool | Action | Button variant |
|---|---|---|
inviteTeamMember | Send a team invitation | default |
revokeTeamInvitation | Cancel a pending invitation | destructive |
changeMemberRole | Promote or demote a member | default |
removeTeamMember | Remove a member from the team | destructive |
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
- 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.”
- The model proposes the tool call and the chat shows an Approve / Deny card describing the exact arguments (who, what role, which invitation).
- Tap Approve to run it or Deny to skip it. For
removeTeamMemberandrevokeTeamInvitationthe Approve button is styled destructive. useChatauto-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 side —
approval-request-card.tsxrenders that part as an inline card;chat-messages.tsxdecides which calls get the destructive variant. - Resume side —
useChatis 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
| Path | Responsibility |
|---|---|
src/lib/ai/tools.ts | The four write tools, their needsApproval flag, role gating, and auditContext wiring |
src/components/ai-assistant/approval-request-card.tsx | Inline Approve / Deny card |
src/components/ai-assistant/chat-messages.tsx | Renders 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:
- Wrap an existing service function. Reuse the same function your routes call (e.g. a
teamServicemethod) so validation, RLS, and audit logging stay in one place. - Set
needsApproval: true. This is what forces the inline approval step. Skip it only for genuinely read-only tools. - 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.
- Pass
auditContext. Forward theconversation_id,tool_call_id, and approver’s user id so your audit row getstriggered_by: 'ai'and stays traceable. - Pick the variant. If the action is destructive, mark it so
chat-messages.tsxrenders 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