Skip to content

[Payables Agent] Agent-driven line matching - #7546

Open
dayland wants to merge 41 commits into
mainfrom
features/607916-payables-agent-line-matching
Open

[Payables Agent] Agent-driven line matching#7546
dayland wants to merge 41 commits into
mainfrom
features/607916-payables-agent-line-matching

Conversation

@dayland

@dayland dayland commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds agent-driven line matching to the Payables Agent. When the ECS feature flag PAAgentDrivenLineMatching is set to agent_driven, the agent resolves each draft purchase line itself by navigating BC pages, instead of relying on the AL PrepareDraft matching pipeline. All new behavior is gated behind that flag, so default (non-treatment) behavior is unchanged.

The feature spans two apps: the E-Document app (new navigation surfaces + historical-line data loader) and the Payables Agent app (new agent instructions prompt, page customizations, and prompt gating).

Feature gating

A single ECS flag, read via Feature Configuration.GetConfiguration('PAAgentDrivenLineMatching'), controls three things when it equals agent_driven:

  1. Prompt selection (Payables Agent Setup, codeunit 3307) — loads the new PayablesAgent-AgentInstructions-AgentDriven.md instructions instead of the standard prompt.
  2. PrepareDraft skip (Prepare Purchase E-Doc. Draft, codeunit 6125) — bypasses the AL PrepareDraft helper so the agent drives matching rather than the built-in pipeline.
  3. Line-action visibility (E-Doc. Purchase Draft Subform, page 6183) — the new line lookup actions are shown only when the flag is enabled.

E-Document app changes

  • E-Doc. Purchase Draft Subform (page 6183) — five new line-scoped (Scope = Repeater) lookup actions, flag-gated: Text-to-Account Mappings, Historical Purchase Lines, Chart of Accounts, Items, and Deferral Templates. The existing Item References action is also scoped to the repeater. Adds an EnsureEDocumentPurchaseHeader helper.
  • E-Doc. Hist. Line Data Loader (codeunit 6244, new) — loads up to 5,000 posted purchase invoice lines from the last year into a temporary table, ordered by relevance: same-vendor matches first, then cross-vendor matches, then remaining same-vendor and cross-vendor fill. Matching is by exact product code, exact description, and LLM-generated similar descriptions (E-Doc. Similar Descriptions). Allocation-account lines display the allocation account name.
  • E-Doc. Historical Lines List (page 6186, new) — read-only temporary list page presenting the loaded historical lines.
  • app.json — extends idRanges with 6244-6249 for the new objects.

Payables Agent app changes

  • PayablesAgent-AgentInstructions-AgentDriven.md (new) — agent-driven instructions implementing a collect-then-synthesize line-matching strategy across six sources (Item References, Text-to-Account Mappings, Historical Purchase Lines, Chart of Accounts, Items, Deferral Templates), including a product-identifier rule, recency weighting, conflict detection, and an updated sitemap.
  • Six new page customizations — locked-down, read-only views (ClearActions/ClearLayout, no insert/modify/delete) exposing only the fields the agent needs: Chart of Accounts, Deferral Template List, Hist. Purchase Lines, Item Reference Entries, Items, and Text-to-Account Mapping.
  • PA EDoc Purchase Draft Subform customization — exposes new line fields (Line Type, No., Item Reference No., Deferral Code, Unit of Measure) and the six new line actions.
  • Payables Agent profile — registers the six new page customizations.

Related NAV PR

NAV PR #250770 -- [Payables Agent] Agent-driven line matching — test scenarios (Slice #607916)

Related Work Items

AB#607916

Test plan

  • E-Document and Payables Agent apps compile cleanly
  • With flag unset, behavior is unchanged (standard prompt + AL PrepareDraft pipeline)
  • With flag = agent_driven, agent-driven prompt loads, PrepareDraft is skipped, and line actions are visible
  • Historical Purchase Lines loads with priority ordering (same-vendor first) and 5,000-record cap
  • All six line-level navigation actions accessible from the draft subform
  • Validated via DME AI-evals (PA-METRICS / PA-TINY) across baseline, control, and treatment variants on gpt4.1 and gpt5.3

@github-actions github-actions Bot added the AL: Apps (W1) Add-on apps for W1 label Apr 6, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Apr 6, 2026
Add IPrepareDraftGuard interface to allow skipping the AL-based
PrepareDraft pipeline when matching is handled by the agent.

