Agents
Agents are reusable AI configurations — a class that defines provider, model, instructions, tools, and behavior.
Defining an Agent
Extend Atlasphp\Atlas\Agent and override the methods you need. All methods have sensible defaults — an agent with no overrides is valid.
use Atlasphp\Atlas\Agent;
use Atlasphp\Atlas\Enums\Provider;
use Atlasphp\Atlas\Providers\Tools\ProviderTool;
class SupportAgent extends Agent
{
public function key(): string
{
return 'support'; // Auto-generated by default
}
public function name(): string
{
return 'Support'; // Auto-generated by default
}
public function description(): ?string
{
return 'Handles customer support inquiries.';
}
public function provider(): Provider|string|null
{
return 'anthropic';
}
public function model(): ?string
{
return 'claude-sonnet-4-20250514';
}
public function instructions(): ?string
{
return <<<'PROMPT'
You are a customer support specialist for {APP_NAME}.
## Customer Context
- **Name:** {customer_name}
- **Account Tier:** {account_tier}
## Guidelines
- Always greet the customer by name
- For order inquiries, use `lookup_order` before providing details
PROMPT;
}
public function tools(): array
{
return [
LookupOrderTool::class,
ProcessRefundTool::class,
];
}
public function providerTools(): array
{
return [];
}
public function temperature(): ?float
{
return 0.7;
}
public function maxTokens(): ?int
{
return null;
}
public function maxSteps(): ?int
{
return 8;
}
public function concurrent(): bool
{
return false;
}
public function providerOptions(): array
{
return [];
}
}Auto-Key Convention
The key() method defaults to the class name in kebab-case, minus the "Agent" suffix:
SupportAgent→'support'BillingAssistantAgent→'billing-assistant'CustomerServiceAgent→'customer-service'
Using Agents
use Atlasphp\Atlas\Atlas;
$response = Atlas::agent('support')
->withVariables([
'customer_name' => 'Sarah',
'account_tier' => 'Premium',
])
->message('I need help with my order.')
->asText();
echo $response->text;Streaming
$stream = Atlas::agent('support')
->withVariables(['customer_name' => 'Sarah'])
->message('Where is my order #12345?')
->asStream();
foreach ($stream as $chunk) {
echo $chunk->text;
}With Media
Send images, documents, audio, or video alongside your message. The agent's provider processes the media inline (e.g. vision for images, transcription for audio):
use Atlasphp\Atlas\Input\Image;
use Atlasphp\Atlas\Input\Document;
// Send an image for the agent to analyze
$response = Atlas::agent('support')
->message('What does this receipt show?', Image::fromUpload($request->file('receipt')))
->asText();
// Send a document for context
$response = Atlas::agent('analyst')
->message('Summarize this report', Document::fromStorage('reports/q4.pdf'))
->asText();
// Multiple attachments
$response = Atlas::agent('reviewer')
->message('Compare these two images', [
Image::fromUrl('https://example.com/before.jpg'),
Image::fromUrl('https://example.com/after.jpg'),
])
->asText();Media can be loaded from uploads, URLs, local paths, Laravel storage, or base64. See Message Attachments for persistence and how agent-generated files are tracked.
Structured Output
use Atlasphp\Atlas\Schema\Schema;
$schema = Schema::object('sentiment', 'Sentiment analysis result')
->enum('sentiment', 'Detected sentiment', ['positive', 'negative', 'neutral'])
->number('confidence', 'Confidence score 0-1')
->string('reasoning', 'Explanation')
->build();
$response = Atlas::agent('analyzer')
->withSchema($schema)
->message('I love this product!')
->asStructured();
$response->structured['sentiment']; // "positive"
$response->structured['confidence']; // 0.95Voice
Start a real-time voice session using the agent's tools, instructions, and voice config:
$session = Atlas::agent('sarah-voice')
->for($user)
->forConversation($conversationId)
->asVoice();
return response()->json($session->toClientPayload());The browser connects directly to the provider for audio. Tools are executed server-side via the package-provided endpoint.
Voice agents can define a voice() method to set their voice identity:
class SarahVoiceAgent extends Agent
{
public function voice(): ?string
{
return 'eve'; // xAI voice ID
}
}When forConversation($id) is set, the voice agent receives the conversation's text history as context — so it knows what was discussed in prior text messages. Voice transcripts are stored in a dedicated VoiceCall record, not as individual messages. Listen for VoiceCallCompleted to post-process the transcript (summarize, create messages, embed into memory).
See Voice Modality for transport details, Voice Integration for the full setup guide, and Post-Processing Patterns for handling transcripts.
Runtime Overrides
Every agent configuration can be overridden at call time via fluent methods on AgentRequest:
$response = Atlas::agent('support')
->withProvider('openai', 'gpt-4o')
->withMaxTokens(500)
->withTemperature(0.3)
->withMaxSteps(5)
->withConcurrent()
->withTools([ExtraTool::class])
->withProviderTools([new WebSearch])
->withProviderOptions(['seed' => 12345])
->withVariables(['customer_name' => 'John'])
->withMeta(['user_id' => 123])
->withMessages($conversationHistory)
->withSchema($schema)
->message('Hello')
->asText();Agent with Tools
Define tools the agent can call. Atlas handles the tool loop automatically — calling the model, executing tools, and feeding results back until the model produces a final response.
class ResearchAgent extends Agent
{
public function provider(): Provider|string|null
{
return 'openai';
}
public function model(): ?string
{
return 'gpt-4o';
}
public function instructions(): ?string
{
return 'You are a research assistant. Use your tools to find and analyze information.';
}
public function tools(): array
{
return [
SearchWebTool::class,
ReadUrlTool::class,
SummarizeTool::class,
];
}
public function maxSteps(): ?int
{
return 10;
}
}Use maxSteps() to limit tool loop iterations and prevent runaway execution.
Concurrent Tool Execution
By default, tool calls execute sequentially — one at a time. This is the safest option and works with all configurations including persistence tracking.
Enabling Concurrency
Override concurrent() in your agent to run tool calls in parallel:
class ResearchAgent extends Agent
{
public function concurrent(): bool
{
return true;
}
// ...
}Or enable at call time:
$response = Atlas::agent('research')
->withConcurrent()
->message('Research these three topics')
->asText();How It Works
When the model requests multiple tool calls in a single step, Atlas runs them simultaneously using Laravel's Concurrency facade:
- With
spatie/fork+pcntl— true parallel execution via OS-level forking - Without — falls back to sequential execution through the sync driver
Requirements
Atlas ships with spatie/fork as a dependency — no extra installation needed. However, true parallelism requires the pcntl PHP extension, which is standard on most Linux/macOS setups but not available on Windows. Without pcntl, concurrent mode falls back to sequential execution.
When to Use
Enable concurrency when:
- Tools are independent and don't depend on each other's results
- Tools make external API calls where parallelism reduces wall-clock time
- You're not using persistence or understand the fork-safety implications
Keep sequential (default) when:
- Tools have side effects that depend on execution order
- You need reliable persistence tracking for every tool call
- You're running on Windows or an environment without
pcntl
Fork Safety
When using the fork driver with persistence enabled, tool call tracking runs inside forked child processes. Database connections are not fork-safe — the child inherits the parent's TCP socket. For production use with persistence, prefer sequential execution or ensure your database driver handles reconnection after fork.
Conversations (Optional)
For persistent conversations, use ->for() and ->forConversation(). Requires persistence to be enabled.
// Start a new conversation for a user
$response = Atlas::agent('support')
->for($user)
->message('I need help with my account.')
->asText();
// Continue an existing conversation
$response = Atlas::agent('support')
->for($user)
->forConversation($conversationId)
->message('What about my billing?')
->asText();
// Multi-user conversation — specify who is sending the message
$response = Atlas::agent('support')
->for($team, as: $currentUser)
->forConversation($conversationId)
->message('Adding a note to this conversation.')
->asText();Respond Without User Message
Proactively have the agent respond in a conversation thread without a new user message. Useful for scheduled follow-ups, background task results, or proactive notifications.
// Agent responds to the thread without a new user message
$response = Atlas::agent('support')
->forConversation($conversationId)
->respond()
->asText();Requires Existing Conversation
respond() requires forConversation($id). The agent must join an existing conversation — there's no user message to create one.
Retry Last Response
Regenerate the last assistant response to get a different answer — like hitting "regenerate" in a chat UI.
$response = Atlas::agent('support')
->forConversation($conversationId)
->retry()
->asText();When you retry:
- The current active response is deactivated (
is_active = false) - A new response is generated with the same conversation context
- The new response shares the same
parent_idas the original — creating a sibling - Only the latest response is active and included in future conversation history
You can retry multiple times. Each retry creates another sibling. Only one is active at a time.
Can Retry?
A response can only be retried if no user message was sent after it. Once the conversation continues, earlier responses are locked.
Sibling Messages
Retries create sibling messages — multiple responses to the same user message. Only one sibling group is active at a time.
For chat UIs that show "1 of 3" navigation between response alternatives:
use Atlasphp\Atlas\Persistence\Services\ConversationService;
$service = app(ConversationService::class);
// Get sibling info for a message
$info = $service->siblingInfo($message);
// ['current' => 2, 'total' => 3, 'groups' => [...]]
// Switch to a different sibling
$service->cycleSibling($conversation, $message->parent_id, $targetIndex);See the Conversations Guide for the full persistence system.
Auto-Discovery
Agents are automatically discovered from your configured directory. Set the path and namespace in config/atlas.php:
'agents' => [
'path' => app_path('Agents'),
'namespace' => 'App\\Agents',
],Any class extending Agent in that directory is registered and available via Atlas::agent('key').
API Reference
Agent Methods
All methods have sensible defaults. Override only what you need.
| Method | Return | Default | Description |
|---|---|---|---|
key() | string | Class name in kebab-case | Unique key for Atlas::agent('key') |
name() | string | Class name with spaces | Display name |
description() | ?string | null | Description for agent-to-agent delegation |
provider() | Provider|string|null | null | Provider (falls back to config default) |
model() | ?string | null | Model identifier (falls back to config default) |
instructions() | ?string | null | System instructions with {variable} interpolation |
tools() | array | [] | Atlas tool classes the agent can invoke |
providerTools() | array | [] | Provider-native tools (web search, code execution) |
temperature() | ?float | null | Sampling temperature |
maxTokens() | ?int | null | Maximum response tokens |
maxSteps() | ?int | null | Maximum tool loop iterations |
concurrent() | bool | false | Execute tool calls concurrently |
providerOptions() | array | [] | Provider-specific options passed through directly |
AgentRequest Fluent Methods
Returned by Atlas::agent('key'). Chain these before a terminal method.
| Method | Description |
|---|---|
->message(string $text, array|Input $media = []) | Set the user message (with optional media) |
->instructions(string $directive) | Override the agent's system instructions |
->withVariables(array $variables) | Set variables for instruction interpolation |
->withMeta(array $meta) | Attach metadata accessible in tools via ToolContext |
->withProvider(Provider|string $provider, string $model) | Override provider and model |
->withMaxTokens(int $tokens) | Override max response tokens |
->withTemperature(float $temp) | Override sampling temperature |
->withMaxSteps(?int $maxSteps) | Override max tool loop iterations |
->withConcurrent(bool $concurrent = true) | Override concurrent tool execution |
->withTools(array $tools) | Add tools in addition to the agent's tools |
->withProviderTools(array $providerTools) | Add provider tools at runtime |
->withProviderOptions(array $options) | Override provider-specific options |
->withSchema(Schema $schema) | Set structured output schema |
->withMessages(array $messages) | Provide conversation history |
->for(Model $owner, ?Model $as = null) | Set conversation owner. Optionally pass as: to set a different message sender (persistence) |
->forConversation(int $id) | Join an existing conversation (persistence) |
->asUser(Model $owner) | (Deprecated) Use for($owner, as: $user) instead |
->withMessageLimit(int $limit) | Override message history limit (persistence) |
->respond() | Respond without a new user message (persistence) |
->retry() | Retry the last assistant response (persistence) |
->asText() | Execute and return TextResponse |
->asStream() | Execute and return StreamResponse |
->asStructured() | Execute and return StructuredResponse |
->asVoice() | Start a voice session and return VoiceSession |
Artisan Command
php artisan make:agent SupportAgentNext Steps
- Instructions — Variable interpolation in instructions
- Tools — Build tools agents can call
- Text — Text generation, streaming, and structured output
- Schema — Schema fields for structured output
- Middleware — Add middleware to agent execution