Skip to content

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.

php
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

php
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

php
$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):

php
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

php
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.95

Voice

Start a real-time voice session using the agent's tools, instructions, and voice config:

php
$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:

php
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:

php
$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.

php
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;
    }
}

Delegating to another agent

You can also list an agent in tools() to hand a task off to it. See Sub-agents.

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:

php
class ResearchAgent extends Agent
{
    public function concurrent(): bool
    {
        return true;
    }

    // ...
}

Or enable at call time:

php
$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 process forking
  • Without — falls back to sequential execution through the sync driver

A step that contains only one tool call always runs inline — there is nothing to parallelize.

Concurrent Sub-agents

Sub-agent delegations parallelize too. When the model delegates to several sub-agents in a single step, they run at the same time — each in its own forked process — and every sub-agent's response is returned to the parent. The parent's tool loop resumes only once all of them complete, so it always continues with the full set of results:

php
class CoordinatorAgent extends Agent
{
    public function concurrent(): bool
    {
        return true;
    }

    public function tools(): array
    {
        // Three independent sub-agents the model can fan out to in one step.
        return [ResearchAgent::class, PricingAgent::class, LegalAgent::class];
    }
}

The complete execution lineage is preserved across the fork boundary: the parent → child tree, each sub-agent's own token usage, the rolled-up subtree usage, and the depth/cycle guards all behave exactly as they do sequentially. Only wall-clock time changes.

Real-time events from concurrent sub-agents

The parent still emits its own AgentToolCallStarted / AgentToolCallCompleted events for each delegation (so the call and its result broadcast normally). But a sub-agent's internal orchestration events — its own AgentStarted / AgentCompleted, step events, and nested tool-call events — fire inside the forked child process and are not delivered to in-process listeners in the parent. There is no inter-process channel back.

This affects only live, in-process observability while a sub-agent is still running (broadcasting each step as it happens, listener-driven side effects). It does not limit what you can show after it completes. The child writes its full execution / step / tool-call tree to the database from inside the fork, so once a concurrent sub-agent finishes you can load and display everything it did — its final response, each step's text, and every tool it ran (with arguments, results, and timing) — by drilling into the delegation tree:

php
use Atlasphp\Atlas\Persistence\Models\Execution;

// Open a completed delegation tool call → the sub-agent that ran it.
$child = Execution::where('parent_tool_call_id', $toolCallId)->first();

$child->steps;      // each step, with ->content (response text) and ->reasoning
$child->toolCalls;  // every tool it ran, with ->arguments, ->result, ->duration_ms
$child->usage;      // its own token usage

So a UI that opens a finished sub-agent call shows its complete response, steps, and tools. Only the live, as-it-happens stream of a sub-agent's internals is unavailable under concurrency — for that, use sequential delegation (the default).

Persistence & Fork Safety

Persistence tracking writes to the database from inside the forked child processes. Database connections (PostgreSQL, MySQL) are not fork-safe — a child that inherits and uses the parent's connection socket would corrupt the wire protocol.

Atlas handles this for you. Before forking, it closes the parent's resolved database connections so each child opens its own fresh connection; the parent transparently reconnects on its next query. This reset runs only when the fork driver is active and persistence is enabled, so the in-process sync driver and non-persistent runs are never affected. Concurrent execution with persistence tracking is safe out of the box — no configuration required.

Requirements & Environment

  • Atlas ships with spatie/fork as a dependency — no extra installation needed. True parallelism requires the pcntl PHP extension (standard on Linux/macOS, not available on Windows). Without pcntl, concurrent mode falls back to sequential execution.
  • Fork-based parallelism runs in CLI, queue-worker, and Artisan contexts. It does not run inside PHP-FPM web requests (forking a live web worker is unsafe), where Atlas falls back to sequential. To parallelize long-running agent work from a web request without blocking it, dispatch the execution to the queue and let workers run concurrently.

When to Use

Enable concurrency when:

  • Tools or sub-agents are independent and don't depend on each other's results
  • They make external API calls (including sub-agent model calls) where parallelism reduces wall-clock time

Keep sequential (default) when:

  • Tools have side effects that depend on execution order
  • You're running on Windows or an environment without pcntl (it falls back to sequential anyway)

Conversations (Optional)

For persistent conversations, use ->for() and ->forConversation(). Requires persistence to be enabled.

php
// 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.

php
// 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.

php
$response = Atlas::agent('support')
    ->forConversation($conversationId)
    ->retry()
    ->asText();

When you retry:

  1. The current active response is deactivated (is_active = false)
  2. A new response is generated with the same conversation context
  3. The new response shares the same parent_id as the original — creating a sibling
  4. 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:

php
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:

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.

MethodReturnDefaultDescription
key()stringClass name in kebab-caseUnique key for Atlas::agent('key')
name()stringClass name with spacesDisplay name
description()?stringnullDescription for agent-to-agent delegation
provider()Provider|string|nullnullProvider (falls back to config default)
model()?stringnullModel identifier (falls back to config default)
instructions()?stringnullSystem instructions with {variable} interpolation
tools()array[]Atlas tool classes the agent can invoke
providerTools()array[]Provider-native tools (web search, code execution)
temperature()?floatnullSampling temperature
maxTokens()?intnullMaximum response tokens
maxSteps()?intnullMaximum tool loop iterations
concurrent()boolfalseExecute 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.

MethodDescription
->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

bash
php artisan make:agent SupportAgent

Next 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

Released under the MIT License.