Atlas
Guides

Actions Framework

Enable approval-gated write operations like sending emails and creating JIRA tickets from the agent.

The action framework lets the Atlas agent perform write operations -- sending emails, creating JIRA tickets, and more -- with configurable approval gates. Actions go through a request-approve-execute lifecycle that gives humans control over what the agent does.

Prerequisites

  • Authentication enabled (any mode except none)
  • Internal database (DATABASE_URL) recommended for persistent action log (in-memory fallback available but lost on restart)
  • For email actions: RESEND_API_KEY
  • For JIRA actions: JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN

Enable

ATLAS_ACTIONS_ENABLED=true

Actions require authentication (any mode except none) and an internal database (DATABASE_URL) for the action log.


Approval Modes

Each action has an approval mode that controls whether human approval is required before execution.

ModeBehaviorWho can approve
autoExecute immediately, no approval neededN/A
manualQueue for approvalanalyst or admin
admin-onlyQueue for approval, admin requiredadmin only

In admin-only mode, the user who requested the action cannot approve their own request (separation of duties).


Action Lifecycle

Agent requests action → pending

         ┌────────────────┼────────────────┐
         ↓                ↓                ↓
    auto_approved      approved          denied
         ↓                ↓
      executed         executed
         ↓                ↓
  success/failed/    success/failed/
    timed_out          timed_out
         ↓                ↓
    rolled_back*     rolled_back*

* reversible actions only
  • auto mode: pendingauto_approvedexecuted (or failed / timed_out)
  • manual / admin-only mode: pendingapproved / deniedexecuted (or failed / timed_out)
  • rollback (reversible actions): executed / auto_approvedrolled_back

When execution exceeds the configured timeout, the action transitions to timed_out instead of remaining in-flight. The timeout duration is logged in the audit trail.


Rollback

Reversible actions can be undone after execution. When an action plugin declares reversible: true and its execute handler returns rollbackInfo, the action can be rolled back via the API or the admin console.

How Rollback Works

  1. The action plugin declares reversible: true in its PluginAction definition
  2. The execute handler returns a rollbackInfo object containing the method and parameters needed to undo the action
  3. Atlas stores rollbackInfo in the action log alongside the execution result
  4. An admin triggers rollback via the API or admin console
  5. The action transitions to rolled_back status via compare-and-swap (CAS)
  6. The rollback handler is dispatched using the stored rollbackInfo

Status Transition

Rollback is only available from the executed or auto_approved statuses:

executed / auto_approved → rolled_back

Actions in any other status (pending, denied, failed, timed_out, or already rolled_back) cannot be rolled back.

API

# Rollback an executed action
curl -X POST http://localhost:3001/api/v1/actions/<id>/rollback \
  -H "Authorization: Bearer <key>"

Request: No body required.

Response (success):

{
  "id": "action-uuid",
  "status": "rolled_back",
  "action_type": "jira:create",
  "target": "PROJ-123",
  "summary": "Created JIRA issue PROJ-123",
  "rollback_info": { "method": "transition", "params": { "issueKey": "PROJ-123" } },
  "error": null
}

Response (rollback handler error):

{
  "id": "action-uuid",
  "status": "rolled_back",
  "action_type": "jira:create",
  "target": "PROJ-123",
  "summary": "Created JIRA issue PROJ-123",
  "rollback_info": { "method": "transition", "params": { "issueKey": "PROJ-123" } },
  "error": "JIRA API returned 403",
  "warning": "Rollback status updated but the rollback handler reported an error. The side-effect may not have been reversed."
}

When the rollback handler fails, the status still transitions to rolled_back but the response includes a warning and error field so operators know the side-effect may not have been reversed.

Admin Console

Admins can rollback actions from the admin console at /admin/actions. Executed reversible actions show a Rollback button.

Error Cases

ScenarioHTTP StatusError Code
No internal database configured404not_available
Invalid action ID format400invalid_request
Action not found404not_found
Action not reversible (no rollback_info)409conflict
Action already rolled back or not in rollbackable state409conflict
Insufficient role403forbidden
Unexpected server error500internal_error
Rollback handler failed200Response includes warning and error fields

Declaring Actions as Reversible

In your action plugin, set reversible: true and return rollbackInfo from the execute handler:

import { z } from "zod";
import { tool } from "@useatlas/plugin-sdk/ai";
import type { PluginAction } from "@useatlas/plugin-sdk";

const action: PluginAction = {
  name: "createJiraIssue",
  description: "Create a JIRA issue",
  tool: tool({
    description: "Create a JIRA issue from analysis findings",
    inputSchema: z.object({ summary: z.string(), project: z.string() }),
    execute: async ({ summary, project }) => {
      const issue = await createIssue({ summary, project });
      return {
        issueKey: issue.key,
        url: issue.url,
        // Return rollbackInfo so the action can be undone
        rollbackInfo: {
          method: "transition",
          params: { issueKey: issue.key },
        },
      };
    },
  }),
  actionType: "jira:create",
  reversible: true,        // Enables the rollback button/API
  defaultApproval: "manual",
  requiredCredentials: ["JIRA_API_TOKEN"],
};

