laravel/ai

[Feature] Polymorphic conversation ownership

Closed

#164 opened on Feb 14, 2026

 (2 comments) (4 reactions) (0 assignees)PHP (261 forks)github user discovery
enhancementhelp wanted

Repository metrics

Stars
 (978 stars)
PR merge metrics
 (Avg merge 6d 11h) (49 merged PRs in 30d)

Description

Description

The forUser() method already accepts any object with an ->id property, but the database schema has no way to distinguish which type of model owns the conversation — it's a plain user_id column with no type discriminator.

This becomes a problem when conversations need to belong to a model other than User. In our case, we're building a multi-tenant SaaS where conversations belong to a tenant-scoped Tenant, not the User directly. A single user can be a member of multiple tenants and needs separate conversation histories per tenant. The same issue applies to apps with multiple authenticatable models (Admin vs Tenant) where IDs can collide.

Workarounds

You can override the ConversationStore singleton with a custom implementation, but that means reimplementing the entire storage layer (conversation CRUD, message storage, retrieval) just to change how ownership is stored. That's disproportionate for what is fundamentally a schema concern, and it means you lose the benefit of any future improvements to the default store.

Proposed Solution

Follow the pattern Filament uses for its Export and Import models via Export::polymorphicUserRelationship() — a static opt-in that switches the ownership column from a plain foreign key to a polymorphic relationship.

Migration

When opting in, the published migration would use morphs instead of foreignId:

 Schema::create('agent_conversations', function (Blueprint $table) {
     $table->string('id', 36)->primary();
-    $table->foreignId('user_id');
+    $table->morphs('participant');
     $table->string('title');
     $table->timestamps();
 });

Same change for agent_conversation_messages.

API

A static method to opt into polymorphic mode, called in a service provider:

AgentConversation::polymorphicParticipantRelationship();

The RemembersConversations trait already holds the full model via forUser(), so the type information is available — it just needs to be threaded through to the store layer when polymorphic mode is enabled.

Fully backward compatible. Polymorphic mode is opt-in — existing installations using foreignId('user_id') continue working without changes.

Related Issues

  • #98 — Flexible user ID types
  • #121 — UUID user ID support
  • #76 — Anonymous conversations (a nullable polymorphic participant would address this too)

Contributor guide