Skip to content

Turn the implement-ticket agent into a spec, implement, check and review pipeline - #5575

Merged
Marc-André Moreau (mamoreau-devolutions) merged 23 commits into
mainfrom
claude/ai-factory-workflow-analysis-f8a3d5
Oct 23, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 23 commits into
mainfrom
claude/ai-factory-workflow-analysis-f8a3d5

Conversation

@mamoreau-devolutions

@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Why

The first run of the implement-ticket agent (run 39783250637) produced a plausible change. But:

  • it never used the feature it built;
  • its screenshots caught a page still loading;
  • it spent most of its time waiting on builds;
  • nothing checked its claims;
  • the same ticket could produce a different design each run.

This turns the workflow into a pipeline around the agent.

What changes

The agent's own run

  • Scripts and prompt:
    • Stop-UniGetUIDev.ps1 replaces a stop command that Copilot CLI refuses to run.
    • Wait-UniGetUIIdle.ps1 waits for the page to finish loading (through UI Automation) before a screenshot.
    • PROMPT.md asks for acceptance criteria, before/after screenshot pairs that look visibly different, and one formatter pass. It also bans dotnet publish.
  • Faster builds: dev builds keep the shared compiler server and skip restore while the project files and their restore results are unchanged.
  • Avalonia docs: the agent gets Avalonia's Build MCP (mcp-config.json).

The pipeline (agent-implement-ticket.yml)

  • resolve (no agent): turns the trigger into a request and acknowledges it on the issue or PR (Resolve-AgentRequest.ps1). It handles:

    • a manual run,
    • an agent:spec or agent:implement label,
    • /agent spec or /agent implement [guidance] on an issue,
    • /agent <feedback> on an agent PR.

    Labels and comments only start a run for users with write access.

  • spec (Linux): an agent writes a spec with behavior, an ASCII UI sketch, acceptance criteria and the alternatives it considered (SPEC_PROMPT.md). spec-publish posts it on the issue; /agent implement (or spec_run_id) implements the approved spec.

  • implement (Windows desktop): the agent, then:

    • checks the workflow runs itself (Test-AgentChange.ps1): no .github/ changes, the build, the tests of projects that reference the change, formatting of the changed files, and new strings missing from lang_en.json;
    • screenshot evidence (New-ScreenshotEvidence.ps1): crops the taskbar, outlines in red what changed between after-Na/after-Nb, and flags pairs that are nearly identical.
  • review and fix round (on the same Windows desktop, inside the implement job):

    • A second agent on a different model (claude-sonnet-5.5, then claude-opus-5.5, gpt-6-luna) reviews the change in place (Invoke-AgentReview.ps1, REVIEW_PROMPT.md).
    • Its findings are structured (id, severity, file, line, fix) and flagged implementable when the fix is local, has one clear resolution and needs no design or spec decision.
    • If the reviewer touches the working tree, the implementer's change is put back.
    • Select-AgentFixes.ps1 picks, without an agent, the failed checks and the implementable blocker, major and minor findings.
    • One fix round (FIX_PROMPT.md) fixes only those, reports each one fixed or not, and updates the description. The checks and evidence then run again; the first round is kept in before-fix/.
    • There's no second review or fix round: whatever is left goes to the maintainer.
  • report (New-AgentReport.ps1):

    • writes the checks, the evidence, the review and the run metrics into the PR body, the job summary and an artifact;
    • opens a draft PR, or pushes to the agent PR it iterates on;
    • comments the result on the issue.
  • Metrics: Invoke-CopilotAgent.ps1 runs every agent with JSON output. It records the model auto picked, the AI credits, the tool calls, the builds, tests, waits, clicks and screenshots, and the MCP calls. The report adds the time spent in each phase.

  • Older starting commits: a run from an older commit (an eval case, a PR iteration) uses this workflow's own .github/agent and .github/actions, committed locally so the agent's patch leaves them out.

Eval (agent-eval.yml)

Desktop session

  • Faster module install: AwakeCoding.PSRemoting is installed with ModuleFast.
  • Devolutions Agent (Devolutions.Agent 2026.3.1.0) is installed with its RDP extension (F.Session).
  • NOW channel: with the now-channel input, the action negotiates NOW right after connecting and checks that a PowerShell command runs in the desktop session over NOW. Invoke-DesktopNowCommand exposes that to the agent.
  • IronRDP pin: ironrdp-ref points at the merge of Devolutions/IronRDP#2209 (10199db3). That fixes the DVC pipe proxy, which handed its NOW messages to the client as channel 0 and dropped the RDP session. ironrdp-patches/ can carry upstream fixes ahead of a merge; it's empty now.
  • NOW check: the command is retried up to 3 times; if NOW keeps failing, the job carries on without it (DESKTOP_SESSION_NOW=0) and warns. One run failed with "NOW operation stream closed before a terminal event" right after the handshake.
  • Cache use:
    • the Rust build cache is gone, since it evicted the other caches from the repository's 10 GB;
    • the agent workflow restores NuGet packages from main without saving its own copy.

