Action Cards: Approve, Edit, or Reject Agent Actions Without Leaving Chat
Action Cards: Approve, Edit, or Reject Agent Actions Without Leaving Chat
Starting with v1.0.35, Sidekick surfaces proposed agent actions as interactive Action Cards directly inside your chat conversation. When the AI decides it should send an email, file a GitHub issue, book a meeting, or post a message on your behalf, you see a structured preview of exactly what it intends to do — and you decide what happens next.
How It Works
- You give Sidekick a task (or it picks one up autonomously from your connected services).
- Before executing, the agent emits an Action Card into the chat thread at the point in the conversation where the action was proposed.
- You review the preview, read the agent's reasoning, and choose: Approve, Edit, or Reject.
- The card updates in place — no page navigation, no separate approvals inbox.
The Card Interface
Per-Type Previews
Each Action Card shows a structured preview tailored to the action type:
| Action Type | Preview Fields |
|---|---|
| To, Subject, Body | |
| Calendar event | Title, Time, Attendees |
| GitHub issue | Repository, Issue Title, Description |
| Message | Platform, Recipient, Body |
| Generic | Action summary |
Controls
- Approve — confirms the action and queues it for execution. The card transitions to Executing… and then to Done (or Failed) once the adapter runs.
- Edit — opens an inline form with type-aware fields populated from the proposed action. Modify any field, save, and then approve. Your edits are stored in
editedPayloadso the original proposal is preserved for the audit trail. - Reject — dismisses the action. The card transitions to Dismissed and the action is not executed.
Agent Reasoning
Each card has an expandable Reasoning section showing why the agent proposed the action. This is especially useful in supervised mode when the agent is managing a high volume of tasks.
Status Badges
| Badge | Meaning |
|---|---|
| ⚡ Awaiting Approval (pulse) | Card is pending your decision |
| Executing… | Approved, adapter running |
| ✅ Done | Action completed successfully |
| ❌ Failed | Action ran but encountered an error |
| Dismissed | You rejected the action |
When an action produces a result (for example, a newly filed GitHub issue), a View link appears on the card pointing directly to the resource.
Pending Actions Badge
The chat header shows a ShieldCheck badge with a count of how many actions are currently awaiting your approval. The count updates in real time as you approve or reject cards.
Delivery: Real-Time and Polling
Action Cards reach the chat interface through two complementary channels:
- SSE (Server-Sent Events) — cards are pushed into the stream immediately when the agent emits them during a chat response. They appear at the correct position in the conversation timeline without any delay.
- 15-second polling — a background query fetches all
pendingcards every 15 seconds. This catches cards created outside the active chat session (e.g. from an autonomous background task) and removes cards that are no longer pending.
Autonomy Modes
Action Cards behave differently depending on your configured autonomy level:
manual and supervised
A card is created with status pending. The action does not run until you approve it. This is the default for most users and integrations.
autonomous
The action executes immediately without waiting for your approval. A card is still created, but its initial status is executed — it acts as an audit record so you can see what the agent did and when.
Act-then-report
If the agent has already executed an action before card creation (e.g. during a background run), the card is created with alreadyExecuted: true and status executed. Same audit behaviour as autonomous mode.
tRPC API Reference
If you are building on top of Sidekick or writing custom skills, you can interact with Action Cards programmatically through the actionCards tRPC router.
actionCards.list
Returns a paginated list of Action Cards, optionally filtered by status.
const { items, nextCursor } = await trpc.actionCards.list.query({
limit: 20,
status: "pending", // "pending" | "approved" | "rejected" | "executed" | "failed"
cursor: undefined, // pass nextCursor for pagination
});
actionCards.byId
Fetch a single card by its ID.
const card = await trpc.actionCards.byId.query({ id: "card_abc123" });
actionCards.approve
Approve a pending card. Transitions status pending → approved and logs to agentDecisions.
await trpc.actionCards.approve.mutate({ id: "card_abc123" });
actionCards.reject
Reject a pending card. Transitions status pending → rejected and logs to agentDecisions.
await trpc.actionCards.reject.mutate({ id: "card_abc123" });
actionCards.edit
Save a modified payload before approving. The original proposed action is preserved; the edited version is stored in editedPayload.
await trpc.actionCards.edit.mutate({
id: "card_abc123",
editedPayload: { subject: "Updated subject line" },
});
actionCards.markExecuted
Mark an approved card as executed (or failed) after the adapter has run.
await trpc.actionCards.markExecuted.mutate({
id: "card_abc123",
status: "executed", // or "failed"
executionResult: { url: "https://github.com/org/repo/issues/42" },
executionError: null,
});
actionCards.stats
Returns counts grouped by status — useful for badge rendering or dashboards.
const stats = await trpc.actionCards.stats.query();
// { pending: 3, approved: 1, rejected: 0, executed: 14, failed: 1 }
Database Schema
Action Cards are persisted in the actionCards table:
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
action | jsonb | The UnifiedAction object describing what to do |
preview | jsonb | Per-type structured preview shown in the UI |
agentReasoning | text | Why the agent proposed this action |
status | action_card_status | pending | approved | rejected | executed | failed |
editedPayload | jsonb | User-modified fields (null if unedited) |
executionResult | jsonb | Adapter result including optional URL |
executionError | text | Error message if execution failed |
resolvedAt | timestamp | When approved or rejected |
executedAt | timestamp | When the adapter completed execution |