Install
$ agentstack add skill-louage-frw-agentic-coding-skill-performance ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Skill: AL Performance Optimization
Purpose
Identify, analyze, and fix performance bottlenecks in AL code: inefficient queries, FlowField issues, loop anti-patterns, and data-volume problems in Business Central.
When to Load
This skill should be loaded when:
- A page, report, or batch process is slow or timing out
- CPU profiling is needed to identify hotspots
- A static triage of the codebase is requested for performance issues
- A feature involves large-dataset processing or high-frequency code paths
- AL0896 (circular FlowField) errors appear
- Architecture review requires performance analysis of a new design
Core Patterns
Pattern 1: SetLoadFields + Early Filtering
Always filter before finding, and load only needed fields. Order matters.
// ✅ Correct: SetRange first, SetLoadFields before Find
Item.SetRange("Third Party Item Exists", false);
Item.SetLoadFields("Item Category Code", Description);
if Item.FindSet() then
repeat
// Only "Item Category Code" and Description loaded from DB
until Item.Next() = 0;
// ❌ Wrong: SetLoadFields after SetRange (ignored), loads all fields
Item.SetLoadFields("Item Category Code");
Item.SetRange("Third Party Item Exists", false);
Item.FindFirst();
// ❌ Wrong: No filter, full table scan
procedure GetCustomersByCity(CityFilter: Text): Integer
var
Customer: Record Customer;
Count: Integer;
begin
if Customer.FindSet() then // loads entire Customer table
repeat
if Customer.City = CityFilter then
Count += 1;
until Customer.Next() = 0;
end;
// ✅ Correct: Filter pushed to DB
procedure GetCustomersByCity(CityFilter: Text): Integer
var
Customer: Record Customer;
begin
Customer.SetRange(City, CityFilter);
Customer.SetRange(Blocked, Customer.Blocked::" ");
exit(Customer.Count());
end;
Pattern 2: Set-Based Aggregation (CalcSums / CalcFields)
Avoid manual loops for aggregation, push the sum to the database.
// ❌ Loop accumulation, N rows fetched and processed in AL
procedure GetTotalSales(CustomerNo: Code[20]): Decimal
var
Entry: Record "Cust. Ledger Entry";
Total: Decimal;
begin
Entry.SetRange("Customer No.", CustomerNo);
if Entry.FindSet() then
repeat
Total += Entry.Amount;
until Entry.Next() = 0;
exit(Total);
end;
// ✅ CalcSums, single aggregation query at DB level
procedure GetTotalSales(CustomerNo: Code[20]): Decimal
var
Entry: Record "Cust. Ledger Entry";
begin
Entry.SetRange("Customer No.", CustomerNo);
Entry.CalcSums(Amount);
exit(Entry.Amount);
end;
For FlowFields accessed outside a page context, always call CalcFields before reading:
Customer.SetLoadFields("Balance (LCY)");
Customer.Get(CustomerNo);
Customer.CalcFields("Balance (LCY)"); // required, not auto-calculated in code
Pattern 3: Temporary Tables / Dictionary / List
Pre-load data once, then process in-memory multiple times.
// ✅ Temporary table, structured record data, multi-pass processing
procedure ProcessSalesData(var TempSalesLine: Record "Sales Line" temporary)
var
SalesLine: Record "Sales Line";
begin
SalesLine.SetLoadFields("No.", Quantity, "Unit Price", Amount);
if SalesLine.FindSet() then
repeat
TempSalesLine := SalesLine;
TempSalesLine.Insert();
until SalesLine.Next() = 0;
// Process in-memory, zero additional DB hits
ApplyDiscounts(TempSalesLine);
CalculateTotals(TempSalesLine);
end;
// ✅ Dictionary, key-value lookup cache
procedure CacheCustomerNames(): Dictionary of [Code[20], Text]
var
Customer: Record Customer;
Cache: Dictionary of [Code[20], Text];
begin
Customer.SetLoadFields("No.", Name);
if Customer.FindSet() then
repeat
Cache.Add(Customer."No.", Customer.Name);
until Customer.Next() = 0;
exit(Cache);
end;
// ✅ List, simple value collection
procedure GetBlockedCustomerNos(): List of [Code[20]]
var
Customer: Record Customer;
Result: List of [Code[20]];
begin
Customer.SetRange(Blocked, Customer.Blocked::All);
Customer.SetLoadFields("No.");
if Customer.FindSet() then
repeat
Result.Add(Customer."No.");
until Customer.Next() = 0;
exit(Result);
end;
Pattern 4: Batch Processing and Avoiding Nested DB Calls
Move database operations outside loops; batch writes.
// ❌ Nested DB call inside loop, O(n²) database hits
if MainTable.FindSet() then
repeat
OtherTable.SetRange(Field, MainTable.Field);
if OtherTable.FindSet() then // DB call per iteration
repeat
// process
until OtherTable.Next() = 0;
until MainTable.Next() = 0;
// ✅ Pre-load into temp table, then join in memory
if OtherTable.FindSet() then
repeat
TempOther := OtherTable;
TempOther.Insert();
until OtherTable.Next() = 0;
if MainTable.FindSet() then
repeat
if TempOther.Get(MainTable.Field) then
// process, no DB hit
until MainTable.Next() = 0;
Batch writes, collect changes, write once:
// ✅ Calculate all values first, then single Modify
procedure UpdateCustomerStats(CustomerNo: Code[20])
var
Customer: Record Customer;
TotalBalance: Decimal;
LastPaymentDate: Date;
begin
CalculateCustomerTotals(CustomerNo, TotalBalance, LastPaymentDate);
Customer.SetLoadFields("Balance (LCY)", "Last Payment Date");
if Customer.Get(CustomerNo) then begin
Customer."Balance (LCY)" := TotalBalance;
Customer."Last Payment Date" := LastPaymentDate;
Customer.Modify(true); // single write
end;
end;
Scale-aware processing:
procedure UpdatePricesForItems(var Item: Record Item)
begin
if Item.Count() > 1000 then
UpdatePricesInBatches(Item) // job queue / chunked
else
UpdatePricesDirectly(Item);
end;
Pattern 5: FlowField Optimization (AL0896)
Circular FlowField references cause infinite evaluation (AL0896 error):
❌ Circular dependency:
Table Customer: FlowField "Total Sales" → CalcFormula from Sales Statistics
Table Sales Statistics: FlowField "Customer Balance" → CalcFormula from Customer
Compiler error AL0896: recursive dependency detected
Resolution strategies (in order of preference):
- Break the chain, convert one FlowField to a regular field updated via trigger
- Use CalcSums, replace FlowField with explicit
CalcSumsin code - Restructure, move the calculation to a dedicated codeunit called on demand
- SumIndexFields, use SIFT keys for frequently summed values
Avoid FlowFields:
- With complex nested
CalcFormulaexpressions - Called inside
repeat...untilloops (callCalcFieldsonce, outside loop if possible) - Where the source table is large and unfiltered
Workflow
Step 1: Triage (Static Code Analysis)
Scan the codebase before profiling to identify structural issues:
Patterns to detect manually or with search + problems:
FindSet()/FindFirst()without precedingSetRange/SetFilterSetLoadFieldsplaced afterSetRange(wrong order)- Database calls (
Get,FindSet,FindFirst) insiderepeat...until CalcFieldsinside loops- FlowField
CalcFormulareferencing tables that reference back (AL0896) Commitinside loops (locks + performance risk)- Missing keys for columns used in
SetRange
Severity assessment:
- 🔴 Critical, DB call in loop over large table, circular FlowField, timeout-causing query
- 🟡 High, Missing
SetLoadFieldsin high-frequency path, nested loops - 🟢 Medium, Suboptimal aggregation (loop instead of
CalcSums) - 🔵 Low, Best-practice suggestion, minimal current impact
Step 2: Profile (Runtime Measurement)
Generate CPU profile for runtime bottleneck identification:
Capture a CPU profile in VS Code (VS Code command, not an agent tool)
Analyze profile for:
- Hotspots, procedures with highest cumulative execution time
- Frequency, procedures called most often (especially in loops)
- DB operations, expensive
FindSet/Getcalls - FlowField evaluations, unexpectedly costly
CalcFields
Compare before/after optimizations by re-profiling after each fix.
⚠️ Human Gate, cleanup: Before clearing codelenses, confirm all findings are documented:
Clear profile codelenses in VS Code ← VS Code command (not an agent tool), only after approval
Step 3: Fix
Apply fixes in priority order (critical first). For each fix:
- Apply targeted change (Pattern 1–5 above)
- Rebuild:
al_build - Re-profile to verify improvement
Performance targets: | Metric | Target | |---|---| | Page load | 1,000 records/minute |
Step 4: Document Findings
For significant optimizations, create a triage report at specs/Plans/perf-triage-.md:
# Performance Triage,
**Date**: YYYY-MM-DD
**Analyzed**: [path or objects]
## Findings
| Severity | Location | Issue | Recommendation |
|---|---|---|---|
| 🔴 | `File.al:42` | Nested DB call in loop | Pre-load into TempTable |
| 🟡 | `File.al:87` | Missing SetLoadFields | Add SetLoadFields("No.", Name) |
## Changes Applied
1. [Change], before/after metric
2. [Change], before/after metric
## Remaining Items
[Issues not yet fixed, prioritized]
⚠️ Human Gate, report: Review findings before saving; confirm no sensitive code patterns are exposed.
References
- SetLoadFields, Microsoft Docs
- Performance Best Practices for AL
- AL0896, Circular FlowField
- SumIndexFields (SIFT)
- CPU Profiler
Constraints
- This skill covers analysis and fix patterns, it does NOT duplicate the passive rules in
al-performance.instructions.md(auto-applied to all.alfiles) - Do NOT modify source code automatically during triage, generate report and get approval first
- Do NOT clear profile codelenses without human confirmation
- Do NOT save triage report without human gate review
- For runtime debugging of a specific issue (breakpoints, snapshots) → load
skill-debug.md - For event subscriber performance issues → load
skill-events.md
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Louage
- Source: Louage/frw-agentic-coding
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.