Automated Actions

Automated Actions are the workflow orchestration layer in Maitento. They enable you to run Apps, Interactions, and Capsules automatically based on schedules (cron expressions) or in response to system events (message triggers).

Table of Contents

  1. What are Automated Actions
  2. Cron-Based Scheduling
  3. Message-Based Triggers
  4. Triggering Apps, Interactions, and Capsules
  5. Execution History and Monitoring
  6. API Reference
  7. SDK Reference
  8. Shell Commands
  9. Best Practices

What are Automated Actions

An Automated Action is a reusable workflow definition that specifies:

  • What to run: One or more actions (Apps, Interactions, Capsules, or Code Generation tasks)
  • When to run: Cron schedules for time-based execution and/or message triggers for event-driven execution
  • Tracking: Execution history, failure counts, and distributed locking for safe concurrent execution

Core Components

ComponentDescription
Name / DescriptionHuman-readable identification
Actions[]Array of action items to execute
Schedules[]Cron-based schedules (max 5 per action)
MessageTriggers[]Event-based triggers
History[]Execution history (capped at 50 entries)
NextScheduledRunAtNext computed execution time
TotalExecutionsLifetime execution count
ConsecutiveFailuresFailure counter (resets on success)

How Execution Works

  1. Trigger: A schedule fires, an event matches a message trigger, or manual trigger is invoked
  2. Claim: The system acquires a distributed lock (5-minute timeout) to prevent duplicate execution
  3. Execute: Each action item in the Actions[] array spawns a process
  4. Record: Execution is recorded in history with outcome (Success, Failed, Killed)
  5. Release: The distributed lock is released

Cron-Based Scheduling

Cron schedules define time-based execution patterns using standard cron expressions.

Cron Expression Format

5-Field Format (Standard):

minute hour dayOfMonth month dayOfWeek

6-Field Format (With Seconds):

second minute hour dayOfMonth month dayOfWeek

Field Reference

FieldAllowed ValuesDescription
second0-59Second of the minute (6-field only)
minute0-59Minute of the hour
hour0-23Hour of the day (24-hour format)
dayOfMonth1-31Day of the month
month1-12Month of the year
dayOfWeek0-6Day of week (0=Sunday, 6=Saturday)

Special Characters

CharacterDescriptionExample
*Any value* in minute = every minute
,List separator1,15 = 1st and 15th
-Range1-5 = Monday through Friday
/Step values*/15 = every 15 units

Common Schedule Examples

ScheduleExpressionFields
Every minute* * * * *minute: *, hour: *, dayOfMonth: *, month: *, dayOfWeek: *
Every 15 minutes*/15 * * * *minute: */15
Every hour0 * * * *minute: 0
Daily at midnight0 0 * * *minute: 0, hour: 0
Daily at 9:00 AM0 9 * * *minute: 0, hour: 9
Weekdays at 9:00 AM0 9 * * 1-5minute: 0, hour: 9, dayOfWeek: 1-5
Every Monday at 3:00 AM0 3 * * 1minute: 0, hour: 3, dayOfWeek: 1
First of month at noon0 12 1 * *minute: 0, hour: 12, dayOfMonth: 1
Multiple times per day0 9,12,15,18 * * *hour: 9,12,15,18
Every 30 seconds*/30 * * * * *second: */30 (6-field)

Schedule Properties

PropertyTypeDefaultDescription
idUUIDAuto-generatedUnique identifier
namestring-Display name
isEnabledbooleantrueWhether schedule is active
secondstring-Second field (6-field cron)
minutestring*Minute field
hourstring*Hour field
dayOfMonthstring*Day of month field
monthstring*Month field
dayOfWeekstring*Day of week field
nextOccurrenceISO 8601ComputedNext scheduled run time

Constraints

  • Maximum 5 schedules per automated action
  • Use comma-separated values or step expressions to consolidate schedules

Message-Based Triggers

Message triggers enable event-driven execution. When a system event occurs and matches the trigger’s filter, the automated action executes.

Available Trigger Types

