Eureka AI
Autopilot Documentation
What is Autopilot?
Autopilot is a modular automation service designed to streamline repetitive workflows by connecting Triggers, Actions, and Notifications into an automated pipeline. When a trigger is fired, Autopilot automatically executes predefined actions and optionally sends notifications to users.
It consists of both a frontend interface for configuration and visualization, and a backend system responsible for processing and executing the automations.
Frontend
The Autopilot frontend is structured around two main screens. The first is the workflow listing screen, which serves as a dashboard for managing all existing workflows. From this interface, users can add new workflows, edit them, pause and resume their execution, or delete them entirely. It's designed to provide quick access and control over the automation processes already in place.
The second screen is the workflow builder. This is the area where users create and configure new workflows from scratch. It provides a visual interface for defining the automation logic. However, in its current form, the builder is limited to handling just one trigger, one action, and one notification per workflow. This constraint simplifies the setup process but also restricts the complexity and flexibility of the workflows that can be created.
To accommodate more advanced use cases, a refactor of the frontend will be required. This future improvement would enable support for multiple nodes of the same type—allowing users to define workflows with several triggers, actions, or notifications. Implementing this would involve changes to the UI to allow dynamic node addition, updates to how workflow data is structured and stored, and likely adjustments in the state management logic to properly handle the new, more complex configurations.
Architecture
The workflow builder is implemented using the VueFlow plugin and is divided into two main sections that work together to provide an intuitive and interactive experience for building automation workflows.
The first section is the node tree. This is the visual canvas where users can construct the structure of their workflow. Nodes represent individual elements such as triggers, actions, and notifications.
The second section is the settings panel. This panel dynamically displays the configuration options for the selected node in the tree. When a user clicks on a node, the settings panel allows them to input specific parameters. This separation between structure and configuration helps keep the interface clean and modular, enabling users to focus either on the flow or the details at any given time.
Key Files and Folders
resources/js/Pages/Autopilot/Workflow/Index.vue- List workflows pageresources/js/Pages/Autopilot/Workflow/Builder/Index.vue- Workflow builder pageresources/js/Components/Autopilot/Nodes- Folder where all the node components are storedresources/js/Components/Autopilot/Panels/Settings- Folder where all the settings panels are storedresources/js/Components/Autopilot/Nodes/Node.vue- Parent component for all the Nodes componentsresources/js/Components/Autopilot/Panels/Settings/SettingsPanelLayout.vue- Parent component for all the settings panels
Implementation
Add new node
- Create a new node component inside
resources/js/Components/Autopilot/Nodes, under the folder with the type of the node (trigger, actions, notification) with the name[NODE_NAME]Node.vue - Copy the following node template to your new file
<script setup>
import Node from '@/Components/Autopilot/Nodes/Node.vue';
import { computed } from 'vue';
import { trans, transChoice } from 'laravel-vue-i18n';
const emit = defineEmits(['edit', 'replace']);
const props = defineProps({
id: {
type: String,
},
data: {
type: Object,
},
selected: {
type: Boolean,
},
});
const generateDescription = computed(() => {
# Implement your node description here, must return a string.
});
</script>
<template>
<Node
:class="{'ring-2! ring-offset-2! ring-purple-600!': selected}"
:title="$t('autopilot.node.YOUR_NODE_NAME')"
:description="generateDescription"
:icon="{ name:'fa-solid fa-calendar-week', backgroundColor: 'bg-purple-500'}"
@edit="emit('edit', props.id)"
@replace="emit('replace', props.id)"
/>
</template>- Create a new settings panel component inside
resources/js/Components/Autopilot/Panels/Settings, under the folder with the type of the node (trigger, actions, notification) with the name[NODE_NAME]SettingsPanel.vue - Copy the following settings panel template to your new file
<script setup>
import { useForm } from '@inertiajs/vue3';
import SettingsPanelLayout from '@/Components/Autopilot/Panels/Settings/SettingsPanelLayout.vue';
import { RecurrenceSettings } from '@/Components/Autopilot/Panels/Settings/Triggers/RecurrenceSettings.js';
const props = defineProps({
workflowUuid: String,
nodeId: Number,
settings: {
type: Object,
},
});
const recurrenceSettingsForm = useForm({
name: 'YOUR_PANEL_NAME',
type: 'trigger',
settings: {
# Settings of your node (the ones that will be saved on the database)
},
});
const savePanelAction = () => {
# The action of clicking on the save button (usually submit the form)
};
</script>
<template>
<SettingsPanelLayout :title="$t('YOUR_PANEL_NAME')" :description="IF_YOU_HAVE_ONE" @save="savePanelAction">
<form class="flex flex-col gap-4 w-full">
# Form implementation, check other settings panels for examples
</form>
</SettingsPanelLayout>
</template>- Add your new node on the
nodeTypeinresources/js/Pages/Autopilot/Workflow/Builder/Index.vue
const nodeType = {
start: markRaw(StartNode),
recurrence: (props) => h(RecurrenceNode, {
...props,
onEdit: (nodeId) => editButtonAction(nodeId),
}),
create_report: (props) => h(CreateReportNode, {
...props,
onEdit: (nodeId) => editButtonAction(nodeId),
onReplace: (nodeId) => replaceButtonAction(nodeId),
}),
update_action_plan: (props) => h(UpdateActionPlanNode, {
...props,
onEdit: (nodeId) => editButtonAction(nodeId),
onReplace: (nodeId) => replaceButtonAction(nodeId),
}),
message: (props) => h(MessageNode, {
...props,
onEdit: (nodeId) => editButtonAction(nodeId),
}),
# Add you new node here, if you are adding a node that can be replaced, you need to set the - onReplace: (nodeId) => replaceButtonAction(nodeId)
end: markRaw(EndNode),
};- After registering your new node you need to add it to the replace menu if the node type has more than one option
const replaceMenu = (nodeId) => {
return {
1: {
title: trans('autopilot.node.trigger.menu.title'),
options: [
# HERE
],
},
2: {
title: trans('autopilot.node.action.menu.title'),
options: [
{
label: trans('autopilot.node.create_new_report'),
type: 'create_report',
icon: {
background: 'bg-amber-500',
name: 'fa-solid fa-file-circle-plus',
},
},
{
label: trans('autopilot.node.update_action_plan'),
type: 'update_action_plan',
icon: {
background: 'bg-amber-500',
name: 'fa-solid fa-magnifying-glass-arrows-rotate',
},
},
# OR HERE
],
},
3: {
title: trans('autopilot.node.notification.menu.title'),
options: [
# OR HERE
],
},
}[nodeId];
};- To finish you need to register the settings panel
const getSettingsPanel = computed(() => {
return {
recurrence: markRaw(RecurrenceSettingsPanel),
create_report: markRaw(CreateReportSettingsPanel),
update_action_plan: markRaw(UpdateActionPlanSettingsPanel),
message: markRaw(MessageSettingsPanel),
# ADD HERE YOUR NEW SETTING PANEL WITH FORMAT NODE_NAME: markRaw(PANEL_COMPONENT)
}[currentNode?.value.type];
});Known Limitations
- Support only 1 node of each
- Static nodes definition on
resources/js/Pages/Autopilot/Workflow/Builder/Index.vue, under constant nameinternalNodesandinternalEdges. This arrays need to be build dynamically in order to support multiple nodes and drag and drop. - Recurrence Node Settings only supports hours and not minutes
Notes
- When you have a settings panel the settings saved on the database will be sent back to the page using inertia, in order to refresh the panel and node with update data.
Backend
The Autopilot backend in our system acts as the core engine responsible for managing the execution flow of a defined workflow. It ensures that each necessary job is properly dispatched and executed in order. To achieve this in a clean, maintainable, and scalable way, Laravel Pipelines have been used. Pipelines allow you to structure the execution logic in a step-by-step, modular fashion, where each piece of logic is handled independently before passing control to the next.
Architecture
The workflow execution process in Autopilot begins with the DispatchActiveWorkflowJobs.php command, which runs every minute. This command identifies all active workflows and dispatches a CreateWorkflowPipeline.php job for each one.
Each job processes its workflow using a Laravel Pipeline composed of four key pipes:
HandleTriggersPipeThis pipe checks if the workflow's trigger conditions are met (e.g., recurrence). If not, the workflow halts hereHandleActionsPipeIf triggered, the workflow moves on to perform its defined actions—like create reports, update action plans, etc...HandleNotificationsPipeAfter actions are executed, this pipe sends out relevant notifications to users or systems, such as emails or in-app alertsHandleFinalisationPipeThis final step is responsible for the cleanup, updating status and execution of trigger end condition
This pipeline architecture keeps workflow logic modular, maintainable, and scalable, with each stage clearly defined and independently testable
Database
The autopilot database consists of 4 tables:
autopilot_workflows- Table responsible for saving the workflow propertiesautopilot_workflow_executions- Table responsible for saving information about the execution of a workflowautopilot_workflow_nodes- Table responsible for storing data about a node on a workflowautopilot_workflow_nodes_relationships- Not being used ATM Relationship between nodes to allow multiple nodes in one workflow
Key Files and Folders
app/Services/Autopilot- Autopilot logic for backendapp/Services/Autopilot/Nodes- Folder where all the nodes are storedapp/Services/Autopilot/Pipes- Folder where all the pipes are storedconfig/autopilot.php- Autopilot configuration fileapp/Services/Autopilot/Nodes/Actions/ActionInterface.php- Interface to be implemented by every Actionapp/Services/Autopilot/Nodes/Notifications/NotificationInterface.php- Interface to be implemented by every Notificationapp/Services/Autopilot/Nodes/Triggers/TriggerInterface.php- Interface to be implemented by every Triggerapp/Services/Autopilot/Nodes/Triggers/TriggerEndConditionInterface.php- Interface to be implemented by every TriggerEndConditionapp/Console/Commands/Autopilot/DispatchActiveWorkflowJobs.php- Command responsible to dispatch active workflowsapp/Jobs/Autopilot/CreateWorkflowPipeline.php- Job responsible to create the workflow execution Pipeline
Implementation
Add new node
- Create a new node inside
app/Services/Autopilot/Nodes(put it into the correct type folder) - Implement the proper interface depending on the node type (Trigger, Action, Notification)
- Register the new node in
config/autopilot.php
For examples, check the already existing nodes.
Notifications
The MessageNotification class delegates to each action a specific notification handler class, it's implemented here:
app/Services/Autopilot/Nodes/Notifications/Message/MessageNotification.php
<?php
namespace App\Services\Autopilot\Nodes\Notifications\Message;
use App\DataTransferObjects\Autopilot\AutopilotWorkflowExecutionData;
use App\Models\AutopilotWorkflowNode;
use App\Services\Autopilot\Nodes\Notifications\NotificationInterface;
use LogicException;
class MessageNotification implements NotificationInterface
{
public function execute(AutopilotWorkflowExecutionData $workflowExecutionData): AutopilotWorkflowExecutionData
{
$actionNode = AutopilotWorkflowNode::query()->findOrFail($workflowExecutionData->actionNodeId);
$handler = match ($actionNode->name) {
'create_report' => app(CreateReportNotificationHandler::class),
'update_action_plan' => app(UpdateActionPlanNotificationHandler::class),
default => throw new LogicException('Unknown action node: ' . $actionNode->name),
};
return $handler->execute($workflowExecutionData);
}
}When creating new Action, you must, on the Notification part: Create a corresponding notification handler class that implements the execution logic. Add it to the match block inside MessageNotification so it is properly routed.
See existing classes to get examples.
Known Limitations
- Support only 1 node of each
- No not support async node executions
- After the duplication of a report was migrated to a job, our main job will finish before the duplication one, so it means the user will get the notification of report complete, before it was completed - Problem only for reports created from master.
- The order of execution is locked at
Triggers -> Actions -> Notifications
Notes
- All the actives workflows are dispatched every minute, is the responsibility of the job to check if that workflow should run or not on the current time. It was done like that to reduce the process on the main server. This way the responsibility of the main server is just to dispatch the jobs for the active workflows.
- Entries on the table
autopilot_workflow_executionsonly happen when the workflow meets the trigger conditions - All the nodes and pipes have access to
AutopilotWorkflowExecutionData - The node name has to be equal on the frontend and backend