Business Central

How to Create a Custom AI Agent in Microsoft Dynamics 365 Business Central Using AL Code

By Hetal Tank September 24, 2026 20 min read

Introduction

Microsoft Dynamics 365 Business Central ships with built-in agents such as the Sales Order Agent and the Payables Agent. With the AI Development Toolkit, you can also build your own custom agents that reason over Business Central data, act within defined permission boundaries, and keep a human in the loop for critical decisions.

There are two ways to create a custom agent. The first is the in-product design experience (the Agent Designer), where you envision and prototype an agent in natural language directly inside Business Central. The second is the AL Agent SDK, where you define, register, and configure the agent entirely in code. This post focuses on the AL code approach, which produces an upgrade-safe, source-controlled, and distributable agent. All code and identifiers are grounded in the official Microsoft Learn documentation.

Preview feature

The AI Development Toolkit and the AL Agent SDK are currently in preview, and the documentation is prerelease and subject to change. The Agent SDK for AL is available in sandbox environments for evaluation from version 27.4, and is supported in production environments starting from version 28.1. Validate everything in a sandbox before any production use.

Business Value

Designing an agent in the UI is ideal for experimentation. Building it in AL is what turns a prototype into a production-grade product. Coding the agent gives you:

  • Upgradeability and source control β€” the agent lives in your extension alongside the rest of your AL code, so it is versioned, reviewable, and upgrade-safe.
  • Repeatable deployment β€” the same agent definition can be installed across environments and customers instead of being hand-built in each tenant.
  • Distribution β€” a coded agent can be shipped as a per-tenant extension (PTE) or an AppSource app.
  • Full control of behavior β€” you control setup pages, default profiles and permissions, message validation, output post-processing, KPIs, and user-intervention suggestions.
  • Testability β€” a coded agent can be covered by automated agent tests before it reaches production.

Prerequisites

  • A Business Central sandbox environment on version 27.4 or later for evaluation. Production is supported from version 28.1.
  • Visual Studio Code with the AL Language extension installed.
  • The Custom Agent capability enabled on the Copilot & agent capabilities page.
  • The AGENT – ADMIN permission set to design and create agents. AGENT – DIAGNOSTICS is useful for inspecting execution cost and task logs.
  • Billing for agent capabilities set up in the tenant (consumption billing for agents).
  • Object IDs in the 50100–99999 range for a PTE, plus your registered object/field affix prefix. In the samples below, the prefix My stands in for your registered affix.
  • Working knowledge of AL β€” enums, interfaces, codeunits, tables, and pages.

Requirement

How do you define, register, configure, and activate a custom AI agent entirely in AL code, so that it is upgrade-safe, source-controlled, and ready to distribute as an extension?

Background

Custom agents reuse the same agent runtime that powers the built-in Business Central agents. The runtime enforces permissions, logs every step, and provides a timeline view for human-in-the-loop review. To define an agent in AL, you extend the Agent Metadata Provider enum and implement three core interfaces:

  • IAgentFactory β€” how agent instances are created and configured: setup page, creation rules, default profile, and default permissions.
  • IAgentMetadata β€” runtime metadata for an instance: task-message page, agent-level annotations, and a summary (KPI) page.
  • IAgentTaskExecution β€” how an instance processes tasks: input validation, output post-processing, and user-intervention suggestions.

Each agent type is paired with a registered Copilot capability, which acts as the feature switch and overview entry on the Copilot & AI Capabilities page. The fastest way to scaffold all of this is the built-in template: in VS Code, press Ctrl+Shift+P, run AL: New Project, and choose the Agent template. It generates a skeleton that covers the objects described below, including the correct namespace using statements.

Steps to Build the Agent in AL

Part 1 – Scaffold the project

Start from the Agent template so the project references and namespaces are correct from the outset, then replace the skeleton objects with the implementation below.

Ctrl + Shift + P  β†’  AL: New Project  β†’  choose the “Agent” template

Part 2 – Register the agent type