- New IPrepareDraftGuard interface with SkipPrepareDraft() method
- Default implementation returns false (AL pipeline runs normally)
- Guard check at top of PrepareDraft procedure
- Added to E-Doc. Proc. Customizations enum with DefaultImplementation
- Extended app.json idRanges (6243-6249)

New Historical Purchase Lines page and smart data loader:
- Priority-based loader: same-vendor first, then cross-vendor
- Matching by exact product code, exact description, LLM similar descriptions
- Per-line results (different lines show different historical matches)
- Capped at 5,000 records

New line-level navigation actions on Draft Subform (Scope = Repeater):
- Item References, Text-to-Account Mappings, Historical Purchase Lines,
  Chart of Accounts, Deferral Templates
- EnsureEDocumentPurchaseHeader guard for agent session compatibility
- Allocation Account No. field on Historical Lines page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dayland
dayland force-pushed the features/607916-payables-agent-line-matching branch from b52fc8c to ca778d8 Compare April 15, 2026 11:07

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AL Documentation Audit

Documentation gaps were detected in the following apps:

  • E-Document-Core: 29% documentation coverage

To generate documentation, run /al-docs init or /al-docs update using GitHub Copilot CLI or Claude Code.
This review is for awareness to help keep documentation in sync with code changes. It is okay to dismiss this request.

dayland-ms and others added 4 commits May 5, 2026 12:44
Add IPrepareDraftGuard interface to allow skipping the AL-based
PrepareDraft pipeline when matching is handled by the agent.

- New IPrepareDraftGuard interface with SkipPrepareDraft() method
- Default implementation returns false (AL pipeline runs normally)
- Guard check at top of PrepareDraft procedure
- Added to E-Doc. Proc. Customizations enum with DefaultImplementation
- Extended app.json idRanges (6243-6249)

New Historical Purchase Lines page and smart data loader:
- Priority-based loader: same-vendor first, then cross-vendor
- Matching by exact product code, exact description, LLM similar descriptions
- Per-line results (different lines show different historical matches)
- Capped at 5,000 records

New line-level navigation actions on Draft Subform (Scope = Repeater):
- Item References, Text-to-Account Mappings, Historical Purchase Lines,
  Chart of Accounts, Deferral Templates
- EnsureEDocumentPurchaseHeader guard for agent session compatibility
- Allocation Account No. field on Historical Lines page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Stale Status Check Deleted

The Pull Request Build workflow run for this PR was older than 72 hours and has been deleted.

📋 Why was it deleted?

Status checks that are too old may no longer reflect the current state of the target branch. To ensure this PR is validated against the latest code and passes up-to-date checks, a fresh build is required.


🔄 How to trigger a new status check:

  1. 📤 Push a new commit to the PR branch, or
  2. 🔁 Close and reopen the PR

This will automatically trigger a new Pull Request Build workflow run.

dayland-ms and others added 2 commits June 1, 2026 15:33
Adds an 'Items' action on the draft subform that opens the Item List
(page 31) filtered to non-blocked items. This supports the Payables
Agent's new Items matching source (Source E) where the agent can look
up items by name/description when no Item Reference exists for the vendor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Stale Status Check Deleted

The Pull Request Build workflow run for this PR was older than 72 hours and has been deleted.

📋 Why was it deleted?

Status checks that are too old may no longer reflect the current state of the target branch. To ensure this PR is validated against the latest code and passes up-to-date checks, a fresh build is required.


🔄 How to trigger a new status check:

  1. 📤 Push a new commit to the PR branch, or
  2. 🔁 Close and reopen the PR

This will automatically trigger a new Pull Request Build workflow run.

dayland-ms and others added 9 commits June 16, 2026 11:50
…arePurchaseEDocDraft

The IPrepareDraftGuard interface dispatch pattern is replaced with a direct
FeatureConfiguration.GetConfiguration() check inside PreparePurchaseEDocDraft,
mirroring how MLLM extraction (EDocPDFFileFormat) works.

Changes:
- PreparePurchaseEDocDraft.Codeunit.al: replace interface dispatch with inline
  ECS check (PAAgentDrivenLineMatching = 'agent_driven')
- EDocProcCustomizations.Enum.al: remove IPrepareDraftGuard from implements
  clause and DefaultImplementation
- Delete EDocDefPrepDraftGuard.Codeunit.al (default always-false guard)
- Delete IPrepareDraftGuard.Interface.al
- app.json: remove id 6243 from range (was EDocDefPrepDraftGuard)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update agent instructions with full line matching guidance (6 sources,
  collect-then-synthesize, HITL confidence, reason field)
