| name | skill-agent-task-patterns |
| description | Agent SDK task integration patterns for Business Central. Triggers on: Agent Task Builder, Agent Task Message Builder, Public API, AssignTask, ExternalId, agent session detection, BindSubscription, TryFunction agent task, multi-turn agent conversation, agent attachment, task lifecycle management, OnPrem-only methods, AddToTask, SetRequiresReview, SetSkipMessageSanitization, runtime availability. |
| argument-hint | Describe the integration pattern needed (Public API, page action, business event, multi-turn, attachment...) |
BC Agent Task Integration Patterns
Knowledge base for every Agent SDK task integration pattern, with runtime-availability matrix and Extension-safe workarounds. For SDK architecture and the 3 interfaces, see skill-agent-toolkit.
References:
API availability matrix (Runtime 17.0 / Platform 27.0)
Critical: not every documented method works in Extension scope today. Verify here before designing.
| Method | Scope | Status | Workaround |
|---|
AddTaskMessage(From, Text) | Extension | ✅ Available | — |
AddAttachment(Name, MediaType, InStream) | Extension | ✅ Available | — |
SetExternalId(Text) | Extension | ✅ Available | — |
SetRequiresReview(true) | OnPrem only | ❌ Ext. blocked | Use IAgentTaskExecution Warning annotation |
SetSkipMessageSanitization(true) | OnPrem only | ❌ Ext. blocked | Pre-format message text upstream |
AddToTask(AgentTask) | Future | ❌ Not in 17.0 | Create a new follow-up task referencing original ExternalId |
Custom Agent.GetCustomAgents() | Future | ❌ Not in 17.0 | Use Agent Setup.OpenAgentLookup() |
OnPrem-only methods are documented but throw at runtime when invoked from a cloud Extension. Treat the workarounds as the canonical Extension pattern.
SDK codeunits — quick reference
| Codeunit | Purpose | Key methods |
|---|
Agent Task Builder | Create new tasks (fluent API) | .Initialize(Guid, Text).SetExternalId(Text).AddTaskMessage(Text, Text).Create() |
Agent Task Message Builder | Build messages with options | .Initialize(From, Text), .AddAttachment(Name, MediaType, InStream), .AddToTask(Record) |
Agent Task | Query/manage existing tasks | GetTaskByExternalId(Guid, Text), CanSetStatusToReady(Record), SetStatusToReady(Record) |
Agent Message | Read/update message text | GetText(Record), UpdateText(Record, Text) |
Agent Setup | Agent lookup + setup management | OpenAgentLookup(Enum, var Guid), GetSetupRecord(...), SaveChanges(...) |
Agent | Core agent operations | SetInstructions(Guid, SecretText), Deactivate(Guid), IsActive(Guid), GetDisplayName(Guid), PopulateDefaultProfile(...) |
Agent Session | Detect agent runtime context | IsAgentSession(var Enum), IsAgentSession(Enum), GetCurrentSessionAgentTaskId() |
8 integration patterns
Pattern A — Public API (standard entry point)
When: every reusable task-creation surface. All other patterns call through here.
Architecture: Public codeunit (Access = Public) + Internal implementation codeunit.
Contract:
// Overload 1: basic task
procedure AssignTask(AgentUserSecurityID: Guid; TaskTitle: Text[150]; From: Text[250]; Message: Text): Record "Agent Task"
// Overload 2: with ExternalId for tracking / multi-turn
procedure AssignTask(AgentUserSecurityID: Guid; TaskTitle: Text[150]; ExternalId: Text[2048]; From: Text[250]; Message: Text): Record "Agent Task"
// Overload 3: with attachments
procedure AssignTaskWithAttachment(AgentUserSecurityID: Guid; TaskTitle: Text[150]; From: Text[250]; Message: Text; var TempAttachments: Record "Agent Task File"): Record "Agent Task"
// Lifecycle
procedure Deactivate(AgentUserSecurityID: Guid)
procedure IsActive(AgentUserSecurityID: Guid): Boolean
Implementation (Impl codeunit, Access = Internal):
local procedure AssignTaskInternal(
AgentUserSecurityID: Guid;
TaskTitle: Text[150];
ExternalId: Text[2048];
From: Text[250];
Message: Text;
var TempAttachments: Record "Agent Task File" temporary): Record "Agent Task"
var
AgentTaskBuilder: Codeunit "Agent Task Builder";
AgentTaskMsgBuilder: Codeunit "Agent Task Message Builder";
ContentStream: InStream;
begin
AgentTaskMsgBuilder.Initialize(From, Message);
if TempAttachments.FindSet() then
repeat
TempAttachments.Content.CreateInStream(ContentStream);
AgentTaskMsgBuilder.AddAttachment(
TempAttachments."File Name",
TempAttachments."Content Type",
ContentStream);
until TempAttachments.Next() = 0;
AgentTaskBuilder := AgentTaskBuilder
.Initialize(AgentUserSecurityID, TaskTitle)
.AddTaskMessage(AgentTaskMsgBuilder);
if ExternalId <> '' then
AgentTaskBuilder.SetExternalId(ExternalId);
exit(AgentTaskBuilder.Create());
end;
Pattern B — Page action (user-initiated)
When: a button on a page sends work to an agent.
Flow: AgentSetup.OpenAgentLookup() → user picks agent → PublicAPI.AssignTask().
trigger OnAction()
var
AgentSetup: Codeunit "Agent Setup";
MyAgentAPI: Codeunit "My Agent Public API";
AgentUserSecurityId: Guid;
begin
if not AgentSetup.OpenAgentLookup(
Enum::"Agent Metadata Provider"::"My Agent", AgentUserSecurityId) then
exit;
MyAgentAPI.AssignTask(
AgentUserSecurityId,
CopyStr(StrSubstNo('Process: %1', Rec.Name), 1, 150),
'LEAD-' + Rec."No.",
CopyStr(UserId(), 1, 250),
StrSubstNo('Please process customer %1 (No. %2).', Rec.Name, Rec."No."));
end;
Pattern C — Business event (automated)
When: a business event (posting, releasing, approval) auto-triggers an agent task.
Non-negotiable rules:
- Always wrap in
[TryFunction] — never block the business event on agent failure.
- Always filter by business condition BEFORE creating the task — not inside the TryFunction.
- Always log failures via
Session.LogMessage with category and GetLastErrorText().
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Sales Document",
OnBeforeReleaseSalesDoc, '', false, false)]
local procedure OnBeforeRelease(var SalesHeader: Record "Sales Header")
begin
if SalesHeader."Document Type" <> SalesHeader."Document Type"::Order then exit;
if not MeetsCondition(SalesHeader) then exit;
if not TryCreateTask(SalesHeader) then
Session.LogMessage('BCA2A-FAIL',
StrSubstNo('Task creation failed for %1: %2', SalesHeader."No.", GetLastErrorText()),
Verbosity::Error, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, 'Category', 'BCA2A');
end;
[TryFunction]
local procedure TryCreateTask(var SalesHeader: Record "Sales Header")
var
PublicAPI: Codeunit "My Agent Public API";
begin
PublicAPI.AssignTask(
GetAgentSecurityId(),
'Validate SO ' + SalesHeader."No.",
'SO-' + SalesHeader."No.",
'System',
BuildMessage(SalesHeader));
end;
Pattern D — Attachment task
When: agent needs to process files (PDFs, images, CSVs, Excel).
Signature: AddAttachment(Name: Text[250]; MediaType: Text; ContentStream: InStream)
procedure CreateTaskWithAttachment(AgentUserId: Guid; var DocumentBlob: Codeunit "Temp Blob")
var
AgentTaskBuilder: Codeunit "Agent Task Builder";
AgentTaskMsgBuilder: Codeunit "Agent Task Message Builder";
ContentStream: InStream;
begin
DocumentBlob.CreateInStream(ContentStream);
AgentTaskMsgBuilder.Initialize(CopyStr(UserId(), 1, 250), 'Review attached document.');
AgentTaskMsgBuilder.AddAttachment('Document.pdf', 'application/pdf', ContentStream);
AgentTaskBuilder := AgentTaskBuilder
.Initialize(AgentUserId, 'Process Document')
.SetExternalId('DOC-1001')
.AddTaskMessage(AgentTaskMsgBuilder);
AgentTaskBuilder.Create();
end;
Common MIME types: application/pdf, image/png, image/jpeg, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (Excel xlsx), text/csv.
When you need a canonical end-to-end example combining attachment + business condition + telemetry + lead handoff, load examples/lead-handoff-with-attachment.md.
Pattern E — Multi-turn conversation
When: continue an existing task with follow-up messages.
Flow: GetTaskByExternalId() → build follow-up → workaround needed (see below) → SetStatusToReady().
Status (Runtime 17.0): AgentTaskMessageBuilder.AddToTask() is not available in Extension scope yet. Until it ships, create a new task that references the original ExternalId:
procedure SendFollowUpTask(AgentUserId: Guid; OriginalExternalId: Text; AdditionalInfo: Text)
var
AgentTaskBuilder: Codeunit "Agent Task Builder";
AgentTaskMsgBuilder: Codeunit "Agent Task Message Builder";
begin
AgentTaskMsgBuilder.Initialize(CopyStr(UserId(), 1, 250), AdditionalInfo);
AgentTaskBuilder := AgentTaskBuilder
.Initialize(AgentUserId, CopyStr('Update for ' + OriginalExternalId, 1, 150))
.SetExternalId(OriginalExternalId + '-UPD')
.AddTaskMessage(AgentTaskMsgBuilder);
AgentTaskBuilder.Create();
end;
When AddToTask becomes available in a future runtime, the canonical pattern will be:
// FUTURE — not yet in Extension scope
AgentTaskRecord := AgentTaskCU.GetTaskByExternalId(AgentSecurityId, ExternalId);
AgentTaskMsgBuilder.Initialize('User', FollowUpText);
AgentTaskMsgBuilder.AddToTask(AgentTaskRecord);
if AgentTaskCU.CanSetStatusToReady(AgentTaskRecord) then
AgentTaskCU.SetStatusToReady(AgentTaskRecord);
Pattern F — Lifecycle management
When: monitor, restart, or stop tasks programmatically.
| Operation | Method |
|---|
| Check active | Agent.IsActive(AgentUserSecurityId) |
| Deactivate | Agent.Deactivate(AgentUserSecurityId) |
| Find by ExternalId | AgentTask.GetTaskByExternalId(AgentUserId, ExternalId) |
| Resume paused task | if AgentTask.CanSetStatusToReady(Rec) then AgentTask.SetStatusToReady(Rec) |
| Display name lookup | Agent.GetDisplayName(AgentUserSecurityId) |
Pattern G — Agent session detection
When: AL code must run only inside an agent session (not in normal user sessions).
// Any agent
var
AgentSession: Codeunit "Agent Session";
Provider: Enum "Agent Metadata Provider";
begin
if not AgentSession.IsAgentSession(Provider) then exit;
// agent-specific logic
end;
// Specific agent
if not AgentSession.IsAgentSession(Enum::"Agent Metadata Provider"::"Lead Qualifier") then exit;
Pattern H — Agent session event binding (performance)
When: event subscribers should be active only during agent tasks, never in normal user sessions.
Architecture: SingleInstance codeunit + BindSubscription on System Initialization.OnAfterInitialization.
codeunit 52120 "My Agent Session Events"
{
Access = Internal;
SingleInstance = true;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"System Initialization",
OnAfterInitialization, '', false, false)]
local procedure OnInit()
var
AgentSession: Codeunit "Agent Session";
begin
if not AgentSession.IsAgentSession(
Enum::"Agent Metadata Provider"::"My Agent") then exit;
GlobalEvents.SetAgentTaskID(AgentSession.GetCurrentSessionAgentTaskId());
if BindSubscription(GlobalEvents) then;
end;
var
GlobalEvents: Codeunit "My Agent Events";
}
Human review without SetRequiresReview
SetRequiresReview is OnPrem-only. To force human review from an Extension, add a Warning annotation in IAgentTaskExecution.AnalyzeAgentTaskMessage:
procedure AnalyzeAgentTaskMessage(
AgentTaskMessage: Record "Agent Task Message";
var Annotations: Record "Agent Annotation")
var
AgentMessage: Codeunit "Agent Message";
MessageText: Text;
begin
MessageText := AgentMessage.GetText(AgentTaskMessage);
if MessageText.Contains('DISQUALIFICATION') then begin
Annotations.Severity := Annotations.Severity::Warning;
Annotations.Message :=
'Disqualification requires human approval. Review before proceeding.';
Annotations.Insert();
end;
end;
Warning annotations trigger the user-intervention flow via GetAgentTaskUserInterventionSuggestions. Error annotations stop the task.
Agent discovery — current state
Until Custom Agent.GetCustomAgents() becomes available in Extension scope, use Agent Setup.OpenAgentLookup() for user selection and Agent.GetDisplayName(Guid) for display:
trigger OnLookup(var Text: Text): Boolean
var
AgentSetup: Codeunit "Agent Setup";
SelectedAgentUserId: Guid;
begin
if AgentSetup.OpenAgentLookup(
Enum::"Agent Metadata Provider"::"Quote Builder", SelectedAgentUserId) then begin
Rec."Target Agent Security ID" := SelectedAgentUserId;
Text := Format(SelectedAgentUserId);
exit(true);
end;
exit(false);
end;
local procedure GetAgentDisplayText(AgentSecurityId: Guid): Text
var
AgentCU: Codeunit Agent;
begin
if IsNullGuid(AgentSecurityId) then exit('');
exit(AgentCU.GetDisplayName(AgentSecurityId) + ' (' + Format(AgentSecurityId) + ')');
end;
Pattern selection guide
| Situation | Pattern(s) |
|---|
| Standard reusable task creation | A (Public API) |
| User clicks button to assign work | B → A |
| Business event triggers agent | C → A |
| Agent processes uploaded files | D + (A, B, or C) |
| Follow-up on existing task | E |
| Monitor / restart / stop tasks | F |
| Code runs only in agent context | G |
| Performance: subscribers only during tasks | H |
| Need to force human review (Extension) | Warning annotation in AnalyzeAgentTaskMessage |
Non-negotiable rules
- Public API is the entry point — all other patterns call through it.
- TryFunction for event-driven creation — never block posting, releasing, approval.
- Filter before creating — business condition checked outside the TryFunction.
- Log failures to telemetry —
Session.LogMessage with category and error text.
- Mensaje contiene todo el contexto — el agente solo sabe lo que le pasas por el mensaje + attachments.
- ExternalId follows
{PREFIX}-{No.} — SO-1001, LEAD-001, EMAIL-{threadId}. Never GUIDs or auto-increments.
- Use
Agent Task Message Builder for attachments — never manipulate the underlying records directly.
- Verify availability against the matrix above before designing — don't ship code that relies on OnPrem-only methods.
Examples
Load an example only when you need its concrete template:
- When implementing an end-to-end attachment + business condition + telemetry + handoff flow, load
examples/lead-handoff-with-attachment.md.
- When wiring Pattern H with a companion Events codeunit, load
examples/session-events-binding.md.