Extend the Agent Metadata Provider enum to create a unique identifier for your agent type and to point at the three codeunits that implement the required interfaces.

enumextension 50101 “My Agent Metadata Provider” extends “Agent Metadata Provider”

{

    value(50101; “My Agent”)

    {

        Caption = ‘My Agent’;

        Implementation = IAgentFactory = MyAgentFactory,

                         IAgentMetadata = MyAgentMetadata,

                         IAgentTaskExecution = MyAgentTaskExecution;

    }

}

Part 3 – Register the Copilot capability

Every agent type needs a matching Copilot capability. Extend the Copilot Capability enum (the value must be unique across all installed extensions):

enumextension 50100 “My Agent Copilot Capability” extends “Copilot Capability”

{

    value(50100; “My Agent Capability”)

    {

        Caption = ‘My Agent’;

    }

}

Then register the capability when the extension is installed, using an Install codeunit and the Copilot Capability codeunit. This is what makes the agent appear on the Copilot & AI Capabilities page:

codeunit 50101 “My Agent Install”

{

    Subtype = Install;

    Access = Internal;

    trigger OnInstallAppPerDatabase()

    begin

        RegisterCapability();

    end;

    local procedure RegisterCapability()

    var

        CopilotCapability: Codeunit “Copilot Capability”;

        LearnMoreUrlTxt: Label ‘link-to-your-documentation’, Locked = true;

    begin

        if not CopilotCapability.IsCapabilityRegistered(Enum::”Copilot Capability”::”My Agent Capability”) then

            CopilotCapability.RegisterCapability(

                Enum::”Copilot Capability”::”My Agent Capability”,

                Enum::”Copilot Availability”::Preview,

                “Copilot Billing Type”::”Microsoft Billed”,

                LearnMoreUrlTxt);

    end;

}

Switch the availability from Preview to GenerallyAvailable when the agent is production-ready.

Part 4 – Create the setup table and setup page

The setup page is the UI for configuring an instance. Its source table must use the agent’s User Security ID (a Guid) as the primary key β€” the runtime uses this field to pass the agent user ID into the page.

table 50100 “My Agent Setup”

{

    DataClassification = CustomerContent;

    fields

    {

        field(1; “User Security ID”; Guid) { Caption = ‘User Security ID’; }

        field(10; “Custom Property”; Text[100]) { Caption = ‘Custom Property’; }

    }

    keys

    {

        key(Key1; “User Security ID”) { Clustered = true; }

    }

}



The setup page itself uses the ConfigurationDialog page type. Embed the standard Agent Setup Part (it handles name, display name, state, and access control, and shows the required AI disclaimers), add your own fields, and use a temporary source table so nothing is written until the user clicks Update:

page 50100 “My Agent Setup”
{

    PageType = ConfigurationDialog;

    Caption = ‘Set up my agent’;

    SourceTable = “My Agent Setup”;

    SourceTableTemporary = true;

    layout

    {

        area(Content)

        {

            part(AgentSetupPart; “Agent Setup Part”)

            {

                ApplicationArea = All;

                UpdatePropagation = Both;

            }

            group(AdditionalConfiguration)

            {

                Caption = ‘Additional Configuration’;

                field(CustomProperty; Rec.”Custom Property”)

                {

                    ApplicationArea = All;

                    ToolTip = ‘Specifies a custom property for agent-specific configuration.’;

                    trigger OnValidate() begin IsUpdated := true; end;

                }

            }

        }

    }

    actions

    {

        area(SystemActions)

        {

            systemaction(OK)     { Caption = ‘Update’; Enabled = IsUpdated; }

            systemaction(Cancel) { Caption = ‘Cancel’; }

        }

    }

    trigger OnOpenPage() begin InitializePage(); end;

    trigger OnAfterGetRecord() begin InitializePage(); end;

    trigger OnQueryClosePage(CloseAction: Action): Boolean

    begin

        if CloseAction = CloseAction::Cancel then

            exit(true);

        CurrPage.AgentSetupPart.Page.GetAgentSetupBuffer(AgentSetupBuffer);

        SaveSetupRecord(Rec, AgentSetupBuffer);   // persists via “Agent Setup” codeunit

        SaveCustomProperties(Rec);                // writes your custom fields

        exit(true);

    end;

    var

        AgentSetupBuffer: Record “Agent Setup Buffer”;

        IsUpdated: Boolean;

}