- Update PAEDocPurchaseDraftSubform page customization to expose all 6
  source actions (Items, ItemRef, HistoricalLines, TTA, GLAccount, Deferral)
- Add 6 new PA profile page customizations:
  PAItems, PAChartOfAccounts, PADeferralTemplateList,
  PAHistPurchaseLines, PAItemReferenceEntries, PATextToAccountMapping

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 6 new page customizations to PayablesAgent.Profile that support
agent-driven line matching source navigation:
- PA Item Reference Entries
- PA Text-to-Account Mapping
- PA Chart of Accounts
- PA Hist. Purchase Lines
- PA Deferral Template List
- PA Items

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dayland-ms and others added 3 commits July 23, 2026 12:14
…hing flag

Select the Payables Agent instructions prompt based on the same ECS
feature flag that gates AL PrepareDraft line matching:

- Control (PAAgentDrivenLineMatching != agent_driven): original prompt
  (PayablesAgent-AgentInstructions.md, restored to main) + AL PrepareDraft.
- Treatment (agent_driven): new agent-driven prompt
  (PayablesAgent-AgentInstructions-AgentDriven.md), AL PrepareDraft skipped.

SetAgentInstructions now reads Feature Configuration and loads the
matching prompt resource.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aeb37867-e4e2-4aa3-ac17-0817b685dab1
…gentDrivenLineMatching flag

The five lookup actions added for agent-driven line matching
(Text-to-Account Mappings, Historical Purchase Lines, Chart of
Accounts, Items, Deferral Templates) on the E-Doc. Purchase Draft
Subform are now shown only when the PAAgentDrivenLineMatching ECS
feature is in the agent_driven treatment, matching the prompt and
PrepareDraft gating. OnOpenPage reads Feature Configuration and drives
each action's Visible via AgentDrivenLineMatchingEnabled.

Note: the Payables Agent profile customization statically forces these
actions visible (profile customizations cannot hold conditional logic),
so the agent still sees them; the agent's behavior remains gated by the
prompt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aeb37867-e4e2-4aa3-ac17-0817b685dab1
…ension keys

- LogError (0000SEO) now emits GetLastErrorText(true) only, dropping the vendor
  number and the unsanitized error text so no vendor-identifying or customer-bearing
  content is sent to telemetry. Removes the now-unused HistoricalDataLoadFailedErr label.
- Rename 0000SEN custom-dimension keys to space-free PascalCase (RecordsLoaded,
  VendorMatchingScope, MaxRecordsLimit, LimitReached) for a stable telemetry schema.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aeb37867-e4e2-4aa3-ac17-0817b685dab1
Comment thread src/Apps/W1/PayablesAgent/app/PayablesAgent.Codeunit.al Outdated
…oncile

When the best-effort instruction reconciliation TryFunction fails, capture the
redacted GetLastErrorText(true) into the 0000SEL telemetry custom dimensions so
Key Vault, prompt-loading, or instruction-update failures remain diagnosable in
production, then remove the transient dimension before the subsequent event.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aeb37867-e4e2-4aa3-ac17-0817b685dab1
Comment thread src/Apps/W1/PayablesAgent/app/Profile/PayablesAgent.Profile.al
@alexei-dobriansky

Copy link
Copy Markdown
Contributor

Agentic PR Review - Round 4

Recommendation: Request Changes

What this PR does