Trigger TypeDescription
AppProcessUpdatedAn app process status changed
InteractionProcessUpdatedAn interaction process status changed
CodeGenerationProcessUpdatedA code generation process status changed
CapsuleProcessUpdatedA capsule process status changed

JPath Filter Expressions

Filters use JPath syntax to match specific events. The event payload is accessible via the $ root object.

Event Payload Structure

All process update events include:

{
  "item": {
    "id": "process-id",
    "status": "Finished|Error|Killed|Running|Pending",
    "tenantId": "tenant-id",
    "dateCreated": "2026-01-01T00:00:00Z",
    "dateUpdated": "2026-01-01T00:05:00Z"
  }
}

Additional fields depend on the process type (e.g., appId for app processes, interactionId for interaction processes).

Filter Examples

Filter ExpressionDescription
$.item.status == 'Finished'Only on successful completion
$.item.status == 'Error'Only on errors
$.item.status == 'Killed'Only on manual termination
$.item.status == 'Finished' || $.item.status == 'Error'On any completion
$.item.appId == 'specific-app-id'Only for a specific app
$.item.status == 'Finished' && $.item.appId == 'abc123'Successful completion of specific app
$.item.interactionId == 'xyz789'Only for a specific interaction

Trigger Properties

PropertyTypeDefaultDescription
idUUIDAuto-generatedUnique identifier
namestring-Display name
isEnabledbooleantrueWhether trigger is active
triggerTypestringRequiredEvent type to listen for
eventFilterstring-JPath expression to filter events

Triggering Apps, Interactions, and Capsules

Automated actions can start different types of processes. The config object in each action item uses JSON polymorphism with the $type discriminator.

Starting an App ($type: "app")

Run a Cogniscript app with input parameters.

{
  "name": "Run My App",
  "config": {
    "$type": "app",
    "version": {
      "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
      "version": 1
    },
    "inputs": [
      { "name": "reportType", "value": "daily-summary" },
      { "name": "format", "value": "pdf" }
    ]
  }
}
FieldTypeRequiredDescription
$type"app"YesType discriminator
version.idUUIDYesApp ID
version.versionintegerYesApp version number
inputsNameValuePair[]NoInput parameters

Starting an Interaction ($type: "interaction")

Start an AI-powered interaction with an initial prompt.

{
  "name": "Start AI Analysis",
  "config": {
    "$type": "interaction",
    "version": {
      "id": "b2c3d4e5-6789-01bc-def2-3456789012cd",
      "version": 1
    },
    "initialPrompt": "Analyze the imported data and generate a summary report.",
    "inputs": [
      { "name": "outputFormat", "value": "markdown" }
    ]
  }
}
FieldTypeRequiredDescription
$type"interaction"YesType discriminator
version.idUUIDYesInteraction ID
version.versionintegerYesInteraction version number
initialPromptstringNoInitial message to send
inputsNameValuePair[]NoInput parameters

Sending a Message to an Existing Interaction ($type: "interaction-message")

Send a follow-up message to a running interaction process.

{
  "name": "Send Follow-up",
  "config": {
    "$type": "interaction-message",
    "interactionProcessIdFromEvent": "$.item.id",
    "message": "Please provide the detailed breakdown.",
    "senderType": "System"
  }
}
FieldTypeRequiredDescription
$type"interaction-message"YesType discriminator
interactionProcessIdUUIDConditionalTarget process ID (static)
interactionProcessIdFromEventstringConditionalJPath to extract process ID from trigger event
messagestringYesMessage to send
senderTypestringNoSender type (default: “System”)

Note: Either interactionProcessId or interactionProcessIdFromEvent must be specified, but not both.

Starting a Capsule ($type: "capsule")

Start an isolated container environment with optional mounts.