Tested in CI

Run What it showed
39893205057 spec Spec agent (gpt-6-luna, 93s, 1.33 credits) wrote a spec with an ASCII sketch and 8 acceptance criteria.
39893563369 implement from that spec Desktop session and NOW: NOW negotiated, and "DevolutionsSession (pid 8356) ran a PowerShell command in desktop session 2 over NOW".
Agent: covered all 8 criteria with 41 waits, 39 clicks and 0 sleeps.
Checks: caught 4 strings missing from lang_en.json.
Review: found 4 real major bugs (updates hidden for undiscovered sources, an error state that can't appear, "Select all" leaving a manager indeterminate, the missing strings).
Evidence: flagged a nearly identical pair.
39899544626 continue that run with feedback Iteration: run B's change became the base, and the new diff is only the feedback (2 files).
The Sonnet review approved with minor findings.
The report artifact has the phase timings (setup 1m13s, base build 1m42s, agent 9m20s, checks 7m20s).
39960837528 implement from the spec, with the fix round NOW ran its check command on the first attempt.
The Sonnet review approved with 5 minor findings, 3 marked implementable.
The fix round fixed all 3 in 7m18s (including 6 new unit tests: 873 → 879).
R1 (a design choice) and R5 (missing evidence for the loading, empty and error states) were left for the maintainer.
The checks passed before and after; the reviewer guard didn't trigger.
39969885850 and 39971646154 (desktop-smoke), IronRDP 10199db3 without the patch ironrdp-agent built from the merge commit.
NOW negotiated, and the check command ran on the first attempt.
desktop-smoke launched UniGetUI and took clean screenshots.
39893339409 eval, case 5561 Reusable-workflow path end to end.
The checks passed (870 and 225 tests).
The review requested changes (a real split-button height bug).
The agent changed the same single file as the human PR.
The scorer wrote the report table.

Locally:

  • the resolver was run on nine sample events (labels, comments, outsiders, non-agent PRs);
  • the checks script was run on deliberately broken changes;
  • the evidence script was run on earlier screenshots;
  • the restore check was run across a Release restore;
  • Build MCP calls were made through --additional-mcp-config.

Not tested yet

  • Label and comment triggers: GitHub only runs issues and issue_comment workflows from the default branch, so they start working after merge. The resolver logic is tested locally, and its dispatch equivalents (spec_run_id, continue_run_id) ran in CI.
    • Not exercised: posting the spec on an issue, reading an approved spec back from an issue, and pushing a commit to an existing agent PR.
    • agent-eval.yml can only be started by hand from the default branch. Its CI run used a temporary push trigger that has since been removed.
  • No real pull request opened: every test run had create_pr off.
  • Labels: the agent:spec and agent:implement labels don't exist in the repository yet.
  • TUI tests: the checks skip UniGetUI.Tui.Tests unless the TUI changed (its test host crashed once on the runner). That rule was tested locally, not in CI.

🤖 Generated with Claude Code

The first agent run only screenshotted that a new control existed, never
used it, captured a page still loading, lost a turn to a blocked
Stop-Process pipeline, and spent most of its time waiting on builds and
formatters.

- Add Stop-UniGetUIDev.ps1, which stops UniGetUI by process id, since
  Copilot CLI refuses `Get-Process | Stop-Process`.
- Add Wait-UniGetUIIdle.ps1, which waits until the page has finished
  loading packages (LoadingProgressBar is out of the UI Automation tree)
  and the window has stopped changing, and can save a screenshot then.
- Keep the shared compiler server for the agent's dev builds and skip
  restore while project files are unchanged; locally, a rebuild after an
  edit drops from 33-39s to 15s.
- Have the prompt ask for acceptance criteria and assumptions up front,
  a screenshot of each criterion being exercised through the UI, one
  formatter pass at the end, and fix its claim that no packages are
  installed on the runner.
- Warn from the export step when code changed without any after-*.png.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IronRDP's harness installs the module with Install-Module when it is
missing. Installing it first with ModuleFast, pinned to v0.6.1 and to
AwakeCoding.PSRemoting 2026.10.21, into the same CurrentUser folder lets
the harness skip that install; locally ModuleFast takes about 5s where
Save-Module takes 12s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) changed the title Make the implement-ticket agent prove its change and build faster Make the implement-ticket agent prove its change and set up faster Oct 22, 2026
The agent's RDP extension (MSI feature F.Session, Session.Enabled in
agent.json) starts DevolutionsSession in each RDP session, which serves
the NOW protocol over a virtual channel for remote execution.