Five commits landed since round 3. They add usage and error telemetry to the historical-line loader (wrapped in a [TryFunction] so a load failure no longer silently aborts the action), sanitize AI-generated filter terms before passing them to SetFilter (removes AL filter metacharacters & | ( ) < > = ? @ * . ' " %), add a TestField guard on the vendor number before invoking the loader action, replace a per-row database re-read in OnAfterGetRecord with a direct Rec assignment, add a prompt-injection hardening note to the agent instructions, and wrap EnsureAgentInstructionsMatchConfiguration in a best-effort [TryFunction] so a Key Vault failure during instruction reconciliation does not abort e-document import.

All five changes are correct and improve the reliability and security of the agent-driven path. However, the standing S2 blocker is still in the diff: PreparePurchaseEDocDraft.Codeunit.al exits immediately (before calling PrepareDraftHelper.PrepareDraft) when PAAgentDrivenLineMatching = agent_driven, skipping VAT product posting group resolution and description enrichment for every draft line. Round 4 commits do not touch that file.

Status of previous suggestions
ID Title Status Evidence
S1 Historical page cannot be customized Disputed No new activity. Dispute remains valid: pagecustomization objects are not blocked by Extensible = false; clean DME pipeline builds confirmed in round 3.
S2 Prepare Draft skip loses required draft work Not addressed PreparePurchaseEDocDraft.Codeunit.al at head 9d42f08 still exits before calling PrepareDraftHelper.PrepareDraft() in agent-driven mode. VAT product posting group resolution and description enrichment are still skipped.
S3 Historical list hides posting date Addressed Fixed in round 3.
S4 Add coverage for deterministic gates Not addressed No test code added in rounds 3 or 4.
S5 Agent sitemap describes historical lines as cross-vendor but loader is vendor-scoped Not addressed Round 4 adds a prompt-injection hardening line to the instructions file but does not update the sitemap bullet that still says "across all vendors".
New observations (commits since round 3)

None — all five commits address prior feedback or add supporting diagnostics. No new issues found.

Risk assessment and necessity

Risk: S2 remains the financial-data integrity blocker. On any treatment tenant where PAAgentDrivenLineMatching = agent_driven, the early exit in PreparePurchaseEDocDraft.PrepareDraft means every e-document draft is created without the PrepareDraftHelper.PrepareDraft step that resolves VAT product posting groups and enriches line descriptions. The agent prompt does not fill VAT fields, so VAT amounts can be blank or wrong on every finalized purchase invoice in agent-driven mode. S4 compounds this: because there are no automated tests for the agent-driven path, this failure mode has no regression coverage. The round 4 changes do not increase or decrease this risk.

The new telemetry, filter sanitization, vendor guard, and prompt-injection note are all positive additions with no regression surface.

Necessity: Unchanged from round 1. The feature goal (agent-driven line matching for Payables) is valid and the scope is appropriate. Resolving S2 before enabling the treatment on any tenant is the remaining gate.


[AI-PR-REVIEW] version=1 promptVersion=1 system=github pr=7546 round=4 by=alexei-dobriansky at=2026-08-11T11:00:17Z lastSha=9d42f0851d60c25d66e3c8e281f6b65d92b84829 reviewKey=5de896b47462d6d6bd224e7b21bb75be1b664492cb017e5baf8d8c2870623aa2 suggestions=S1@9928801b:disputed,S2@7e1c8c09:notaddressed,S3@843f93d1:addressed,S4@4166c3af:notaddressed,S5@612fae2b:notaddressed parentRound=3

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: aeb37867-e4e2-4aa3-ac17-0817b685dab1
@alexei-dobriansky

alexei-dobriansky commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Agentic PR Review - Round 5

Recommendation: Request Changes

What this PR does

Since round 4, the PR branch only merged the latest main. I found no new author-owned non-merge commits to review after applying the round-2+ attribution guard, so the current assessment is based on the net PR diff at the new head.

The feature goal is still coherent: Payables Agent gets an ECS-gated, agent-driven line matching flow with new lookup pages and a historical purchase line tool. The remaining blocker is unchanged: the agent_driven path still exits before the existing purchase draft preparation helper runs, so it skips non-matching preparation work that the agent prompt does not replace.

Status of previous suggestions
ID Title Status Author response
S1 Historical page cannot be customized Disputed Dispute remains valid: page customizations are not blocked by Extensible = false.
S2 Prepare Draft skip loses required draft work Not addressed PreparePurchaseEDocDraft.Codeunit.al still exits before PrepareDraftHelper.PrepareDraft() when PAAgentDrivenLineMatching = agent_driven.
S3 Historical list hides posting date Addressed The historical list and the Payables Agent page customization now expose Posting Date.
S4 Add coverage for deterministic gates Not addressed No automated test changes are present in the current PR diff.
S5 Agent sitemap describes historical lines as cross-vendor but loader is vendor-scoped Not addressed The prompt still says Historical Purchase Lines are loaded "across all vendors", while the loader remains vendor-scoped.
New observations (commits since round 4)

None. The only new branch commit is a merge from main; I did not attribute base-branch changes to this PR.

Risk assessment and necessity

Risk: S2 is still the blocking data-integrity risk. In treatment tenants, the early exit can leave purchase draft lines without existing prepare-draft outputs such as VAT product posting group resolution and related enrichment, and the agent-driven prompt does not fill those fields. The current GitHub build is also red because fail-on-new-warning validation now reports warnings, so the PR is not in a merge-ready validation state.

Necessity: The slice remains valid and useful. The feature should preserve the existing non-matching purchase draft preparation guarantees while moving only line-account matching to the agent-driven flow.


[AI-PR-REVIEW] version=1 promptVersion=2 system=github pr=7546 round=5 by=alexei-dobriansky at=2026-08-12T08:19:41Z lastSha=32d1a9285c8ba53872a6f7f2ac115f001a254639 reviewKey=6a20a0d5e62228ef03f3449ffeeac70ece7c78e09d0d9b4c0f663c8f9340b00e suggestions=S1@9928801b:disputed,S2@7e1c8c09:notaddressed,S3@843f93d1:addressed,S4@4166c3af:notaddressed,S5@612fae2b:notaddressed parentRound=4

@dayland
dayland dismissed Jose Antonio Garcia Garcia (Jose-agg)’s stale review August 13, 2026 12:31

Jose is on holiday, asking other team members to review in his place.

ventselartur
ventselartur previously approved these changes Aug 13, 2026
/// similar descriptions), then any remaining lines for the same vendor.
/// The search is scoped to the draft's vendor; no cross-vendor history is loaded.
/// </summary>
procedure LoadHistoricalLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20]; ProductCode: Text[100]; Description: Text[100])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Breaking\ Changes}$

