← Back to featured work

Case Study

DevAssistant — Agentic Ticket-to-Fix Pipeline

A multi-agent pipeline that carries a work item from assignment to an approval-gated fix, with every agent held to exactly the tools its role needs.

DevAssistant takes a bug or story assigned to me in Azure DevOps and carries it from new ticket to a pull request waiting for my approval. Each stage is its own Microsoft Agent Framework workflow, every agent is held to exactly the tools its role needs, and nothing reaches the remote until I say so.

  • Microsoft Agent Framework
  • Multi-Agent
  • Claude
  • C#
  • .NET
  • WPF
  • SQLite
6
Pipeline stages, each its own workflow
4 + 1
Parallel AI reviewers and a verdict
~1,400
Model calls a day removed by REST change detection
~700
Automated tests, including architecture tests

The problem

Every ticket starts with the same groundwork

Before a line of the fix is written, someone has to read the description and comment history, find the code it touches, work out why it is broken and write that down. It is slow, it is repetitive, and it goes stale the moment the ticket changes.

Coding assistants help inside a single session, but none of them keeps watch over a sprint's worth of tickets, and none can say for certain what an agent was and was not allowed to touch.

The pipeline

From new ticket to approved pull request in six stages

Each stage is a separate workflow with its own agents, tools and failure handling. A ticket moves on only after its current stage succeeds, so an interrupted ticket repeats its stage instead of resuming halfway through. A person starts every stage: the scheduler finds new tickets and notices changes, but never starts work on its own.

  • Init reads the work item, its comments and attachments, writes the brief the later stages work from, and creates the ticket's own git worktree on its own branch.
  • Root cause analysis has an analyst agent investigate the code and write an RCA document. A separate assessor reads only that document and judges, with a confidence score, whether it names a concrete cause worth fixing.
  • Create plan turns the analysis into ordered steps, each naming the file and the change, plus how it will be verified. With no concrete cause, the planner is told to stop rather than invent one.
  • Coding implements the plan, builds, runs the tests and commits. The workflow checks the branch itself instead of trusting the agent's report, so claimed commits that never landed fail the stage.
  • Code review runs a four-reviewer panel and a verdict, described below.
  • PR preview drafts the title, body, risks and testing notes for my approval. PR submission deliberately has no model: it confirms the branch head is still the commit I approved, pushes without force and opens the pull request.
The DevAssistant Tickets screen: the current sprint's assigned tickets with their Azure DevOps state, AI status, current stage, a seven-step progress track and tokens used.
The Tickets screen. Each assigned ticket shows its stage and progress through the pipeline, from Init to PR submission.

Code review

A panel, a verdict and a bounded loop

Four reviewer agents examine the diff in parallel: correctness, silent failures, test coverage and the repository's own conventions. A verdict agent, which can run on a stronger model than the panel, weighs their findings and either approves or sends the ticket back to Coding with only the blocking findings.

  • Every round is added to a review document that both sides read. The coder can dispute a finding with evidence, or decline one that is true but out of scope, with a reason.
  • A reviewer that raises an answered finding again must include a rebuttal, or the finding no longer holds up the change.
  • If the coder changes nothing when sent back, the ticket moves on rather than having the same commits reviewed again.
  • After two send-backs the loop stops and I decide, so review never spins indefinitely.
A ticket's detail panel: the model runs for each review and coding round, links to the analysis, plan, code review and PR preview, the pipeline progress, and a token table by model.
One ticket after review sent it back twice. Local qwen3 models ran the review panel while Claude did the coding, and the token table breaks the cost down by model.

Guardrails

Capabilities enforced in code, not requested in prompts

An agent can call only the typed tools it was built with, and every tool set is assembled in one file, so what each agent can do is checked in one place.

  • Reviewers, the assessor and the PR writer have no write tool at all. The coder's file tools refuse anything under docs/, and there is no push tool anywhere.
  • File tools are confined to the ticket's worktree and refuse .git, symlinks and junctions. Git refs that could be read as command-line options are rejected.
  • When an agent runs as a CLI subprocess, it gets a per-stage allowlist: git by subcommand, never push or a branch switch, and never a bare shell.
  • API keys and Azure DevOps tokens are encrypted with Windows DPAPI and scrubbed from every log, trace and tool result.
  • Every child process runs inside one Windows Job Object, so closing or crashing the app also stops every agent, build and git process it started.

Models

Any agent, any vendor, any endpoint

Each agent has its own assignment of vendor, model and mode, so a stage can run on a small local model, a frontier model or anything in between. Claude, GitHub Copilot, OpenAI Codex, Google Gemini and local Ollama models are all supported.

  • API mode runs a Microsoft Agent Framework agent over the vendor's HTTP API. Headless mode streams and parses the vendor's CLI as a subprocess. Interactive mode opens the CLI in a terminal for me to drive.
  • One OpenAI-compatible client serves every non-Anthropic API, and its base URL is a setting, so LM Studio, vLLM, Azure OpenAI, OpenRouter or a company gateway work without a code change.
  • Local models are first-class: reviewers take turns instead of competing for one GPU, and failure messages suggest a larger model when a small one can't keep up.
  • Everything that differs between coding-agent CLIs is data on a profile, so a new CLI can be added from the Settings screen without a new build.
The LLM Providers settings tab for Google Gemini: enabled toggle, encrypted API key, OpenAI-compatible endpoint, model list and CLI executable.
Each vendor has its own tab. Gemini is reached through its OpenAI-compatible endpoint, with the key stored encrypted and the model list read from the vendor.

Observability

Every run, token and turn on the record

  • Every model run is recorded with its model, outcome, duration, turns, and input, output and cache-read tokens, per ticket and across the system, with a CSV-exportable run log.
  • Every streamed turn is timed, recording time to first token and generation speed, so models can be compared on evidence rather than impressions.
  • Tokens are recorded, never invented costs: subscription runs have no meaningful per-call price, so the result type has no cost field at all.
  • Each stage writes a JSONL trace of prompts, model turns, tool calls and errors that can be followed live or replayed, with secrets scrubbed before anything is written.
  • Change detection is a single REST read, and a model is called only when a ticket has really changed, which removed about 1,400 model requests a day for five watched tickets.

Engineering

A desktop app with an engine built to move out

  • A WPF desktop app with screens for tickets and their pipeline timelines, live agent transcripts, pending approvals, the run log and settings, across several Azure DevOps organisations.
  • A single SQLite ledger records tickets, stages, runs and tokens. After a crash, the state of every workflow is rebuilt from it.
  • The engine targets plain .NET 10 behind one narrow contract with the UI, so it can move into a separate process or service without changing engine code.
  • About 700 xUnit tests, including architecture tests that enforce the layering between the UI, the contracts and the engine.

Outcome

A teammate that does the groundwork and waits for my sign-off

DevAssistant turns an assigned ticket into a written root cause, a plan, a reviewed change on its own branch and a drafted pull request, while I stay in control of when each stage runs and what ships. Every capability boundary is enforced in code and every token is accounted for, so trusting the system is a matter of evidence rather than faith.

Built with

  • .NET 10
  • C#
  • WPF
  • Microsoft Agent Framework
  • Microsoft.Extensions.AI
  • Claude
  • Ollama
  • EF Core
  • SQLite
  • Azure DevOps REST
  • Git worktrees
  • xUnit