Pattern to follow

Use a temporary source table, always include the Agent Setup Part, and persist only on Update. Initialize with Agent Setup.GetSetupRecord and save with Agent Setup.SaveChanges (it returns the agent’s User Security ID). Make sure an error in the page never commits partial data.

Part 5 – Implement IAgentFactory

The factory tells the runtime how to create instances. Return the setup page, control whether users can create instances, and supply the default profile and permissions:

codeunit 50102 MyAgentFactory implements IAgentFactory

{

    procedure GetFirstTimeSetupPageId(): Integer

    begin

        exit(Page::”My Agent Setup”);

    end;

    procedure ShowCanCreateAgent(): Boolean

    var

        AgentSystemPermissions: Codeunit “Agent System Permissions”;

    begin

        // From 28.1, discovery is no longer limited to admins.

        // Restrict creation to agent administrators if desired:

        exit(AgentSystemPermissions.CurrentUserHasCanManageAllAgentsPermission());

    end;

    procedure GetDefaultProfile(var TempAllProfile: Record “All Profile” temporary)

    begin

        TempAllProfile.”Profile ID” := ‘ACCOUNTANT’;

        TempAllProfile.”App ID” := SystemApplicationAppId;

        TempAllProfile.Insert();

    end;

    procedure GetDefaultAccessControls(var TempAccessControlBuffer: Record “Access Control Buffer” temporary)

    begin

        TempAccessControlBuffer.”Role ID” := ‘D365 BASIC’;

        TempAccessControlBuffer.Scope := TempAccessControlBuffer.Scope::System;

        TempAccessControlBuffer.Insert();

    end;

}

Use ShowCanCreateAgent to enforce single-instance agents (return true only when no instance exists), licensing conditions, or code-only creation (always return false). Returning false does not block programmatic creation.

Part 6 – Implement IAgentMetadata

Metadata controls what the user sees at runtime. Point to a task-message page (the default Agent Task Message Card is fine), surface agent-level errors or warnings, and define a summary KPI page:

codeunit 50103 MyAgentMetadata implements IAgentMetadata

{

    procedure GetAgentTaskMessagePageId(AgentUserId: Guid; MessageId: Guid): Integer

    begin

        exit(Page::”Agent Task Message Card”);

    end;

    procedure GetAgentAnnotations(AgentUserId: Guid; var Annotations: Record “Agent Annotation”)

    var

        LicenseMissingMsg: Label ‘Agent license not found.’;

        DetailsTxt: Label ‘A premium license is required. Contact your administrator.’;

    begin

        Clear(Annotations);

        // Validate preconditions such as licensing or configuration:

        // Annotations.Code := ‘LICENSE001’;

        // Annotations.Severity := Annotations.Severity::Error;

        // Annotations.Message := LicenseMissingMsg;

        // Annotations.Details := DetailsTxt;

        // Annotations.Insert();

    end;

    procedure GetSummaryPageId(AgentUserId: Guid): Integer

    begin

        // Shown on hover over the agent icon; supports numeric KPIs only.

        exit(Page::”My Agent Summary”);

    end;

}

Part 7 – Implement IAgentTaskExecution

This is where the agent’s task behavior lives. AnalyzeAgentTaskMessage runs for both incoming and outgoing messages: validate inputs (an Error annotation stops processing; a Warning requests user intervention) and post-process outputs (for example, append a signature).

codeunit 50104 MyAgentTaskExecution implements IAgentTaskExecution