The new internal codeunit 'E-Doc. Hist. Line Data Loader' exposes LoadHistoricalLines and MaxHistoricalRecords with default public access even though both are only used inside the E-Document app. That turns implementation-detail routines into callable contracts for friend apps. Mark the cross-object entry point 'internal' and keep the helper 'local' unless you intend to support them as stable APIs.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4

mynjj
Joshua (mynjj) previously approved these changes Aug 14, 2026
Avoid refreshing agent instructions from the e-document import callback, and rely on the Agent Framework security prompt instead of duplicating prompt hardening.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0fedf3ac-fbae-4f75-9c22-f5b7dfadda9b
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0fedf3ac-fbae-4f75-9c22-f5b7dfadda9b
PrepareDraftHelper: Codeunit "EDoc Prepare Purch. Draft";

procedure PrepareDraft(EDocument: Record "E-Document"; EDocImportParameters: Record "E-Doc. Import Parameters"): Enum "E-Document Type"
var

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Agent}$

The tenant-level experiment key 'PAAgentDrivenLineMatching' and its treatment value 'agent_driven' are declared as separate, independently-maintained Label literals in two different apps: src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al (AgentDrivenLineMatchingTok/AgentDrivenTreatmentTok) and src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Codeunit.al (same token names/values, used both to select the prompt variant and to build the config-hash fingerprint). Both call sites must agree on the exact key and treatment string for the feature to behave consistently (EDocument decides whether to skip its own draft-prep logic based on the same flag PayablesAgent uses to pick the prompt and to detect instruction drift). Because the string is duplicated rather than shared from one owning location, a future rename or value change in one app silently desynchronizes from the other with no compile-time signal, causing PrepareDraft's branch and the agent's active prompt/config-hash to diverge. Recommend hoisting the experiment key (and its treatment value) into a single shared/public constant or procedure that both apps reference, or having EDocument call a published PayablesAgent API for the check instead of re-declaring the literal.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

@github-actions

Copy link
Copy Markdown
Contributor

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

The new local procedure EnsureEDocumentPurchaseHeader raises a plain Error when the purchase header for the current e-document line cannot be found. This is an internal invariant failure (the row the page is showing has no corresponding header) that the user cannot correct, so it should be classified as ErrorType::Internal rather than a client-facing validation error.

Knowledge:

Line mapping was unavailable, so this was posted as an issue comment.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

if TotalLoaded >= MaxHistoricalRecords() then
exit;

PurchInvLine.Reset();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance}$

The historical-line loader (E-Doc. Hist. Line Data Loader) builds several non-primary-key 'Purch. Inv. Line' scans filtered by vendor/date/type/description and caps the result at 5000 rows, but never selects a key matching the actual access pattern (e.g. vendor + posting date) before FindSet(). Without SetCurrentKey aligned to the filters, the engine may scan a wider, arbitrarily ordered slice and the 5000-row cap can be reached before the most relevant historical lines are found.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.33.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 Finance GitHub request for Finance area Ownership: Manual Preserve the manually selected team ownership

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants