Deck by PromptPHP provides first-class, optional integration with the Laravel AI SDK. When the AI SDK is installed, you get:
make:agent automatically creates a matching prompt directory.HasPromptTemplate trait — Provides instructions() and promptMessages() methods that load versioned prompts directly into your AI agents.TrackPromptMiddleware — Automatically records prompt executions (tokens, latency, model, etc.) using Deck's tracking system.All of this is entirely optional. Deck works perfectly without the AI SDK.
Deck does not require the AI SDK — it's listed as a suggest dependency. Install it when you're ready:
composer require laravel/ai
Once laravel/ai is installed, Deck's AI SDK features activate automatically. No additional configuration is needed.
When the Laravel AI SDK is installed, Deck automatically hooks into the make:agent command. Whenever you create a new agent:
php artisan make:agent SalesCoach
Deck detects the successful command and automatically runs:
php artisan make:prompt sales-coach
This creates a versioned prompt directory ready for the agent to use via the HasPromptTemplate trait — zero extra setup required.
Deck registers a listener (AfterMakeAgent) on Laravel's CommandFinished event. When make:agent completes successfully, the listener:
SalesCoach → sales-coach).App\Ai\Agents\SalesCoach → sales-coach).make:prompt with the derived name.The listener is only registered when laravel/ai is installed. If the prompt creation fails for any reason, it does not break the make:agent workflow — the agent is still created successfully.
$ php artisan make:agent SalesCoach
INFO Agent [app/Ai/Agents/SalesCoach.php] created successfully.
Deck: Created prompt sales-coach for SalesCoach.
To disable automatic prompt scaffolding, set the configuration option:
// config/deck.php
'scaffold_on_make_agent' => false,
Or via environment variable:
DECK_SCAFFOLD_ON_MAKE_AGENT=false
Use the HasPromptTemplate trait on any agent class:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
use PromptPHP\Deck\Concerns\HasPromptTemplate;
class SalesCoach implements Agent
{
use Promptable, HasPromptTemplate;
// instructions() is provided automatically by HasPromptTemplate.
// It loads prompts/sales-coach/v{active}/system.md
}
That's it. The HasPromptTemplate trait provides the instructions() method required by the Agent contract, loading the system prompt from your Deck files.
The HasPromptTemplate trait bridges Deck's file-based templates with the Laravel AI SDK's agent contracts.
| Deck | AI SDK | Description |
|---|---|---|
system.md role file |
instructions() |
Agent's system prompt. |
user.md, assistant.md, etc. |
messages() via promptMessages() |
Conversation context. |
metadata.json |
— | Prompt metadata (description, variables, etc.). |
v1/, v2/, etc. |
— | Version management. |
prompts/sales-coach/
├── v1/
├── system.md → instructions()
├── user.md → promptMessages()
└── metadata.json
└── v2/
├── system.md → instructions() (active version)
├── user.md → promptMessages()
├── assistant.md → promptMessages()
└── metadata.json
By default, the prompt name is derived from the class name in kebab-case:
SalesCoach → sales-coachDocumentAnalyzer → document-analyzerOverride promptName() to use a custom name:
class SalesCoach implements Agent
{
use Promptable, HasPromptTemplate;
public function promptName(): string
{
return 'coaching/sales'; // loads from prompts/coaching/sales/
}
}
By default, the active version is loaded. Pin to a specific version by overriding promptVersion():
public function promptVersion(): ?int
{
return 2; // Always use v2
}
Return null (the default) to always load the active version — useful for A/B testing and gradual rollouts.
Pass dynamic values into your prompt templates by overriding promptVariables():
class SalesCoach implements Agent
{
use Promptable, HasPromptTemplate;
public function __construct(public User $user) {}
public function promptVariables(): array
{
return [
'user_name' => $this->user->name,
'company' => $this->user->company,
];
}
}
In your system.md:
You are a sales coach for {{ $company }}.
You are helping {{ $user_name }} improve their technique.
Variables are interpolated into all roles (system, user, assistant, etc.) when accessed via instructions() or promptMessages().
Here's a complete agent using all Deck features with the AI SDK:
<?php
namespace App\Ai\Agents;
use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use PromptPHP\Deck\Ai\TrackPromptMiddleware;
use PromptPHP\Deck\Concerns\HasPromptTemplate;
class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput, HasMiddleware
{
use Promptable, HasPromptTemplate;
public function __construct(public User $user) {}
// instructions() is provided by HasPromptTemplate — no need to define it.
/**
* Dynamic variables injected into the prompt template.
*/
public function promptVariables(): array
{
return [
'user_name' => $this->user->name,
'company' => $this->user->company,
];
}
/**
* Conversation context from the prompt template plus history.
*/
public function messages(): iterable
{
return $this->promptMessages();
}
/**
* Tools available to the agent.
*/
public function tools(): iterable
{
return [
new RetrievePreviousTranscripts,
];
}
/**
* Structured output schema.
*/
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
/**
* Middleware for automatic tracking.
*/
public function middleware(): array
{
return [
new TrackPromptMiddleware,
];
}
}
Create and populate the prompt files:
php artisan make:prompt sales-coach --user
Edit prompts/sales-coach/v1/system.md:
You are a sales coach for {{ $company }}.
You are helping {{ $user_name }} improve their sales technique.
Analyse transcripts carefully and provide:
- Specific, actionable feedback
- A score from 1-10
If your agent implements Conversational, you can load pre-defined conversation context from Deck role files using the promptMessages() method.
By default, promptMessages() returns all roles except system (which goes through instructions()):
public function messages(): iterable
{
// Returns Message[] for all non-system roles (user, assistant, etc.)
return $this->promptMessages();
}
Pass an array to limit which roles are included:
public function messages(): iterable
{
return $this->promptMessages(['user']);
}
Combine template messages with conversation history from your database:
use Laravel\Ai\Messages\Message;
public function messages(): iterable
{
// Pre-defined context from the prompt template.
$context = $this->promptMessages();
// Plus conversation history from the database.
$history = History::where('user_id', $this->user->id)
->latest()
->limit(50)
->get()
->reverse()
->map(fn ($m) => new Message($m->role, $m->content))
->all();
return array_merge($context, $history);
}
The TrackPromptMiddleware automatically records prompt executions via Deck's tracking system.
```php
'tracking' => [
'enabled' => true,
'connection' => null, // uses default database connection
],
```
class SalesCoach implements Agent, HasMiddleware
{
use Promptable, HasPromptTemplate;
public function middleware(): array
{
return [
new TrackPromptMiddleware,
];
}
}
```
The middleware automatically records the following fields to the prompt_executions table:
| Field | Source |
|---|---|
prompt_name |
Agent's promptName() method. |
prompt_version |
Resolved template version number. |
input |
The user's prompt text from the AgentPrompt. |
output |
The AI response text. |
tokens |
Total token usage from the response. |
latency_ms |
Round-trip time in milliseconds (measured via hrtime). |
model |
Model used (e.g. gpt-4o, claude-3-sonnet). |
provider |
Provider name (e.g. openai, anthropic). |
The middleware:
hrtime(true).then() hook to record execution data after the response completes.PromptManager::track() with the collected data.The middleware only tracks agents that use the HasPromptTemplate trait. If the agent doesn't have a promptName() method, the tracking is silently skipped.
You can access the full PromptTemplate object from within your agent for advanced use cases:
// Get the template instance.
$template = $this->promptTemplate();
// Check available roles.
$template->roles(); // ['system', 'user', 'assistant']
$template->has('skill'); // false
// Get raw content (no interpolation).
$template->raw('system');
// Get the resolved version.
$template->version(); // 2
// Get prompt metadata.
$template->metadata(); // ['description' => '...', ...]
The template instance is cached for the lifetime of the agent object, so repeated calls to promptTemplate() don't incur additional filesystem or cache lookups.
Clear the cached template to force a fresh load on next access:
$agent->forgetPromptTemplate();
This is useful in:
The method returns $this for fluent chaining:
$agent->forgetPromptTemplate()->promptTemplate(); // fresh load
The HasPromptTemplate trait works even without laravel/ai installed. The instructions() method simply returns a string, and promptMessages() falls back to returning raw arrays instead of AI SDK Message objects:
// Without laravel/ai — returns array
$messages = $agent->promptMessages();
// [['role' => 'user', 'content' => '...'], ...]
// With laravel/ai — returns Message[]
$messages = $agent->promptMessages();
// [Message('user', '...'), ...]
This allows you to use Deck's template loading in any context, not just with the AI SDK.
How can I help you explore Laravel packages today?