Approval cards
Pause mutating chat tool calls and resume runs from interactive approve/deny cards on the platform thread.
Approval cards
Channels integrate with Maniac's HITL checkpoint flow. When a tool call requires approval, serveChannels renders an interactive card on the originating platform thread. Button clicks resolve through handleApprovalCallback → Maniac.resumeCheckpointStream.
Two approval paths
Policy-driven — set effect: "require_approval" on rules that match chat toolsets:
import { StaticPermissionPolicy } from "@maniac-ai/agents";
const policy = new StaticPermissionPolicy(
[{
id: "approve-chat-writes",
principal: "*",
scope: { toolset: "chat", arg_constraints: [] },
effect: "require_approval"
}],
{ allowed: true }
);Toolset-driven — ChatToolset({ requireApproval: true }) enforces a runner-level requires_approval tag gate on mutating chat tools, pausing writes even when no policy matches.
Use both together in production bots so the model explains intent before posting.
Card rendering
When chatStream yields status="paused", serveChannels:
- Reads
RunCheckpointandpending_approvalsfrom the terminal result - Calls
renderApprovalCardwith agent id, thread id, and platform thread reference - Posts the card via Chat SDK's adapter
The card embeds metadata keys the callback handler needs:
maniac.checkpointIdmaniac.pendingIdmaniac.agentIdmaniac.threadIdmaniac.platformThreadId
Approve and deny buttons use action ids maniac.approve.<pendingId> and maniac.deny.<pendingId>.
Callback flow
User clicks Approve
→ handleApprovalCallback(payload)
→ parseApprovalCallback extracts checkpoint + decision
→ buildApprovalResponse → ApprovalResponse[]
→ Maniac.resumeCheckpointStream(checkpointId, responses)
→ stream continues; final reply posts to the threadCheckpointNotPendingError is thrown when a checkpoint is no longer pending (already resolved or stale claim). Re-pause after resolve creates a fresh checkpoint.
Plain-text fallback
Set cards: false on ChannelsOptions to post proposed args as plain text instead of interactive buttons. The model's normal loop can then continue without a card click — useful on platforms without native button support.
Dedup
Approval callbacks use a 15-minute TTL dedup store (APPROVAL_DEDUP_TTL_SECONDS) so double-clicks or Slack retries do not resume twice.
Resume APIs
| API | Use case |
|---|---|
Maniac.resumeCheckpoint(id, responses) | Blocking resume |
Maniac.resumeCheckpointStream(id, responses) | Stream envelopes through the rest of the run |
runAgentResumeStream(spec, checkpoint, responses) | Lower-level resume without the app |
Channels always prefer the streaming resume path so partial assistant text can post incrementally after approval.
Batched delegated tools
When a paused run carries sub_actions on PendingApproval (batched delegated_tool invocations), the approval card notes the batch count. ApprovalResponse.sub_decisions answers each sub-action individually on resume.