PHPackages                             akoslabs/conductor - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. akoslabs/conductor

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

akoslabs/conductor
==================

A Laravel package for building production AI features: agents, multi-step workflows, RAG pipelines, and tool-calling chains.

v1.0.0(1mo ago)01MITPHPPHP ^8.2CI passing

Since Mar 15Pushed 1mo agoCompare

[ Source](https://github.com/akoslabs/conductor)[ Packagist](https://packagist.org/packages/akoslabs/conductor)[ RSS](/packages/akoslabs-conductor/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (10)Versions (2)Used By (0)

Conductor
=========

[](#conductor)

Orchestration layer for building AI features in Laravel — agents, workflows, RAG, tool calling. You bring your API keys, Conductor handles the plumbing.

```
$response = Conductor::agent('support-bot')
    ->using('anthropic', 'claude-sonnet-4-20250514')
    ->withSystemPrompt('You are a helpful support agent.')
    ->withTools([new SearchKnowledgeBase, new CreateTicket])
    ->run('I need help resetting my password');

echo $response->text();
```

Under the hood, all LLM calls go through [Prism](https://github.com/prism-php/prism). Whatever provider Prism supports (Anthropic, OpenAI, Ollama, Mistral, etc.), Conductor supports.

Requirements
------------

[](#requirements)

PHP 8.2+, Laravel 11 or 12.

Installation
------------

[](#installation)

```
composer require conductor-php/conductor
```

Then publish config + migrations:

```
php artisan vendor:publish --provider="Conductor\ConductorServiceProvider"
php artisan migrate
```

In your `.env`, set your provider, model, and the matching API key:

```
CONDUCTOR_PROVIDER=anthropic
CONDUCTOR_MODEL=claude-sonnet-4-20250514
ANTHROPIC_API_KEY=your-key-here
```

Each provider has its own key variable:

ProviderAPI Key VariableExample Model`anthropic``ANTHROPIC_API_KEY``claude-sonnet-4-20250514``openai``OPENAI_API_KEY``gpt-4o``mistral``MISTRAL_API_KEY``mistral-large-latest``groq``GROQ_API_KEY``llama-3.1-70b-versatile``gemini``GEMINI_API_KEY``gemini-1.5-pro``xai``XAI_API_KEY``grok-2``deepseek``DEEPSEEK_API_KEY``deepseek-chat``ollama`none (runs locally)`llama3`See [Prism's docs](https://github.com/prism-php/prism) for more provider config options like custom URLs and org IDs.

Agents
------

[](#agents)

You can define agents inline with the builder or as standalone classes. The builder is great for one-offs, classes are better when you're reusing the same agent across your app.

### Builder

[](#builder)

```
use Conductor\Facades\Conductor;

$response = Conductor::agent('summarizer')
    ->using('anthropic', 'claude-sonnet-4-20250514')
    ->withSystemPrompt('Summarize the given text in 2-3 sentences.')
    ->withTokenBudget(1000)
    ->run('Long article text here...');

$response->text();             // "The article discusses..."
$response->promptTokens();     // 342
$response->completionTokens(); // 87
$response->costUsd();          // 0.0018
```

Chain whatever you need — order doesn't matter:

```
Conductor::agent('research-assistant')
    ->using('openai', 'gpt-4o')
    ->withSystemPrompt('You help with research tasks.')
    ->withTools([new WebSearch, new ExtractData])
    ->withMemory('conversation-123')
    ->withFallback('anthropic', 'claude-sonnet-4-20250514')
    ->withMaxSteps(5)
    ->withTokenBudget(4000)
    ->run('Find recent papers on transformer architectures');
```

### Streaming

[](#streaming)

```
$stream = Conductor::agent('writer')
    ->withSystemPrompt('Write creative fiction.')
    ->stream('Tell me a story about a cat');

foreach ($stream as $chunk) {
    echo $chunk;
}
```

### Class-based agents

[](#class-based-agents)

```
php artisan make:conductor-agent SupportAgent
```

Creates `app/Agents/SupportAgent.php`:

```
use Conductor\Agents\Agent;

final class SupportAgent extends Agent
{
    protected string $name = 'support-agent';
    protected string $provider = 'anthropic';
    protected string $model = 'claude-sonnet-4-20250514';
    protected ?int $tokenBudget = 2000;

    public function systemPrompt(): string
    {
        return 'You are a customer support agent. Be helpful and concise.';
    }

    public function tools(): array
    {
        return [
            new SearchFaq,
            new CreateTicket,
        ];
    }

    public function memory(): ?string
    {
        return null; // return a conversation ID to enable memory
    }
}
```

Then just call it:

```
$response = SupportAgent::run('How do I cancel my subscription?');
```

### Structured output

[](#structured-output)

If you need typed data back instead of free text, pass a schema:

```
$response = Conductor::agent('extractor')
    ->withSystemPrompt('Extract contact info from text.')
    ->withSchema([
        'type' => 'object',
        'properties' => [
            'name' => ['type' => 'string'],
            'email' => ['type' => 'string'],
            'phone' => ['type' => 'string'],
        ],
        'required' => ['name', 'email'],
    ])
    ->run('Reach me at jane@example.com, my name is Jane Park');

$response->structured();
// ['name' => 'Jane Park', 'email' => 'jane@example.com', 'phone' => null]
```

Tools
-----

[](#tools)

Tools give agents the ability to call your code. Extend `Tool` and fill in the blanks:

```
use Conductor\Tools\Tool;

final class GetWeather extends Tool
{
    public function name(): string
    {
        return 'get-weather';
    }

    public function description(): string
    {
        return 'Get the current weather for a city';
    }

    public function parameters(): array
    {
        return [
            'type' => 'object',
            'properties' => [
                'city' => [
                    'type' => 'string',
                    'description' => 'City name',
                ],
            ],
            'required' => ['city'],
        ];
    }

    public function execute(array $arguments): string|array
    {
        return Weather::forCity($arguments['city'])->toArray();
    }
}
```

Or scaffold one: `php artisan make:conductor-tool GetWeather`

Workflows
---------

[](#workflows)

For anything multi-step. Steps can depend on each other, run conditionally, run in parallel, or require human approval before continuing.

```
use Conductor\Facades\Conductor;

$result = Conductor::workflow('content-pipeline')
    ->step('research', function ($state) {
        $response = Conductor::agent('researcher')
            ->withSystemPrompt('Research the given topic.')
            ->run($state->input());

        return $response->text();
    })
    ->step('draft', function ($state) {
        return Conductor::agent('writer')
            ->withSystemPrompt('Write an article based on this research.')
            ->run($state->get('research'));
    }, dependsOn: ['research'])
    ->step('review', function ($state) {
        return Conductor::agent('editor')
            ->withSystemPrompt('Review and improve this draft.')
            ->run($state->get('draft'));
    }, dependsOn: ['draft'])
    ->start('The future of renewable energy');

$result->output()->text(); // the final edited article
$result->totalTokens();
$result->totalCostUsd();
```

### Conditionals

[](#conditionals)

Only run a step if a condition is met:

```
->when(
    name: 'translate',
    condition: fn ($state) => $state->get('needs_translation') === true,
    callable: fn ($state) => Conductor::agent('translator')->run($state->get('draft')),
    dependsOn: ['draft']
)
```

### Human approval gates

[](#human-approval-gates)

Sometimes you want a person to sign off before the workflow continues. `humanApproval` pauses execution and persists the run so you can resume it later.

```
$result = Conductor::workflow('publish-flow')
    ->step('draft', fn ($state) => /* ... */)
    ->humanApproval('review', fn ($state) => $state->get('draft'), dependsOn: ['draft'])
    ->step('publish', fn ($state) => /* ... */, dependsOn: ['review'])
    ->start('Write a blog post');

$result->status();  // 'paused'
$result->runId();   // 'abc-123'

// later, when the reviewer approves:
$final = Conductor::workflow('publish-flow')
    ->resume($result->runId(), ['approved' => true, 'feedback' => 'Looks good']);
```

### Parallel steps

[](#parallel-steps)

```
->parallel([
    'seo-check' => fn ($state) => /* ... */,
    'grammar-check' => fn ($state) => /* ... */,
    'tone-check' => fn ($state) => /* ... */,
], dependsOn: ['draft'])
```

### Retries

[](#retries)

Steps automatically retry on failure. The defaults live in config (`workflows.default_retry_attempts`), but you can override per step:

```
->step('flaky-api', fn ($state) => /* ... */, retries: 5, backoffMs: 2000)
```

You can also generate workflow classes: `php artisan make:conductor-workflow ContentPipeline`

RAG
---

[](#rag)

Ingest documents, chunk them up, generate embeddings, then query by similarity. Nothing fancy — just the basics done right.

```
use Conductor\Rag\RagPipeline;

$pipeline = app(RagPipeline::class);

// ingest a document
$chunks = $pipeline
    ->using('openai', 'text-embedding-3-small')
    ->withChunking(size: 500, overlap: 50)
    ->ingest(
        content: file_get_contents('docs/guide.md'),
        documentId: 'guide-v2',
        metadata: ['source' => 'docs']
    );

// query it
$results = $pipeline->query('How do I configure authentication?', limit: 5);
```

To wire RAG into an agent:

```
$retriever = app(\Conductor\Contracts\RetrieverInterface::class);

Conductor::agent('docs-bot')
    ->withSystemPrompt('Answer questions using the provided context.')
    ->withRag($retriever, limit: 5)
    ->run('How do I reset my password?');
```

For vector storage, the default is `memory` which is fine for dev and tests. For production you'll want `pgvector` (needs the pgvector Postgres extension):

```
CONDUCTOR_VECTOR_STORE=pgvector
```

Memory
------

[](#memory)

Give an agent a conversation ID and it'll remember previous messages:

```
Conductor::agent('chat')
    ->withSystemPrompt('You are a helpful assistant.')
    ->withMemory('user-42-session')
    ->run('My name is Alex');

// next request, same session
Conductor::agent('chat')
    ->withSystemPrompt('You are a helpful assistant.')
    ->withMemory('user-42-session')
    ->run('What is my name?');
// "Your name is Alex."
```

Three drivers available:

- **database** — permanent storage, good for production
- **cache** — TTL-based, good for ephemeral sessions
- **array** — in-memory only, mostly useful in tests

Set via `CONDUCTOR_MEMORY_DRIVER=database` in your `.env`. You can also cap how many messages are stored per conversation with `memory.max_messages` in the config (defaults to 50).

Monitoring
----------

[](#monitoring)

Usage tracking is on by default. Every agent call gets logged to `conductor_usage_logs` with token counts, cost, duration, and which provider/model was used.

### Cost calculation

[](#cost-calculation)

There's a built-in calculator with pricing for common models:

```
use Conductor\Monitoring\CostCalculator;

$cost = CostCalculator::calculate(
    model: 'claude-sonnet-4-20250514',
    promptTokens: 1000,
    completionTokens: 500
);
```

### Budgets

[](#budgets)

You can cap spending at different levels:

```
CONDUCTOR_BUDGET_PER_REQUEST=0.50
CONDUCTOR_BUDGET_PER_WORKFLOW=5.00
CONDUCTOR_BUDGET_PER_HOUR=20.00
```

Hit a limit and you get a `TokenBudgetExceededException`. A `TokenBudgetExceeded` event fires too, so you can hook into it with a listener if you want alerts.

### Pulse

[](#pulse)

If you're running [Laravel Pulse](https://pulse.laravel.com), Conductor registers a recorder automatically. Shows up as a card on your Pulse dashboard.

Testing
-------

[](#testing)

`Conductor::fake()` swaps out the real thing for a fake that never hits any APIs. Works the same way as Laravel's built-in fakes.

```
use Conductor\Facades\Conductor;

Conductor::fake([
    'support-agent' => 'I can help you with that!',
    'summarizer' => 'This is a summary.',
]);

// your code runs against the fakes
$response = Conductor::agent('support-agent')
    ->withSystemPrompt('...')
    ->run('Help me');

$response->text(); // "I can help you with that!"
```

Then assert:

```
Conductor::assertAgentCalled('support-agent');
Conductor::assertAgentCalled('support-agent', times: 1);
Conductor::assertAgentNotCalled('translator');
Conductor::assertToolUsed('search-faq');
Conductor::assertTokensBelow(5000);
Conductor::assertWorkflowCompleted('content-pipeline');
```

Use `'*'` as a catch-all:

```
Conductor::fake(['*' => 'default response for any agent']);
```

For agents called multiple times, use sequences:

```
use Conductor\Testing\ConductorFake;

Conductor::fake([
    'chat' => ConductorFake::sequence([
        'First response',
        'Second response',
        fn () => 'Dynamic third response',
    ]),
]);
```

Artisan Commands
----------------

[](#artisan-commands)

```
php artisan make:conductor-agent MyAgent         # app/Agents/MyAgent.php
php artisan make:conductor-tool MyTool            # app/Tools/MyTool.php
php artisan make:conductor-workflow MyWorkflow     # app/Workflows/MyWorkflow.php

```

Events
------

[](#events)

Everything fires events you can listen to. All in the `Conductor\Events` namespace:

- `AgentStarted` / `AgentCompleted` / `AgentFailed`
- `ToolExecuted`
- `TokenBudgetExceeded`
- `WorkflowStarted` / `WorkflowStepCompleted` / `WorkflowPaused` / `WorkflowCompleted` / `WorkflowFailed`

Standard Laravel listeners:

```
Event::listen(AgentCompleted::class, function ($event) {
    Log::info("Agent {$event->agentName} finished in {$event->durationMs}ms");
});
```

Configuration
-------------

[](#configuration)

Published to `config/conductor.php`. The important env vars:

VariableDefaultWhat it does`CONDUCTOR_PROVIDER``anthropic`Default LLM provider`CONDUCTOR_MODEL``claude-sonnet-4-20250514`Default model`CONDUCTOR_MEMORY_DRIVER``database`Where conversation history is stored`CONDUCTOR_VECTOR_STORE``memory`Vector store backend for RAG`CONDUCTOR_BUDGET_PER_REQUEST``null`Spending cap per agent call`CONDUCTOR_BUDGET_PER_WORKFLOW``null`Spending cap per workflow`CONDUCTOR_BUDGET_PER_HOUR``null`Hourly spending capCheck the config file itself for the full list of options.

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance88

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

59d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/77e2b7b033bb0d8dc6671ad1ea8ff381f31a9a643652e81142a2091bb3ae3332?d=identicon)[RianMorningstar](/maintainers/RianMorningstar)

---

Top Contributors

[![RianMorningstar](https://avatars.githubusercontent.com/u/130930018?v=4)](https://github.com/RianMorningstar "RianMorningstar (26 commits)")

---

Tags

laravelaiagentsllmworkflowsrag

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/akoslabs-conductor/health.svg)

```
[![Health](https://phpackages.com/badges/akoslabs-conductor/health.svg)](https://phpackages.com/packages/akoslabs-conductor)
```

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M687](/packages/barryvdh-laravel-ide-helper)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k12.1M99](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[laragear/preload

Effortlessly make a Preload script for your Laravel application.

119363.5k](/packages/laragear-preload)[flarum/core

Delightfully simple forum software.

211.3M1.9k](/packages/flarum-core)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