{

    procedure AnalyzeAgentTaskMessage(AgentTaskMessage: Record “Agent Task Message”;

                                      var Annotations: Record “Agent Annotation”)

    begin

        if AgentTaskMessage.Type = AgentTaskMessage.Type::Output then

            PostProcessOutputMessage(AgentTaskMessage)

        else

            ValidateInputMessage(AgentTaskMessage, Annotations);

    end;

    local procedure ValidateInputMessage(AgentTaskMessage: Record “Agent Task Message”;

                                         var Annotations: Record “Agent Annotation”)

    var

        AgentMessage: Codeunit “Agent Message”;

        NotRelevantMsg: Label ‘This message does not appear to be relevant.’;

        DetailsTxt: Label ‘Please provide a message related to the agent”s scope.’;

        MessageText: Text;

    begin

        MessageText := AgentMessage.GetText(AgentTaskMessage);

        if not IsMessageRelevant(MessageText) then begin

            Annotations.Code := ‘RELEVANCE001’;

            Annotations.Severity := Annotations.Severity::Warning;

            Annotations.Message := NotRelevantMsg;

            Annotations.Details := DetailsTxt;

            Annotations.Insert();

        end;

    end;

    local procedure PostProcessOutputMessage(var AgentTaskMessage: Record “Agent Task Message”)

    var

        AgentMessage: Codeunit “Agent Message”;

        SignatureTxt: Label ‘\n\nWritten with the help of AI\n’;

        OldText: Text;

    begin

        OldText := AgentMessage.GetText(AgentTaskMessage);

        AgentMessage.UpdateText(AgentTaskMessage, OldText + SignatureTxt);

    end;

    procedure GetAgentTaskUserInterventionSuggestions(

        AgentTaskUserInterventionRequestDetails: Record “Agent User Int Request Details”;

        var Suggestions: Record “Agent Task User Int Suggestion”)

    var

        ApproveLbl: Label ‘Approve the document’;

        ApproveDescLbl: Label ‘Use when the agent needs approval to proceed.’, Locked = true;

        ApproveInstrLbl: Label ‘Review the details and approve if everything is correct.’;

    begin

        if AgentTaskUserInterventionRequestDetails.Type =

           AgentTaskUserInterventionRequestDetails.Type::Assistance then begin

            Suggestions.Summary := ApproveLbl;

            Suggestions.Description := ApproveDescLbl;   // used internally to judge relevance

            Suggestions.Instructions := ApproveInstrLbl;

            Suggestions.Insert();

        end;

    end;

}

Implement IsMessageRelevant with your own logic; for AI-based relevance checks you can call Azure OpenAI through the Copilot developer tools.

Part 8 – Configure and create instances in code

Each instance is identified by its User Security ID and configured through the Agent codeunit. Instances are normally created from the setup page, but you can also create them programmatically (for example, from a custom wizard):

procedure CreateMyAgent(AgentInstructions: SecretText)

var

    TempAgentAccessControl: Record “Agent Access Control” temporary;

    Agent: Codeunit Agent;

    AgentUserSecurityID: Guid;

begin

    AgentUserSecurityID := Agent.Create(

        Enum::”Agent Metadata Provider”::”My Agent”,

        ‘MYAGENT’,                 // user name (Code[50])

        ‘My Agent Display Name’,

        TempAgentAccessControl);

    Agent.SetInstructions(AgentUserSecurityID, AgentInstructions);

    Agent.Activate(AgentUserSecurityID);

end;

If the instructions are static, store them in a resource file and load them with NavApp.GetResourceAsText. Add “resourceFolders”: [“Resources”] to app.json, then:

procedure AssignStaticInstructions(AgentUserSecurityID: Guid)

var

    Agent: Codeunit Agent;

    Instructions: SecretText;

    InstructionsLbl: Label ‘Instructions.txt’, Locked = true;

begin

    Instructions := NavApp.GetResourceAsText(InstructionsLbl);

    Agent.SetInstructions(AgentUserSecurityID, Instructions);

end;

Important constraint