The rollbackInfo.method and rollbackInfo.params are stored in the action log and passed to the rollback handler when triggered.

SDK

const result = await atlas.rollbackAction("action-uuid");
console.log(result.status);  // "rolled_back"
if (result.warning) {
  console.warn(result.warning); // Handler error — side-effect may persist
}

See SDK Reference for the full method signature.


Built-in Actions

Email (email:send)

Send email reports via Resend.

Default approval: admin-only

Required credentials:

  • RESEND_API_KEY -- Resend API token

Optional:

  • ATLAS_EMAIL_FROM -- From address (default: Atlas <atlas@notifications.useatlas.dev>)
  • ATLAS_EMAIL_ALLOWED_DOMAINS -- Comma-separated domain whitelist for recipients

Input:

  • to -- Recipient email address(es)
  • subject -- Email subject line
  • body -- Email body (HTML)

JIRA (jira:create)

Create JIRA issues from data insights.

Default approval: manual

Required credentials:

  • JIRA_BASE_URL -- JIRA instance URL (e.g., https://myco.atlassian.net)
  • JIRA_EMAIL -- Authentication email
  • JIRA_API_TOKEN -- API token

Optional:

  • JIRA_DEFAULT_PROJECT -- Default project key when not specified

Input:

  • summary -- Issue title (max 255 chars)
  • description -- Issue description
  • project -- Project key (optional, falls back to JIRA_DEFAULT_PROJECT)
  • labels -- Optional labels

This action is reversible -- on rollback, Atlas transitions the created issue to "Closed" (best-effort, depends on JIRA workflow).


Configuration

Via Environment Variables

ATLAS_ACTIONS_ENABLED=true
ATLAS_ACTION_TIMEOUT=300000   # 5 minute execution timeout (ms)

Via atlas.config.ts

Override approval modes, role requirements, and timeouts per action:

import { defineConfig } from "@atlas/api/lib/config";

export default defineConfig({
  actions: {
    // Default approval mode and timeout for all actions unless overridden
    defaults: {
      approval: "manual",
      timeout: 300000,            // 5 minute default (ms)
    },
    // Per-action overrides — key is the action type identifier
    "email:send": {
      approval: "admin-only",       // Only admins can approve email sends
      requiredRole: "admin",
      timeout: 30000,               // 30 second timeout for email sends
      credentials: {
        RESEND_API_KEY: { env: "RESEND_API_KEY" },  // Validated at startup
      },
    },
    "jira:create": {
      approval: "manual",           // Any analyst or admin can approve
      requiredRole: "analyst",
    },
  },
});

Timeout resolution: With a config file, per-action timeout overrides defaults.timeout. The ATLAS_ACTION_TIMEOUT env var is only used when no config file is present (it populates defaults.timeout automatically). When no timeout is configured, actions run without a time limit.

See Configuration for the full config schema.


Approving and Denying Actions

Web UI

Pending actions appear in the chat UI with Approve and Deny buttons. Admins can also manage actions from the Admin Console at /admin/actions.

API

# Approve a pending action
curl -X POST http://localhost:3001/api/v1/actions/<id>/approve \
  -H "Authorization: Bearer <key>"

# Deny with an optional reason (recorded in the action log)
curl -X POST http://localhost:3001/api/v1/actions/<id>/deny \
  -H "Authorization: Bearer <key>" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Not relevant"}'

Slack

When using the Slack integration, pending actions show as ephemeral messages with Approve and Deny buttons visible only to the requesting user.


API Endpoints

MethodPathDescription
GET/api/v1/actionsList actions (filter by status, default: pending)
GET/api/v1/actions/:idGet action details
POST/api/v1/actions/:id/approveApprove a pending action
POST/api/v1/actions/:id/denyDeny a pending action
POST/api/v1/actions/:id/rollbackRollback an executed reversible action

Role Requirements

RoleCan request actionsCan approve manualCan approve admin-only
viewerYesNoNo
analystYesYesNo
adminYesYesYes

See Authentication for role configuration.


Building Custom Actions

Action plugins follow the same pattern as built-in actions. See Plugin Authoring Guide for the action plugin type, which registers custom tools with approval gates and credential validation.


Troubleshooting

Actions not appearing -- Verify ATLAS_ACTIONS_ENABLED=true and authentication is configured (any mode except none). Without DATABASE_URL, actions still work but use in-memory storage.

Approval stuck -- Check that the approving user has the required role. In admin-only mode, the requester cannot self-approve.

See Troubleshooting for more diagnostic steps.


See Also

On this page