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
- What are Automated Actions
- Cron-Based Scheduling
- Message-Based Triggers
- Triggering Apps, Interactions, and Capsules
- Execution History and Monitoring
- API Reference
- SDK Reference
- Shell Commands
- 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
| Component | Description |
|---|---|
Name / Description | Human-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) |
NextScheduledRunAt | Next computed execution time |
TotalExecutions | Lifetime execution count |
ConsecutiveFailures | Failure counter (resets on success) |
How Execution Works
- Trigger: A schedule fires, an event matches a message trigger, or manual trigger is invoked
- Claim: The system acquires a distributed lock (5-minute timeout) to prevent duplicate execution
- Execute: Each action item in the
Actions[]array spawns a process - Record: Execution is recorded in history with outcome (Success, Failed, Killed)
- 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
| Field | Allowed Values | Description |
|---|---|---|
second | 0-59 | Second of the minute (6-field only) |
minute | 0-59 | Minute of the hour |
hour | 0-23 | Hour of the day (24-hour format) |
dayOfMonth | 1-31 | Day of the month |
month | 1-12 | Month of the year |
dayOfWeek | 0-6 | Day of week (0=Sunday, 6=Saturday) |
Special Characters
| Character | Description | Example |
|---|---|---|
* | Any value | * in minute = every minute |
, | List separator | 1,15 = 1st and 15th |
- | Range | 1-5 = Monday through Friday |
/ | Step values | */15 = every 15 units |
Common Schedule Examples
| Schedule | Expression | Fields |
|---|---|---|
| Every minute | * * * * * | minute: *, hour: *, dayOfMonth: *, month: *, dayOfWeek: * |
| Every 15 minutes | */15 * * * * | minute: */15 |
| Every hour | 0 * * * * | minute: 0 |
| Daily at midnight | 0 0 * * * | minute: 0, hour: 0 |
| Daily at 9:00 AM | 0 9 * * * | minute: 0, hour: 9 |
| Weekdays at 9:00 AM | 0 9 * * 1-5 | minute: 0, hour: 9, dayOfWeek: 1-5 |
| Every Monday at 3:00 AM | 0 3 * * 1 | minute: 0, hour: 3, dayOfWeek: 1 |
| First of month at noon | 0 12 1 * * | minute: 0, hour: 12, dayOfMonth: 1 |
| Multiple times per day | 0 9,12,15,18 * * * | hour: 9,12,15,18 |
| Every 30 seconds | */30 * * * * * | second: */30 (6-field) |
Schedule Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | UUID | Auto-generated | Unique identifier |
name | string | - | Display name |
isEnabled | boolean | true | Whether schedule is active |
second | string | - | Second field (6-field cron) |
minute | string | * | Minute field |
hour | string | * | Hour field |
dayOfMonth | string | * | Day of month field |
month | string | * | Month field |
dayOfWeek | string | * | Day of week field |
nextOccurrence | ISO 8601 | Computed | Next 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 Type | Description |
|---|---|
AppProcessUpdated | An app process status changed |
InteractionProcessUpdated | An interaction process status changed |
CodeGenerationProcessUpdated | A code generation process status changed |
CapsuleProcessUpdated | A 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 Expression | Description |
|---|---|
$.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
| Property | Type | Default | Description |
|---|---|---|---|
id | UUID | Auto-generated | Unique identifier |
name | string | - | Display name |
isEnabled | boolean | true | Whether trigger is active |
triggerType | string | Required | Event type to listen for |
eventFilter | string | - | 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" }
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
$type | "app" | Yes | Type discriminator |
version.id | UUID | Yes | App ID |
version.version | integer | Yes | App version number |
inputs | NameValuePair[] | No | Input 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" }
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
$type | "interaction" | Yes | Type discriminator |
version.id | UUID | Yes | Interaction ID |
version.version | integer | Yes | Interaction version number |
initialPrompt | string | No | Initial message to send |
inputs | NameValuePair[] | No | Input 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"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
$type | "interaction-message" | Yes | Type discriminator |
interactionProcessId | UUID | Conditional | Target process ID (static) |
interactionProcessIdFromEvent | string | Conditional | JPath to extract process ID from trigger event |
message | string | Yes | Message to send |
senderType | string | No | Sender 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"
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
$type | "capsule" | Yes | Type discriminator |
version.id | UUID | Yes | Capsule ID |
version.version | integer | Yes | Capsule version number |
vfsMounts | VFSMount[] | No | Virtual filesystem mounts |
secretMounts | SecretMount[] | No | Secret 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"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
$type | "codegen" | Yes | Type discriminator |
gitUrl | string | Yes | Git repository URL |
gitSshKeyBase64 | string | No | Base64-encoded SSH private key |
gitHttpCredentials | string | No | HTTP credentials (username:password) |
branchSource | string | Yes | Source branch name |
branchWork | string | Yes | Working branch name |
codeGuidelines | string | Yes | Code guidelines/context |
steps | Step[] | No | Generation steps |
generateDiffOnCompletion | boolean | No | Generate diff on completion |
engine | string | No | Engine: "Claude" or "Kimi" |
Execution History and Monitoring
Maitento tracks execution history for each automated action, enabling monitoring, debugging, and alerting.
History Entry Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Entry identifier |
triggerType | string | How triggered: "Schedule", "Message", or "Manual" |
triggerId | UUID | Schedule or trigger ID that initiated execution |
actionId | UUID | ID of the action item that was executed |
processId | UUID | Spawned process ID |
processType | string | "App", "Interaction", "CodeGeneration", or "Capsule" |
dateStarted | ISO 8601 | Start timestamp |
dateCompleted | ISO 8601 | Completion timestamp |
outcome | string | "Pending", "Success", "Failed", or "Killed" |
errorMessage | string | Error message if failed |
Monitoring Metrics
| Metric | Description | Use Case |
|---|---|---|
totalExecutions | Lifetime execution count | Usage tracking |
consecutiveFailures | Failures since last success | Alerting (resets on success) |
lastExecutedAt | Last execution timestamp | Staleness detection |
lastProcessId | Most recent process ID | Quick access to last run |
nextScheduledRunAt | Next scheduled execution | Schedule verification |
Execution Claim (Distributed Lock)
To prevent duplicate execution across distributed workers, the system uses an execution claim:
| Field | Description |
|---|---|
machineName | Name of the machine holding the lock |
processId | OS process ID |
instanceId | Instance identifier |
claimedAt | Lock acquisition timestamp |
The claim has a 5-minute timeout for crashed instances.
API Reference
Authentication
All endpoints require authentication:
| Method | Header | Description |
|---|---|---|
| Bearer Token | Authorization: Bearer <token> | OAuth2/JWT token |
| API Key | X-API-Key: <api-key> | Tenant API key |
Required Roles: Tenant.Admin or Tenant.User
Endpoints
Core Operations
| Endpoint | Method | Description |
|---|---|---|
/automated-actions | GET | List all actions |
/namespaces/{ns}/automated-actions | GET | List actions in namespace |
/namespaces/{ns}/automated-actions | POST | Create action |
/automated-actions/by-id/{id} | GET | Get action by ID |
/namespaces/{ns}/automated-actions/by-name/{name} | GET | Get action by name |
/automated-actions/by-id/{id} | PUT | Update action |
/automated-actions/by-id/{id} | DELETE | Delete action |
/automated-actions/by-id/{id}/trigger | POST | Manual trigger |
/automated-actions/by-id/{id}/history | GET | Get execution history |
Schedule Management
| Endpoint | Method | Description |
|---|---|---|
/automated-actions/by-id/{id}/schedules | POST | Add schedule |
/automated-actions/by-id/{id}/schedules/{sid} | PUT | Update schedule |
/automated-actions/by-id/{id}/schedules/{sid} | DELETE | Remove schedule |
/automated-actions/by-id/{id}/schedules/{sid}/enable | POST | Enable schedule |
/automated-actions/by-id/{id}/schedules/{sid}/disable | POST | Disable schedule |
Message Trigger Management
| Endpoint | Method | Description |
|---|---|---|
/automated-actions/by-id/{id}/message-triggers | POST | Add trigger |
/automated-actions/by-id/{id}/message-triggers/{tid} | PUT | Update trigger |
/automated-actions/by-id/{id}/message-triggers/{tid} | DELETE | Remove trigger |
/automated-actions/by-id/{id}/message-triggers/{tid}/enable | POST | Enable trigger |
/automated-actions/by-id/{id}/message-triggers/{tid}/disable | POST | Disable 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:
- Action A runs an app that imports data
- Action B has a message trigger for Action A’s app completion
- Action B runs analysis when import finishes
- Action C has a message trigger for Action B’s completion
- Action C sends a notification when analysis completes
Troubleshooting
Action Not Running on Schedule
- Verify the schedule is enabled:
action-get --name my-action --namespace production - Check the
nextScheduledRunAtfield is set correctly - Ensure the cron expression is valid
- Check that the action itself is not disabled
Message Trigger Not Firing
- Verify the trigger type matches the event source
- Test your JPath filter syntax against actual event payloads
- Ensure the trigger is enabled
- Verify the source process is actually completing/failing
Execution History Shows Failures
- Check the
errorMessagefield in history entries - Verify the target app/interaction/capsule exists and is not disabled
- Check input parameter values are correct
- Review the spawned process logs for detailed error information
Duplicate Executions
- Check for overlapping schedules
- Verify message trigger filters are specific enough
- Ensure your app logic is idempotent
Related Documentation
- Apps and Capsules Guide - Learn about apps and capsules
- API Reference - Complete API documentation
- Shell Reference - Shell command reference
- SDK Reference - C# SDK documentation