- Install the Devolutions.Agent winget package, 2026.3.1.0 by default,
  with ADDLOCAL=F.Agent,F.Session, before the desktop session connects:
  the agent only starts DevolutionsSession on RDP connect and logon.
  winget is not on the runner image, so the MSI comes from the package's
  winget manifest and is checked against the manifest's SHA256.
- Check that DevolutionsSession runs in the desktop session, and copy the
  agent's config and logs into the session artifacts.
- An empty devolutions-agent-version input skips both steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first run with Devolutions Agent showed DevolutionsSession starting
in the RDP session and exiting 5 seconds later. It opens the
Devolutions::Now::Agent channel and gives up unless NOW negotiation
starts within 5 seconds, while ironrdp-agent only negotiates on its first
NOW request, so nothing negotiated and the agent never restarted it.

- Start-DesktopSession.ps1 -NowChannel runs `ironrdp-agent now
  capabilities` right after connecting; it waits for the channel and
  negotiates in time, and the daemon keeps the channel alive afterwards.
- The check step now runs a PowerShell command over NOW and checks that
  it ran in the desktop session, and also collects DevolutionsSession's
  logs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second run negotiated in time, but the RDP session dropped on the
first NOW message: ironrdp-dvc-pipe-proxy encodes its messages for the
channel yet hands them to the client as channel 0, and the client's
DvcMessageBatch::try_new rejects the mismatch and ends the session.

Devolutions Agent is still installed; the handshake and the remote
execution check only run with the new now-channel input set to true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent now gets the avalonia-docs MCP server
(https://docs-mcp.avaloniaui.net/mcp) through --additional-mcp-config,
and the prompt has it load get_avalonia_expert_rules before writing UI
code and look up Avalonia 12 APIs in the docs rather than from memory.
It is a public, read-only documentation service and gets no credential.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…publish

In run 39806855126 the agent ran a NativeAOT publish twice (about 6
minutes). Its Release restore rewrote project.assets.json without the
Debug-only diagnostics package, and Start-UniGetUIDev.ps1 then skipped
restore because no project file had changed, so the next Debug build
failed.

- The restore stamp also records each project's project.assets.json
  timestamp, so any other restore forces a fresh one.
- The prompt tells the agent not to run dotnet publish; the PR's CI
  checks the NativeAOT build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) changed the title Make the implement-ticket agent prove its change and set up faster Make the implement-ticket agent prove its change, set up faster, and use Avalonia docs Oct 22, 2026
