// case-study.md

Autonomous AI Engineer
Multi-LLM agent pipeline inside an enterprise ticketing system

A production AI-engineer pipeline embedded in a real ticketing system: assign a ticket to the “AI Engineer”, run php artisan ai:work {ref}, and an LLM agent (Claude, Gemini, OpenAI, OpenRouter, or Qwen) picks up the ticket context, auto-detects the target repository, makes the change, and pushes a candidate fix — then an independent verifier runs the real test suite before the work is allowed near production.

client Confidential · logistics operator under NDA
last touched
role Full-Stack Developer / AI Engineer
period 2025 — Present
status In production · supervised mode
parent system Ticketing · ~145 models · ~99 controllers · ~206 migrations
Laravel 11 Sanctum Spatie Permissions Claude Gemini OpenAI OpenRouter Qwen PHPUnit
// receipts · tl;dr
LLM brains wired in
5
console commands
3
security gates
board + repo
verification
PHPUnit ✓
// outcome · what changed
BEFORE

Tickets sat in queue waiting for human pickup; first-touch latency capped throughput.

AFTER

5-LLM autonomous engineer picks up ASSIGNED tickets, auto-detects the right repo, runs the work end-to-end.

BEFORE

Hand-merging unverified AI-suggested code created review fatigue.

AFTER

Every AI patch must pass PHPUnit before any merge — failing patches never reach review.

BEFORE

No audit trail for AI-touched commits; trust required for adoption.

AFTER

Every action logged with ticket + run + result; board-level + repo-level security gates.

What it is

Inside a production ticketing system (~145 Eloquent models, ~99 controllers, ~206 migrations, Kanban boards via Spatie Permission), there’s a user named “AI Engineer” who can be assigned tickets like any other developer. When a ticket reaches the ASSIGNED board column, an operator runs a single Laravel console command:

php artisan ai:work IN-2025-000006 --model=claude

An autonomous LLM agent picks up the ticket, reads its context from the AI Harness, auto-detects the target repository (from the systems table), makes the proposed change, and pushes it back with a session record. A separate verifier (php artisan ai:verify {ticket_no}) then runs the real test suite (php artisan test by default) and logs the pass / fail result back to the ticket as an internal comment — a human reviews before merge. A nightly tickets:auto-close job closes tickets that have sat in deployed for 72+ hours.

Five LLM brains are wired in side-by-side — Claude, Gemini, OpenAI, OpenRouter, and Qwen — selected per ticket so the operator can pick the right tool for the job (e.g., Claude for refactors, Gemini for big-context reads, Qwen for cost).

The bottleneck

A small dev team supporting many internal systems (WMS, TMS, CRM, Ticketing, HR, Developer Portal) means a long tail of small tickets — “rename this column,” “add this field to the export,” “adjust this validation,” “tweak the report” — that each take ~15–30 minutes of real work but hours of context-switch and queue time across multiple repos. Specific pain points:

The ask: make AI work look exactly like a human engineer’s work — assigned via the same Kanban board, scoped to specific repos, verified by the same test suite, reviewed by a human before merge, and closed through the same workflow.

How I broke it down

What I built

Pipeline components shipped:

The work command — security-gated, model-selectable, repo-aware:

~/app/Console/Commands/AiWorkCommand.php
class AiWorkCommand extends Command
{
    protected $signature = 'ai:work {ref_no?} {--model=}';
    protected $description = 'Starts an autonomous AI agent with interactive ticket and model selection.';

    public function __construct(
        private AiHarnessService $harness,
        private ClaudeAgentService $claude,
        private GeminiAgentService $gemini,
        private OpenAIAgentService $openai,
        private OpenRouterAgentService $openrouter,
        private QwenAgentService $qwen
    ) { parent::__construct(); }

    public function handle()
    {
        $ref = $this->argument('ref_no')
            ?? $this->ask('Ticket Reference Number (e.g. IN-2025-000006)');

        $ticket = SystemTicket::where('ref_no', $ref)->first();
        if (!$ticket) return $this->error("Ticket {$ref} not found.");

        // Security gate — only work on ASSIGNED board status (2 or 12)
        if (!in_array($ticket->board_status_id, [2, 12])) {
            return $this->error("Ticket not in ASSIGNED status.");
        }

        $model = $this->option('model')
            ?? $this->choice('AI brain?', ['claude', 'gemini', 'openai', 'openrouter', 'qwen'], 0);

        $repo = DB::table('systems')->where('code', $ticket->system)->value('repo');
        $agent = $this->{$model}; // dispatch to chosen brain

        return $this->harness->run($ticket, $repo, $agent);
    }
}

And the verifier — deliberately separate from the worker:

~/app/Console/Commands/AiVerifyCommand.php
// php artisan ai:verify IN-2025-000006 --cmd="vendor/bin/phpunit --filter MemoTest"
$context = $this->harness->getSessionContext($ticket);
$repo    = $this->option('repo') ?? $context['target_repos'][0];
$cmd     = $this->option('cmd') ?? 'php artisan test';

$result = $this->harness->runVerification($ticket, $repo, $cmd);

// Log pass/fail + stdout to the ticket comment thread automatically
return $result['success'] ? 0 : 1;
Autonomous AI Engineer console — terminal-style screenshot showing the ai:work command flow: ticket lookup, security gate, repo auto-detect, model selection (Claude/Gemini/OpenAI/OpenRouter/Qwen), and AI agent run output.
MOCKConsole session for php artisan ai:work IN-2025-000006 --model=claude · security gate, auto repo detection, multi-brain selector, AI agent run, verification result. Ticket and file names anonymized.

Tech

Results

LLM brains wired in
5
console commands
3
security gates
board + repo
verification
PHPUnit ✓

The team gained an “extra engineer” that picks up small tickets, edits the right repository, and verifies its own work against the real test suite — without escaping the existing ticket, board, and audit-log workflow. The 5-LLM selector means a single ticket can be retried with a different brain when one model misreads the task. Every AI action lands as a comment in the ticket thread, so a human reviewer always has the full session record before approving a merge.

Specific business figures (ticket-throughput delta, hours saved per week) stay with the client; happy to discuss specifics on request.

What I’d do again — and differently

Worked well:

Would tighten: