Agent workflow
SkillMediaMulti-agent workflow contract for this repo, orchestrator vs implementation agents, GitHub label state machine, PR lifecycle, the read-only al-language corpus and how a run resolves it, and the expectations-manifest path for OOS-by-design tests. Use when acting as orchestrator/impl-agent without the dedicated sub-agent, when triaging the issue/PR queue manually, or when deciding whether an issue is a runner gap, an OOS-by-design declaration, or a corpus bug to upstream.
Use Agent workflow in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Agent workflow and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Agent workflow skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by stefanmaron/businesscentral.al.runner in .claude/skills/al-runner-workflow/SKILL.md and read by Ahel’s review.
This repository uses a multi-agent workflow. Agents are identified by GitHub issue / PR labels.
Identity
Your agent identity (impl-1, impl-2, orchestrator) is given in the task prompt. It maps to a GitHub label (agent: impl-1, etc.).
Implementation agent loop
If you are impl-1 or impl-2:
-
Check for issues labeled
agent: <your-id>ANDstatus: in-progress— that is your active issue if one exists. -
If no active issue, find the next unclaimed issue:
status: readywith noagent:label and no human assignee, excluding anytype: trackerissue — taking the highestpriority:first (urgent>high>medium>low), oldest first within a priority. Claim it: addagent: <your-id>,status: in-progress, assignee@me. Removestatus: ready.gh issue list --repo StefanMaron/BusinessCentral.AL.Runner --state open --limit 200 \ --label "status: ready" --json number,title,labels,assignees,createdAt \ --jq '[.[] | select((.assignees|length)==0) | select([.labels[].name] | map(startswith("agent:")) | any | not) | select([.labels[].name] | map(. == "type: tracker") | any | not) | {n:.number, t:.title, p:([.labels[].name | select(startswith("priority:"))] | first // "priority: (none)"), c:.createdAt}] | sort_by(({"priority: urgent":0,"priority: high":1,"priority: medium":2, "priority: low":3}[.p] // 9), .c) | .[] | "\(.p)\t#\(.n)\t\(.t[:70])"'Going lower than the top needs one line on the issue, and nothing else — no approval, no waiting. Folding siblings into a PR you are already writing is exempt: those may be any priority (
batch-sibling-issues-by-file.md). For any new pick below the highest available, comment on the issue you took saying why — blocked on artifacts, needs a corpus verdict, the higher one is stalled behind a PR. The comment is the audit trail, and the set of reasons that accumulates is how the real exception list gets written from evidence instead of guessed.An unprioritised
status: readyissue sorts last, not first: it has not been ranked, so it has not been judged worth displacing ranked work. Say so and take it only when the ranked queue is empty, or ask the triager to rank it.A
type: trackerissue is a set of related items, not one unit of work, and is excluded from the queue entirely —status: readyon one means the items are available to work, not this issue closes. File a narrow issue per item and take that. A PR touching a tracker declaresPart of #N, neverCloses #N, with the marker starting the line (branch-and-pr.md). Nothing in CI catches the mistake:reject-deferred-scopekeys on where a deferral points, not on whether a PR finished what it closed, so aCloseson a tracker merges green and shuts a multi-item record having addressed one. -
Verify you understand the AL pattern. If the issue body lacks a runnable AL reproducer or a specific failing assertion, do not guess. Add
status: needs-input, ask the reporter, stop (.claude/rules/no-assumption-fixes.md). -
Branch
agent/<your-id>/issue-<N>, pushed, with a draft PR carryingCloses #N, the labelagent: <your-id>, assigned to@me(.claude/agents/impl-agent.md, Step 2). -
Implement red → green (
.claude/rules/tdd.md). The right test depends on what kind of issue this is — see "Issue kinds" below. -
Rewrite the body, mark the draft ready, label
status: review-ready. -
Return. The coordinator reads CI and resumes you, or dispatches a fresh agent onto the PR, when it goes red.
-
Auto-merge fires when approved + green (
allow_auto_merge=trueis a repo setting, not visible in the checkout). Return to step 1.
One issue at a time per impl agent. No second claim while a PR is open.
Issue kinds — where does the test go?
| Kind | Test lives in | Notes |
|---|---|---|
| Runner gap on an in-scope AL pattern | An al-language test that fails before, passes after. If the corpus does not cover the pattern, write the upstream test first in StefanMaron/BusinessCentral.AL.Language.Tests and cite it with a Corpus-PR: line — there is no pin to bump (#3737); that line is what makes your PR's matrix resolve the corpus at your corpus PR's branch head. | Most common. The corpus must validate against real BC before the runner can claim parity. |
| Test is OOS-by-design (SMTP, real HTTP, …) | New entry in tests/expectations/oos-<area>.json per docs/expectations.md. | The runner must throw RunnerOutOfScopeException with the reason from docs/scope.md. .claude/rules/loud-failures.md. |
| In-scope but not yet implemented | tests/expectations/known-gaps-<area>.json entry linking the GH issue. | Transient — entry is removed when the gap is closed. |
| Runner-specific positive assertion | New suite under tests/runner-extras/. | E.g. "calling X throws OOS with reason Y". |
| Corpus bug (test mis-asserts something real BC also fails) | Upstream PR against the corpus. | Do not edit the corpus from this repo. |
tests/al-language/ is read-only, and since #3737 it is not in git at all: it is resolved per
run — at master, or at the head of the corpus pull request a PR body's Corpus-PR: line names
— and every run prints corpus: <full sha> (<ref>). Locally, tools/corpus-checkout.py puts it
there. Never edit it. See .claude/rules/al-language-submodule.md.
Orchestrator loop (priority order)
If you are orchestrator:
- PRs first. Find PRs labeled
status: review-ready. CI green + no unresolved threads + noCHANGELOG.mdin diff + no edits undertests/al-language/+ relevant expectation entries / runner-extras tests cited in the body + every condition of the arming list inorchestrating-a-session("A reviewer that approves a PR arms auto-merge") → approve and squash-merge (gh pr merge --auto --squash). Whereghis absent (web and remote sessions,.claude/rules/github-access.md) the verdict cannot be read: review, comment, and hold. Otherwise leave actionable review comments. - Unblock. Review
status: blockedissues; resolve if possible. - Triage of new untriaged issues is owned by the
triagersub-agent (Opus), which runs at the start of a cycle and setsstatus: readyvs.status: needs-input. The orchestrator does not triage.
Workers self-select from the status: ready queue in priority: order, skipping any type: tracker issue. The orchestrator does not assign issues to specific workers — priority is what orders the queue, not a dispatcher.
GitHub access: operation → tool map
.claude/rules/github-access.md covers detecting whether gh is available.
Once detected, here is which tool covers which operation:
| Operation | gh | MCP tool |
|---|---|---|
| Who am I | gh api user --jq .login | mcp__github__get_me |
| List issues | gh issue list | mcp__github__list_issues |
| Read issue / its comments | gh issue view | mcp__github__issue_read (get, get_comments) |
| Label / assign / close issue | gh issue edit, gh issue close | mcp__github__issue_write (method: update) |
| Comment on issue or PR | gh issue comment, gh pr comment | mcp__github__add_issue_comment (PRs too — pass the PR number) |
| List PRs | gh pr list | mcp__github__list_pull_requests |
| PR detail / diff / files / CI | gh pr view, gh pr diff, tools/ci-wait.py <N> --timeout 0 | mcp__github__pull_request_read (get, get_diff, get_files); get_check_runs reads checks but is not the verdict (ci-wait.py needs gh), so without gh a PR is reviewed and held, never armed |
| Merge a PR | gh pr merge --squash | mcp__github__merge_pull_request (merge_method: "squash") |
| Open a PR | gh pr create | mcp__github__create_pull_request |
| Label a PR | gh pr edit --add-label | mcp__github__update_pull_request |
| Edit a PR body | tools/pr-body.py <N> --replace OLD NEW (never a hand-rolled fetch/modify/upload — see .claude/rules/branch-and-pr.md) | mcp__github__update_pull_request after reading the body back, with the same care |
| Read failing CI logs | gh run view --log-failed — empty output is a refusal, not an empty log; see .claude/rules/ci-verdicts.md §3 | mcp__github__get_job_logs (failed_only: true, return_content: true) |
| Search for duplicates | gh issue list --search | mcp__github__search_issues |
Any agent definition granting Bash for GitHub work must also grant the
mcp__github__* tools it needs (plus ToolSearch, since those tools are
deferred) in its tools: frontmatter — otherwise the fallback is unavailable
precisely where it is needed.
Concurrency with anyone who is not you
The GitHub assignee field is the boundary between your account's work and everyone else's — a human maintainer or another account's agent pool (check-open-prs-before-claiming.md):
- When an impl agent claims an issue, it assigns
@mealongside the labels. PRs the bot opens are also assigned to@me. - Every agent (triager, orchestrator, impl) skips any issue or PR whose assignee is a user other than
@me. Which side of the boundary matters, not who is on it: the login decides, never whether the claimant looks like a person. - A human can take over an in-flight agent task by re-assigning the issue / PR; agents back off on their next pass.
Hard rules (all agents)
- Never push directly to
main— always via PR. - Never touch an issue or PR assigned to a non-
@meuser. - Impl agents never self-assign work outside the orchestrator queue.
- Branch name:
agent/<agent-id>/issue-<N>— no exceptions. - PR body must contain
Closes #N. - Set
status: review-readyon the PR when you mark it ready; the coordinator reads CI. - One PR at a time per impl agent.
- Never edit
CHANGELOG.md. - Never edit a file inside
tests/al-language/— it is gitignored and resolved per run, so a diff cannot legitimately reach it. Cite a corpus change with aCorpus-PR:line instead (al-language-submodule.md). - Honour the precompiled-DLL contract (
.claude/rules/precompiled-dll-respect.md) and loud-failures rule (.claude/rules/loud-failures.md). --repo StefanMaron/BusinessCentral.AL.Runneron everyghcommand when running outside the repo's default.
Label state machine
| Label | Meaning |
|---|---|
status: ready | Unclaimed, ready for an impl agent to pick up |
status: in-progress | Currently being worked on by the labeled agent: * |
status: review-ready | PR is marked ready; the orchestrator reads CI and reviews |
status: blocked | Needs human or cross-issue input |
status: needs-input | Issue body too thin to identify root cause; reporter must elaborate (set by triager — see no-assumption-fixes) |
priority: urgent | Blocks other work right now: red main, a broken gate, a rule actively giving wrong guidance |
priority: high | Large measured blast radius, or blocks a cluster of other issues |
priority: medium | A real defect with bounded scope; nobody is blocked |
priority: low | Cosmetic, a stale pointer, or a nice-to-have |
type: tracker | A set, not one unit of work: excluded from every queue pick; file a narrow issue per item, and declare Part of rather than Closes |
agent: impl-1 / agent: impl-2 | Identity claim on an issue or PR |
Sister docs
docs/expectations.md— schema and modes fortests/expectations/docs/scope.md— per-API in/out-of-scope reasons.claude/rules/branch-and-pr.md.claude/rules/al-language-submodule.md.claude/rules/precompiled-dll-respect.md.claude/rules/loud-failures.md.claude/rules/tdd.md.claude/rules/no-assumption-fixes.md.claude/rules/no-changelog-edits.md.claude/rules/file-issues-for-gaps.md
Signals
- GitHub stars
- 40
- Forks
- 13
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
al-runner-workflow- Source
- github.com/stefanmaron/businesscentral.al.runner
github.com/stefanmaron/businesscentral.al.runner
Related picks
Skill · tddworks
The pick for GitHub Actionsgithub-actions-docs
Skill · devantler-tech
The pick for GitHub Actionsprototype
Skill · mattpocock
More in Mediabrand-guidelines
Skill · anthropics
More in Mediaaccessibility
Skill · affaan-m
More in Mediagrill-with-docs
Skill · mattpocock
More in Media