diff --git a/app/Domain/Memory/Actions/StoreMemoryAction.php b/app/Domain/Memory/Actions/StoreMemoryAction.php index ba89de27..e21e40f7 100644 --- a/app/Domain/Memory/Actions/StoreMemoryAction.php +++ b/app/Domain/Memory/Actions/StoreMemoryAction.php @@ -163,7 +163,7 @@ private function normalizeRejectedAlternatives(array $raw): array */ private function storeChunk( string $teamId, - string $agentId, + ?string $agentId, string $chunk, string $sourceType, ?string $projectId, @@ -225,7 +225,7 @@ private function storeChunk( */ private function evaluateWriteGate( string $teamId, - string $agentId, + ?string $agentId, string $contentHash, string $embedding, ): WriteGateResult { @@ -304,7 +304,7 @@ private function handleUpdate( string $newContent, string $newEmbedding, string $teamId, - string $agentId, + ?string $agentId, float $newConfidence, float $newImportance, array $newTags, @@ -347,7 +347,7 @@ private function handleUpdate( private function handleAdd( string $teamId, - string $agentId, + ?string $agentId, string $chunk, string $embedding, string $contentHash, @@ -409,7 +409,7 @@ private function handleAdd( /** * LLM-assisted merge of two related facts. */ - private function mergeContent(string $existing, string $new, string $teamId, string $agentId): ?string + private function mergeContent(string $existing, string $new, string $teamId, ?string $agentId): ?string { if (! $this->gateway) { return null; diff --git a/tests/Unit/Domain/Memory/Actions/StoreMemoryActionTest.php b/tests/Unit/Domain/Memory/Actions/StoreMemoryActionTest.php index 94e99012..ef911f67 100644 --- a/tests/Unit/Domain/Memory/Actions/StoreMemoryActionTest.php +++ b/tests/Unit/Domain/Memory/Actions/StoreMemoryActionTest.php @@ -35,6 +35,28 @@ public function test_chunk_content_splits_long_content_by_paragraphs(): void $this->assertStringContainsString('A', $chunks[0]); } + public function test_team_level_memory_accepts_a_null_agent_id(): void + { + // execute() advertises `?string $agentId`, but storeChunk() and the + // write-gate helpers below it declared a non-nullable `string`. Every + // team-scoped write — DistillTeamEventsAction passes `agentId: null` + // explicitly — therefore threw a TypeError that execute()'s catch + // swallowed into a Log::warning, so nothing was ever stored and no + // error surfaced. Production had 0 rows in `memories`. + $action = new StoreMemoryAction; + + foreach (['storeChunk', 'evaluateWriteGate', 'handleUpdate', 'handleAdd', 'mergeContent'] as $name) { + $param = collect((new \ReflectionMethod($action, $name))->getParameters()) + ->firstWhere(fn (\ReflectionParameter $p) => $p->getName() === 'agentId'); + + $this->assertNotNull($param, "{$name}() should take an \$agentId"); + $this->assertTrue( + $param->getType()?->allowsNull(), + "{$name}() must accept a null \$agentId — team-scoped memories have no agent", + ); + } + } + public function test_execute_returns_empty_array_when_content_is_empty(): void { $action = new StoreMemoryAction;