Swatinem/rust-cache saved about 400 MB per branch (77s at job end), and
with the repository's Actions cache at its 10 GB limit that evicted the
cached ironrdp-agent and NuGet packages, so run 39862220431 rebuilt
ironrdp-agent and restored NuGet from scratch. The built agent is
already cached by IronRDP commit, so the Rust cache only shortened the
rare full rebuild (about 9 minutes without it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… agent workflow

Turns the implement-ticket workflow into a pipeline around the agent:

- Spec stage: mode spec (or the agent:spec label, or "/agent spec" on an
  issue) has an agent write a spec with acceptance criteria and a UI
  sketch, posted on the issue; "/agent implement" (or spec_run_id)
  implements the approved spec.
- Checks after the agent (Test-AgentChange.ps1): paths, build, the
  tests of projects that reference the change, formatting of the
  changed files, and new translation keys, reported in the PR.
- Review by a second agent on a different model (REVIEW_PROMPT.md),
  with its verdict and findings in the PR.
- Screenshot evidence (New-ScreenshotEvidence.ps1): taskbar cropped,
  each before/after step pair outlined in red, nearly identical pairs
  flagged.
- Triggers: agent:spec / agent:implement labels, "/agent ..." comments
  on issues, "/agent <feedback>" on agent PRs to iterate on them, all
  limited to users with write access (Resolve-AgentRequest.ps1);
  results are posted back on the issue.
- Invoke-CopilotAgent.ps1 runs every agent with JSON output and records
  the model, Copilot usage and what the agent ran; the report adds the
  phase timings. agent-eval.yml replays merged PRs and scores them.
- NuGet packages are restored from main's cache without saving one per
  agent branch; Invoke-DesktopNowCommand runs commands over NOW once
  the desktop session's now-channel input is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new workflow can only be dispatched once it is on the default branch.
To be removed before merging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A called workflow sees its caller's event, so a push-triggered eval
skipped every job. A run with an artifact prefix is a workflow call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oxy fix

The desktop-session action now applies ironrdp-patches/*.patch on top of
ironrdp-ref before building ironrdp-agent, and keys the agent's cache on
them. 0001 is IronRDP 2cc70912, not merged upstream yet: the DVC pipe
proxy reported channel 0 for the NOW messages it sent, and the client
dropped the RDP session. With it, the agent workflow turns now-channel
on, so DevolutionsSession's NOW channel is negotiated and checked, and
the agent can use Invoke-DesktopNowCommand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In eval run 39893339409 the agent wrote its description next to the
ticket instead of to $env:AGENT_PR_BODY, so the report and the review
had none. The prompt now names that file and the screenshots folder.
The report is also uploaded and printed, and the review tries Sonnet
before Opus (34 AI credits for one review against 4.6 for the change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eval run 39893339409 (case 5561) passed through it; from the default
branch the eval is started by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In run 39899544626 UniGetUI.Tui.Tests crashed in its test host after 104
of 225 tests (Avalonia CreateWindow), where run 39893563369 had passed
them. They take about five minutes; the pull request's CI runs them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) changed the title Make the implement-ticket agent prove its change, set up faster, and use Avalonia docs Turn the implement-ticket agent into a spec, implement, check and review pipeline Oct 23, 2026
The review moves into the desktop job, after the checks and the
screenshot evidence, so its findings can be fixed where the app runs:

- REVIEW_PROMPT.md asks for structured findings (id, severity, file,
  line, details, fix) with an implementable flag: local, one clear
  resolution, no design or spec decision.
- Invoke-AgentReview.ps1 runs the reviewer on a different model against
  the working tree and puts the implementer's change back if the review
  touched it.
- Select-AgentFixes.ps1 picks, without an agent, the failed checks and
  the implementable blocker, major and minor findings.
- One fix round (FIX_PROMPT.md) fixes only those, on the same desktop,
  updates the description and reports each item fixed or not. The
  checks and the evidence then run again; the first round is kept in
  before-fix/. There is no second review or second fix round.
- The report lists each problem with its outcome, what was left for the
  maintainer, the checks before the fix, and the fixer's metrics; the
  eval counts cases with no blocker or major finding left unfixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In run 39958994732 the NOW handshake succeeded but the check's command
failed with "NOW operation stream closed before a terminal event", and
DevolutionsSession logged nothing after the handshake; the same check
passed in run 39893563369. The command is now tried three times; if it
still fails, the job continues with DESKTOP_SESSION_NOW=0 and a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IronRDP#2209 (10199db3) merged the fix that ironrdp-patches/0001
carried: the pipe proxy now sends NOW messages on their own channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The report job ran after cancelled run 39969885850 and failed to
download the change it never got. It now runs only after an implement
job that succeeded or failed, tolerates a missing change, and the report
script no longer trips over an empty fix request in strict mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… runs

Following https://awakecoding.com/posts/speeding-up-dotnet-builds-on-windows-nuget-cache/:
on Windows runners NuGet's package and HTTP caches default to C:, the
slow OS disk. NUGET_PACKAGES and NUGET_HTTP_CACHE_PATH now point under
RUNNER_TEMP on D: for the whole job (base build, the agents' builds and
tests, the checks). The NuGet cache restored from main is dropped:
every run restores from scratch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-release, dotnet-test, cli-headless-e2e and the unigetui-build
action already keep NUGET_PACKAGES in the workspace, on D: on Windows
runners. NUGET_HTTP_CACHE_PATH now goes beside it in .nuget/http-cache
instead of defaulting to C:. Their NuGet caches are unchanged and still
cover only .nuget/packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NUGET_PACKAGES and NUGET_HTTP_CACHE_PATH move to the workflow-level env
of build-release, dotnet-test and cli-headless-e2e, and desktop-smoke
gets them too; the unigetui-build action no longer sets its own and
uses its caller's. dotnet-test keeps its exact path, so its NuGet cache
still matches; build-release's Windows build job now uses the forward
slashes of its other jobs, so its cache misses once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 1f139e5 into main Oct 23, 2026
6 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the claude/ai-factory-workflow-analysis-f8a3d5 branch October 23, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant