Maniac Docs
Channels

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 handleApprovalCallbackManiac.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-drivenChatToolset({ 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:

  1. Reads RunCheckpoint and pending_approvals from the terminal result
  2. Calls renderApprovalCard with agent id, thread id, and platform thread reference
  3. Posts the card via Chat SDK's adapter

The card embeds metadata keys the callback handler needs:

  • maniac.checkpointId
  • maniac.pendingId
  • maniac.agentId
  • maniac.threadId
  • maniac.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 thread

CheckpointNotPendingError 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

APIUse 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.

On this page