{
  "name": "Run Container Task",
  "config": {
    "$type": "capsule",
    "version": {
      "id": "c3d4e5f6-7890-12cd-ef34-567890123456",
      "version": 1
    },
    "vfsMounts": [
      {
        "vfsId": "d4e5f6a7-8901-23de-f456-789012345678",
        "vfsNamespaceId": "e5f6a7b8-9012-34ef-5678-901234567890",
        "mountName": "data"
      }
    ],
    "secretMounts": [
      {
        "secretName": "api-credentials",
        "mountName": "secrets"
      }
    ]
  }
}
FieldTypeRequiredDescription
$type"capsule"YesType discriminator
version.idUUIDYesCapsule ID
version.versionintegerYesCapsule version number
vfsMountsVFSMount[]NoVirtual filesystem mounts
secretMountsSecretMount[]NoSecret mounts

Starting Code Generation ($type: "codegen")

Trigger an AI-powered code generation task.

{
  "name": "Update Dependencies",
  "config": {
    "$type": "codegen",
    "gitUrl": "git@github.com:myorg/myrepo.git",
    "gitSshKeyBase64": "LS0tLS1CRUdJTi...",
    "branchSource": "main",
    "branchWork": "auto/dependency-updates",
    "codeGuidelines": "Follow semantic versioning.",
    "steps": [
      {
        "step": "Update all npm dependencies",
        "isCommitableStep": true
      }
    ],
    "generateDiffOnCompletion": true,
    "engine": "Claude"
  }
}
FieldTypeRequiredDescription
$type"codegen"YesType discriminator
gitUrlstringYesGit repository URL
gitSshKeyBase64stringNoBase64-encoded SSH private key
gitHttpCredentialsstringNoHTTP credentials (username:password)
branchSourcestringYesSource branch name
branchWorkstringYesWorking branch name
codeGuidelinesstringYesCode guidelines/context
stepsStep[]NoGeneration steps
generateDiffOnCompletionbooleanNoGenerate diff on completion
enginestringNoEngine: "Claude" or "Kimi"

Execution History and Monitoring

Maitento tracks execution history for each automated action, enabling monitoring, debugging, and alerting.

History Entry Fields

FieldTypeDescription
idUUIDEntry identifier
triggerTypestringHow triggered: "Schedule", "Message", or "Manual"
triggerIdUUIDSchedule or trigger ID that initiated execution
actionIdUUIDID of the action item that was executed
processIdUUIDSpawned process ID
processTypestring"App", "Interaction", "CodeGeneration", or "Capsule"
dateStartedISO 8601Start timestamp
dateCompletedISO 8601Completion timestamp
outcomestring"Pending", "Success", "Failed", or "Killed"
errorMessagestringError message if failed

Monitoring Metrics

MetricDescriptionUse Case
totalExecutionsLifetime execution countUsage tracking
consecutiveFailuresFailures since last successAlerting (resets on success)
lastExecutedAtLast execution timestampStaleness detection
lastProcessIdMost recent process IDQuick access to last run
nextScheduledRunAtNext scheduled executionSchedule verification

Execution Claim (Distributed Lock)

To prevent duplicate execution across distributed workers, the system uses an execution claim:

FieldDescription
machineNameName of the machine holding the lock
processIdOS process ID
instanceIdInstance identifier
claimedAtLock acquisition timestamp

The claim has a 5-minute timeout for crashed instances.


API Reference

Authentication

All endpoints require authentication:

MethodHeaderDescription
Bearer TokenAuthorization: Bearer <token>OAuth2/JWT token
API KeyX-API-Key: <api-key>Tenant API key

Required Roles: Tenant.Admin or Tenant.User

Endpoints

Core Operations

EndpointMethodDescription
/automated-actionsGETList all actions
/namespaces/{ns}/automated-actionsGETList actions in namespace
/namespaces/{ns}/automated-actionsPOSTCreate action
/automated-actions/by-id/{id}GETGet action by ID
/namespaces/{ns}/automated-actions/by-name/{name}GETGet action by name
/automated-actions/by-id/{id}PUTUpdate action
/automated-actions/by-id/{id}DELETEDelete action
/automated-actions/by-id/{id}/triggerPOSTManual trigger
/automated-actions/by-id/{id}/historyGETGet execution history

Schedule Management

