Agent workflow

SkillMedia

Multi-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.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Agent workflowStart free

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:

  1. Check for issues labeled agent: <your-id> AND status: in-progress — that is your active issue if one exists.

  2. If no active issue, find the next unclaimed issue: status: ready with no agent: label and no human assignee, excluding any type: tracker issue — taking the highest priority: first (urgent > high > medium > low), oldest first within a priority. Claim it: add agent: <your-id>, status: in-progress, assignee @me. Remove status: 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: ready issue 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: tracker issue is a set of related items, not one unit of work, and is excluded from the queue entirely — status: ready on 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 declares Part of #N, never Closes #N, with the marker starting the line (branch-and-pr.md). Nothing in CI catches the mistake: reject-deferred-scope keys on where a deferral points, not on whether a PR finished what it closed, so a Closes on a tracker merges green and shuts a multi-item record having addressed one.

  3. 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).

  4. Branch agent/<your-id>/issue-<N>, pushed, with a draft PR carrying Closes #N, the label agent: <your-id>, assigned to @me (.claude/agents/impl-agent.md, Step 2).

  5. Implement red → green (.claude/rules/tdd.md). The right test depends on what kind of issue this is — see "Issue kinds" below.

  6. Rewrite the body, mark the draft ready, label status: review-ready.

  7. Return. The coordinator reads CI and resumes you, or dispatches a fresh agent onto the PR, when it goes red.

  8. Auto-merge fires when approved + green (allow_auto_merge=true is 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?

KindTest lives inNotes
Runner gap on an in-scope AL patternAn 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 implementedtests/expectations/known-gaps-<area>.json entry linking the GH issue.Transient — entry is removed when the gap is closed.
Runner-specific positive assertionNew 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:

  1. PRs first. Find PRs labeled status: review-ready. CI green + no unresolved threads + no CHANGELOG.md in diff + no edits under tests/al-language/ + relevant expectation entries / runner-extras tests cited in the body + every condition of the arming list in orchestrating-a-session ("A reviewer that approves a PR arms auto-merge") → approve and squash-merge (gh pr merge --auto --squash). Where gh is absent (web and remote sessions, .claude/rules/github-access.md) the verdict cannot be read: review, comment, and hold. Otherwise leave actionable review comments.
  2. Unblock. Review status: blocked issues; resolve if possible.
  3. Triage of new untriaged issues is owned by the triager sub-agent (Opus), which runs at the start of a cycle and sets status: ready vs. 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:

OperationghMCP tool
Who am Igh api user --jq .loginmcp__github__get_me
List issuesgh issue listmcp__github__list_issues
Read issue / its commentsgh issue viewmcp__github__issue_read (get, get_comments)
Label / assign / close issuegh issue edit, gh issue closemcp__github__issue_write (method: update)
Comment on issue or PRgh issue comment, gh pr commentmcp__github__add_issue_comment (PRs too — pass the PR number)
List PRsgh pr listmcp__github__list_pull_requests
PR detail / diff / files / CIgh pr view, gh pr diff, tools/ci-wait.py <N> --timeout 0mcp__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 PRgh pr merge --squashmcp__github__merge_pull_request (merge_method: "squash")
Open a PRgh pr createmcp__github__create_pull_request
Label a PRgh pr edit --add-labelmcp__github__update_pull_request
Edit a PR bodytools/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 logsgh run view --log-failed — empty output is a refusal, not an empty log; see .claude/rules/ci-verdicts.md §3mcp__github__get_job_logs (failed_only: true, return_content: true)
Search for duplicatesgh issue list --searchmcp__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 @me alongside 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-@me user.
  • 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-ready on 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 a Corpus-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.Runner on every gh command when running outside the repo's default.

Label state machine

LabelMeaning
status: readyUnclaimed, ready for an impl agent to pick up
status: in-progressCurrently being worked on by the labeled agent: *
status: review-readyPR is marked ready; the orchestrator reads CI and reviews
status: blockedNeeds human or cross-issue input
status: needs-inputIssue body too thin to identify root cause; reporter must elaborate (set by triager — see no-assumption-fixes)
priority: urgentBlocks other work right now: red main, a broken gate, a rule actively giving wrong guidance
priority: highLarge measured blast radius, or blocks a cluster of other issues
priority: mediumA real defect with bounded scope; nobody is blocked
priority: lowCosmetic, a stale pointer, or a nice-to-have
type: trackerA 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-2Identity claim on an issue or PR

Sister docs

  • docs/expectations.md — schema and modes for tests/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