perf: optimize AgentPhase dependency checking with batch query #52

Merged
Snider merged 1 commit from perf/optimize-agent-phase-dependency-checking into main 2026-02-23 11:38:54 +00:00
2 changed files with 81 additions and 3 deletions

View file

@ -317,11 +317,17 @@ class AgentPhase extends Model
public function checkDependencies(): array
{
$dependencies = $this->dependencies ?? [];
if (empty($dependencies)) {
return [];
}
$blockers = [];
foreach ($dependencies as $depId) {
$dep = AgentPhase::find($depId);
if ($dep && ! $dep->isCompleted() && ! $dep->isSkipped()) {
$deps = AgentPhase::whereIn('id', $dependencies)->get();
foreach ($deps as $dep) {
if (! $dep->isCompleted() && ! $dep->isSkipped()) {
$blockers[] = [
'phase_id' => $dep->id,
'phase_order' => $dep->order,

View file

@ -286,6 +286,78 @@ class AgentPhaseTest extends TestCase
$this->assertEquals($dep2->id, $blockers[0]['phase_id']);
}
public function test_check_dependencies_returns_empty_when_no_dependencies(): void
{
$phase = AgentPhase::factory()->pending()->create([
'agent_plan_id' => $this->plan->id,
'dependencies' => null,
]);
$this->assertSame([], $phase->checkDependencies());
}
public function test_check_dependencies_not_blocked_by_skipped_phase(): void
{
$dep = AgentPhase::factory()->skipped()->create([
'agent_plan_id' => $this->plan->id,
'order' => 1,
]);
$phase = AgentPhase::factory()->pending()->create([
'agent_plan_id' => $this->plan->id,
'order' => 2,
'dependencies' => [$dep->id],
]);
$this->assertSame([], $phase->checkDependencies());
$this->assertTrue($phase->canStart());
}
public function test_check_dependencies_uses_single_query_for_multiple_deps(): void
{
$deps = AgentPhase::factory()->pending()->count(5)->create([
'agent_plan_id' => $this->plan->id,
]);
$phase = AgentPhase::factory()->pending()->create([
'agent_plan_id' => $this->plan->id,
'dependencies' => $deps->pluck('id')->toArray(),
]);
$queryCount = 0;
\DB::listen(function () use (&$queryCount) {
$queryCount++;
});
$blockers = $phase->checkDependencies();
$this->assertCount(5, $blockers);
$this->assertSame(1, $queryCount, 'checkDependencies() should issue exactly one query');
}
public function test_check_dependencies_blocker_contains_expected_keys(): void
{
$dep = AgentPhase::factory()->inProgress()->create([
'agent_plan_id' => $this->plan->id,
'order' => 1,
'name' => 'Blocker Phase',
]);
$phase = AgentPhase::factory()->pending()->create([
'agent_plan_id' => $this->plan->id,
'order' => 2,
'dependencies' => [$dep->id],
]);
$blockers = $phase->checkDependencies();
$this->assertCount(1, $blockers);
$this->assertEquals($dep->id, $blockers[0]['phase_id']);
$this->assertEquals(1, $blockers[0]['phase_order']);
$this->assertEquals('Blocker Phase', $blockers[0]['phase_name']);
$this->assertEquals(AgentPhase::STATUS_IN_PROGRESS, $blockers[0]['status']);
}
public function test_can_start_checks_dependencies(): void
{
$dep = AgentPhase::factory()->pending()->create([