EndpointMethodDescription
/automated-actions/by-id/{id}/schedulesPOSTAdd schedule
/automated-actions/by-id/{id}/schedules/{sid}PUTUpdate schedule
/automated-actions/by-id/{id}/schedules/{sid}DELETERemove schedule
/automated-actions/by-id/{id}/schedules/{sid}/enablePOSTEnable schedule
/automated-actions/by-id/{id}/schedules/{sid}/disablePOSTDisable schedule

Message Trigger Management

EndpointMethodDescription
/automated-actions/by-id/{id}/message-triggersPOSTAdd trigger
/automated-actions/by-id/{id}/message-triggers/{tid}PUTUpdate trigger
/automated-actions/by-id/{id}/message-triggers/{tid}DELETERemove trigger
/automated-actions/by-id/{id}/message-triggers/{tid}/enablePOSTEnable trigger
/automated-actions/by-id/{id}/message-triggers/{tid}/disablePOSTDisable trigger

Example: Create Action with Schedule

Request:

POST /namespaces/production/automated-actions
Content-Type: application/json
Authorization: Bearer <token>

{
  "name": "daily-cleanup",
  "description": "Runs cleanup app every day at midnight",
  "actions": [
    {
      "name": "Run Cleanup",
      "config": {
        "$type": "app",
        "version": {
          "id": "11111111-1111-1111-1111-111111111111",
          "version": 1
        },
        "inputs": [
          { "name": "daysToKeep", "value": "30" }
        ]
      }
    }
  ]
}

Add Schedule:

POST /automated-actions/by-id/{id}/schedules
Content-Type: application/json
Authorization: Bearer <token>

{
  "name": "Midnight Daily",
  "minute": "0",
  "hour": "0",
  "dayOfMonth": "*",
  "month": "*",
  "dayOfWeek": "*"
}

Example: Add Message Trigger

POST /automated-actions/by-id/{id}/message-triggers
Content-Type: application/json
Authorization: Bearer <token>

{
  "name": "On Data Import Complete",
  "triggerType": "AppProcessUpdated",
  "eventFilter": "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
}

Example: Manual Trigger

POST /automated-actions/by-id/{id}/trigger
Authorization: Bearer <token>

Response:

{
  "processId": "f7a8b9c0-1234-56d7-8901-234567890123"
}

SDK Reference

Creating an Automated Action

using Maitento.Sdk;
using Maitento.Entities.AutomatedActions;

// Create the client
var client = MaitentoClient.Create("your-api-key");

// Define the action to run
var appAction = new AutomatedActionItem
{
    Name = "Generate Report",
    Config = new AppProcessStartConfig
    {
        Version = new VersionReference
        {
            Id = Guid.Parse("a1b2c3d4-5678-90ab-cdef-1234567890ab"),
            Version = 1
        },
        Inputs = new[]
        {
            new NameValuePair("reportType", "daily-summary"),
            new NameValuePair("format", "pdf")
        }
    }
};

// Create the automated action
var action = await client.AutomatedActions.CreateAsync(
    namespaceName: "production",
    name: "daily-report",
    description: "Generates daily summary reports",
    actions: new[] { appAction }
);

Console.WriteLine($"Created action: {action.Id}");

Adding a Schedule

// Add a daily schedule at 9 AM
var schedule = new CronSchedule
{
    Name = "Daily 9am",
    IsEnabled = true,
    Minute = "0",
    Hour = "9",
    DayOfMonth = "*",
    Month = "*",
    DayOfWeek = "*"
};

var updatedAction = await client.AutomatedActions.AddScheduleAsync(
    action.Id,
    schedule
);

Console.WriteLine($"Next run: {updatedAction.NextScheduledRunAt}");

Adding a Message Trigger

// Add a trigger for when another app completes
var trigger = new MessageTrigger
{
    Name = "On Data Import Complete",
    IsEnabled = true,
    TriggerType = MessageTriggerType.AppProcessUpdated,
    EventFilter = "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"
};

var updatedAction = await client.AutomatedActions.AddMessageTriggerAsync(
    action.Id,
    trigger
);

Manual Triggering

// Manually trigger the action
var processId = await client.AutomatedActions.TriggerAsync(action.Id);
Console.WriteLine($"Started process: {processId}");

Monitoring Execution History

// Get execution history
var history = await client.AutomatedActions.GetHistoryAsync(
    action.Id,
    limit: 20
);

foreach (var entry in history)
{
    Console.WriteLine($"[{entry.Outcome}] {entry.TriggerType} at {entry.DateStarted}");

    if (entry.Outcome == ExecutionOutcome.Failed)
    {
        Console.WriteLine($"  Error: {entry.ErrorMessage}");
    }
}

// Check for consecutive failures (alerting)
var action = await client.AutomatedActions.GetByIdAsync(actionId);
if (action.ConsecutiveFailures >= 3)
{
    await SendAlert($"Action '{action.Name}' has failed {action.ConsecutiveFailures} times");
}

Enable/Disable Schedules and Triggers

// Disable a schedule
await client.AutomatedActions.DisableScheduleAsync(action.Id, scheduleId);

// Re-enable a schedule
await client.AutomatedActions.EnableScheduleAsync(action.Id, scheduleId);

// Disable a trigger
await client.AutomatedActions.DisableMessageTriggerAsync(action.Id, triggerId);

// Re-enable a trigger
await client.AutomatedActions.EnableMessageTriggerAsync(action.Id, triggerId);

Exception Handling

try
{
    var action = await client.AutomatedActions.CreateAsync(...);
}
catch (MaitentoValidationException ex)
{
    Console.WriteLine("Validation errors:");
    foreach (var error in ex.ValidationErrors)
    {
        Console.WriteLine($"  {error.Key}: {string.Join(", ", error.Value)}");
    }
}
catch (MaitentoApiException ex)
{
    Console.WriteLine($"API error ({ex.StatusCode}): {ex.ResponseBody}");
}
catch (MaitentoApiAuthenticationException ex)
{
    Console.WriteLine("Authentication failed - check your API key");
}

Shell Commands

Core Operations

# List all actions in a namespace
action-list --namespace production

# Get action details by name
action-get --name daily-report --namespace production

# Get action details by ID
action-get --id 12345678-1234-1234-1234-123456789abc

# Create an action
action-create my-action \
    --namespace production \
    --description "My automated action" \
    --actions '[{"name":"Run App","config":{"$type":"app","version":{"id":"...","version":1}}}]'

# Update an action
action-update --id <id> --name new-name --actions '[...]'

# Delete an action
action-delete --name my-action --namespace production

# Manually trigger an action
action-trigger --name my-action --namespace production

# Get execution history
action-history --name my-action --namespace production --limit 10

Schedule Management

# Add a schedule (daily at 2:30 AM)
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Daily 2:30am" \
    --minute 30 --hour 2

# Add a schedule (every 15 minutes)
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Every 15 min" \
    --minute "*/15"

# Add a schedule (weekdays at 9 AM)
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Weekday morning" \
    --minute 0 --hour 9 --day-of-week 1-5

# Add a schedule (first of month at midnight)
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Monthly" \
    --minute 0 --hour 0 --day-of-month 1

# Add a schedule with seconds (every 30 seconds)
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Every 30 sec" \
    --second "*/30"

# Remove a schedule
action-schedule-remove --action-name my-action --namespace production \
    --schedule-id <schedule-id>

# Enable/disable a schedule
action-schedule-enable --action-name my-action --namespace production \
    --schedule-id <schedule-id>

action-schedule-disable --action-name my-action --namespace production \
    --schedule-id <schedule-id>

Message Trigger Management

# Add a trigger for app process updates
action-msg-trigger-add --action-name my-action --namespace production \
    --trigger-name "On App Complete" \
    --type AppProcessUpdated

# Add a trigger with JPath filter
action-msg-trigger-add --action-name my-action --namespace production \
    --trigger-name "On Success Only" \
    --type AppProcessUpdated \
    --filter "$.item.status == 'Finished'"

# Add a trigger for specific app completion
action-msg-trigger-add --action-name my-action --namespace production \
    --trigger-name "On Data Import" \
    --type AppProcessUpdated \
    --filter "$.item.status == 'Finished' && $.item.appId == 'data-import-app-id'"