Agent instances cannot be created from install codeunits, upgrade codeunits, or background sessions β€” creation requires an interactive user session. If you apply static instructions only at creation time, add upgrade code to update older instances.

Part 9 – (Optional) Expose a public API for other apps

For security, the toolkit only lets an app interact with agents defined in its own application. Calls that target an agent from a different app fail. To allow controlled cross-app access, expose public procedures that wrap the Agent codeunit:

codeunit 50110 “My Agent API”

{

    Access = Public;

    procedure SetDisplayName(AgentUserSecurityID: Guid; NewDisplayName: Text[80])

    var

        Agent: Codeunit Agent;

    begin

        Agent.SetDisplayName(AgentUserSecurityID, NewDisplayName);

    end;

    procedure SetActiveState(AgentUserSecurityID: Guid; Activate: Boolean)

    var

        Agent: Codeunit Agent;

    begin

        if Activate then

            Agent.Activate(AgentUserSecurityID)

        else

            Agent.Deactivate(AgentUserSecurityID);

    end;

}

Part 10 – Publish, enable, and activate

  1. Publish the extension to your sandbox (or, from 28.1, a production environment).
  2. Open Copilot & agent capabilities, confirm the Custom Agent capability is enabled, and activate your new capability.
  3. In the role center, choose Agent > Create to launch your setup page, configure the instance, and activate it.
  4. Assign a task to the agent, then use the task timeline and the AGENT – DIAGNOSTICS view to inspect each step and the execution cost.

Important Notes

  • Preview and versioning: the feature is in preview. Sandbox is supported from 27.4; production from 28.1. Object names, signatures, and behavior may change.
  • Least privilege: grant the agent only the permission sets it needs via GetDefaultAccessControls. Never assign SUPER.
  • Tight scope: well-behaved agents do one thing well. Keep instructions focused and validate inputs in AnalyzeAgentTaskMessage.
  • Discovery changed in 28.1: agent discovery and creation are no longer limited to administrators. Use ShowCanCreateAgent (and the Agent Configuration Rights page) to restrict creation where needed.
  • Reversible setup: keep the setup page on a temporary table so an accidental cancel or error never persists partial configuration.
  • Verify against GA, not preview UI: preview interfaces can differ from the shipped experience. Confirm the final flow against the GA build before publishing.

Sandbox verification before publishing

Every code identifier in this post is taken from the official Microsoft Learn samples, but the AL Agent SDK is a preview API. Compile, deploy, and exercise the agent end-to-end in a sandbox to confirm the interface signatures, the Copilot capability registration, and the setup-page behavior on your exact version before relying on it. Two items in particular to confirm hands-on: (1) the namespace using-statements scaffolded by the Agent template for your version, and (2) the summary KPI page wiring, since GetSummaryPageId supports numeric values only.

Conclusion

Building a custom agent in AL turns an AI prototype into a real, supportable Business Central product. By extending the Agent Metadata Provider and Copilot Capability enums and implementing IAgentFactory, IAgentMetadata, and IAgentTaskExecution – backed by a ConfigurationDialog setup page and the Agent codeunit – you get an agent that is upgrade-safe, source-controlled, permission-bounded, and ready to ship as a PTE or AppSource app. Start from the Agent template, scope the agent tightly, test thoroughly in a sandbox, and graduate it to production on 28.1 when it is ready.

References

  1. Coding agents in AL (preview) β€” Microsoft Learn
  2. Define and register an agent programmatically (preview) β€” Microsoft Learn
  3. Configure agents programmatically (preview) β€” Microsoft Learn
  4. Create agent setup pages (preview) β€” Microsoft Learn
  5. Designing and coding agents (preview) β€” Microsoft Learn
  6. Create and activate an agent (preview) β€” Microsoft Learn
  7. The ConfigurationDialog page type (preview) β€” Microsoft Learn

Leave a Comment

Your email address will not be published. Required fields are marked *

Your email will not be published.

Chat on WhatsApp Call Us Now