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 page
  • resources/js/Pages/Autopilot/Workflow/Builder/Index.vue - Workflow builder page
  • resources/js/Components/Autopilot/Nodes - Folder where all the node components are stored
  • resources/js/Components/Autopilot/Panels/Settings - Folder where all the settings panels are stored
  • resources/js/Components/Autopilot/Nodes/Node.vue - Parent component for all the Nodes components
  • resources/js/Components/Autopilot/Panels/Settings/SettingsPanelLayout.vue - Parent component for all the settings panels

Implementation

Add new node

  1. 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
  2. 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>
  1. 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
  2. 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>
  1. Add your new node on the nodeType in resources/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),
};
  1. 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];
};
  1. 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 name internalNodes and internalEdges. 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:

  1. HandleTriggersPipe This pipe checks if the workflow's trigger conditions are met (e.g., recurrence). If not, the workflow halts here

  2. HandleActionsPipe If triggered, the workflow moves on to perform its defined actions—like create reports, update action plans, etc...

  3. HandleNotificationsPipe After actions are executed, this pipe sends out relevant notifications to users or systems, such as emails or in-app alerts

  4. HandleFinalisationPipe This 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 properties
  • autopilot_workflow_executions - Table responsible for saving information about the execution of a workflow
  • autopilot_workflow_nodes - Table responsible for storing data about a node on a workflow
  • autopilot_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 backend
  • app/Services/Autopilot/Nodes - Folder where all the nodes are stored
  • app/Services/Autopilot/Pipes - Folder where all the pipes are stored
  • config/autopilot.php - Autopilot configuration file
  • app/Services/Autopilot/Nodes/Actions/ActionInterface.php - Interface to be implemented by every Action
  • app/Services/Autopilot/Nodes/Notifications/NotificationInterface.php - Interface to be implemented by every Notification
  • app/Services/Autopilot/Nodes/Triggers/TriggerInterface.php - Interface to be implemented by every Trigger
  • app/Services/Autopilot/Nodes/Triggers/TriggerEndConditionInterface.php - Interface to be implemented by every TriggerEndCondition
  • app/Console/Commands/Autopilot/DispatchActiveWorkflowJobs.php - Command responsible to dispatch active workflows
  • app/Jobs/Autopilot/CreateWorkflowPipeline.php - Job responsible to create the workflow execution Pipeline

Implementation

Add new node

  1. Create a new node inside app/Services/Autopilot/Nodes (put it into the correct type folder)
  2. Implement the proper interface depending on the node type (Trigger, Action, Notification)
  3. 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_executions only 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
Previous
Agent