# Add a trigger for any failure
action-msg-trigger-add --action-name alert-action --namespace production \
    --trigger-name "On Any Error" \
    --type AppProcessUpdated \
    --filter "$.item.status == 'Error'"

# Remove a trigger
action-msg-trigger-remove --action-name my-action --namespace production \
    --trigger-id <trigger-id>

# Enable/disable a trigger
action-msg-trigger-enable --action-name my-action --namespace production \
    --trigger-id <trigger-id>

action-msg-trigger-disable --action-name my-action --namespace production \
    --trigger-id <trigger-id>

Best Practices

Design for Idempotency

Your automated actions may run multiple times due to retries, duplicate triggers, or overlapping schedules. Design your apps and workflows to handle this gracefully:

// Example: Check if work is already done
var existingReport = await GetReportForDate(DateTime.Today);
if (existingReport != null)
{
    Console.WriteLine("Report already generated, skipping");
    return;
}

Use Descriptive Names

Give your schedules and triggers meaningful names for easier debugging:

# Good
--schedule-name "Weekday Morning Report"
--trigger-name "On Customer Data Import Complete"

# Bad
--schedule-name "Schedule 1"
--trigger-name "Trigger"

Consolidate Schedules Efficiently

Each action supports a maximum of 5 schedules. Use cron expressions efficiently:

# Instead of 4 separate schedules for 9am, 12pm, 3pm, 6pm:
# Use a single schedule with comma-separated hours
action-schedule-add --action-name my-action --namespace production \
    --schedule-name "Business hours" \
    --minute 0 --hour "9,12,15,18"

Monitor Consecutive Failures

Use the consecutiveFailures counter for alerting:

var action = await client.AutomatedActions.GetByIdAsync(actionId);

if (action.ConsecutiveFailures >= 3)
{
    await SendAlert($"Action '{action.Name}' has failed {action.ConsecutiveFailures} times in a row");
}

Test with Manual Triggers

Always test your action manually before relying on schedules:

# Test the action
action-trigger --name daily-report --namespace production

# Check the result
action-history --name daily-report --namespace production --limit 1

Use Specific Event Filters

Avoid overly broad message triggers. Use JPath filters to target specific events:

# Broad (triggers on ANY app update)
--type AppProcessUpdated

# Specific (triggers only on successful completion of a specific app)
--type AppProcessUpdated \
--filter "$.item.status == 'Finished' && $.item.appId == 'my-specific-app'"

Handle Execution History Limits

History is capped at 50 entries. For long-term analytics, export history to external storage:

// Periodically export history before it rotates out
var history = await client.AutomatedActions.GetHistoryAsync(actionId, limit: 50);
await ExportToDataWarehouse(history);

Set Appropriate Timeouts

For apps triggered by automated actions, ensure they have appropriate ExecutionTimeLimit values to prevent runaway processes.

Chain Actions with Message Triggers

Build complex workflows by chaining actions:

  1. Action A runs an app that imports data
  2. Action B has a message trigger for Action A’s app completion
  3. Action B runs analysis when import finishes
  4. Action C has a message trigger for Action B’s completion
  5. Action C sends a notification when analysis completes

Troubleshooting

Action Not Running on Schedule

  1. Verify the schedule is enabled:
    action-get --name my-action --namespace production
    
  2. Check the nextScheduledRunAt field is set correctly
  3. Ensure the cron expression is valid
  4. Check that the action itself is not disabled

Message Trigger Not Firing

  1. Verify the trigger type matches the event source
  2. Test your JPath filter syntax against actual event payloads
  3. Ensure the trigger is enabled
  4. Verify the source process is actually completing/failing

Execution History Shows Failures

  1. Check the errorMessage field in history entries
  2. Verify the target app/interaction/capsule exists and is not disabled
  3. Check input parameter values are correct
  4. Review the spawned process logs for detailed error information

Duplicate Executions

  1. Check for overlapping schedules
  2. Verify message trigger filters are specific enough
  3. Ensure your app logic is idempotent