PHP code example of obaid / laravel-clutch

1. Go to this page and download the library: Download obaid/laravel-clutch library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

obaid / laravel-clutch example snippets


$session = Clutch::agent(ResearchAgent::class)->for($user)->create();

$result = $session->prompt('Research our competitors and recommend a wedge.');

$response = (new ResearchAgent)->prompt('Research our competitors and write a brief.');

namespace App\Ai\Agents;

use Clutch\Laravel\Facades\Clutch;
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Contracts\RemembersConversations as RemembersConversationsContract;
use Laravel\Ai\Promptable;
use Stringable;

class ResearchAgent implements Agent, HasTools, RemembersConversationsContract
{
    use Promptable;

    // Laravel AI persists this agent's conversation, which is what lets a
    // harness session continue across requests, workers and deployments.
    use RemembersConversations;

    public function instructions(): Stringable|string
    {
        return 'You research competitors and write positioning briefs.';
    }

    public function tools(): iterable
    {
        return Clutch::policy([new SearchWeb, new FetchPage]);
    }
}

use Clutch\Laravel\Facades\Clutch;
use App\Ai\Agents\ResearchAgent;

$session = Clutch::agent(ResearchAgent::class)
    ->for($request->user())
    ->name('Competitor research')
    ->create();

$result = $session->prompt(
    'Research our three closest competitors and recommend a positioning wedge.'
);

return [
    'session_id' => $session->id,
    'run_id' => $result->run->id,
    'text' => $result->text,
    'artifacts' => $result->artifacts,
    'usage' => $result->usage,
];

$session = Clutch::session($sessionId)->authorizeFor($request->user());

$result = $session->prompt('Turn the recommendation into a one-page strategy memo.');

$run = $session->queue('Analyze every page on our website and produce a content gap report.');

return response()->accepted([
    'session_id' => $session->id,
    'run_id' => $run->id,
    'status' => $run->status,
]);

$session = Clutch::agent(ResearchAgent::class)
    ->for($user)
    ->onConnection('redis')
    ->onQueue('agents')
    ->timeout(seconds: 900)
    ->create();

Route::post('/research', function (Request $request) {
    $session = Clutch::session($request->session_id)->authorizeFor($request->user());

    return $session
        ->stream('Create the report and explain your progress.')
        ->usingVercelDataProtocol();
});

use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;

class PublishArticle implements Tool, Approvable
{
    use InteractsWithApprovals;

    // ...
}

$run = Clutch::run($runId)->authorizeFor($user);

$run->approve(
    approvalId: $approvalId,
    reason: 'The content has been reviewed and may be published.',
);

$run->reject(
    approvalId: $approvalId,
    reason: 'Do not publish this draft. Remove the unsupported claim first.',
);

Event::listen(ApprovalRequested::class, function (ApprovalRequested $event) {
    $event->approval->session->participant->notify(
        new AgentNeedsYou($event->approval)
    );
});

use Clutch\Laravel\Enums\PermissionMode;

$session = Clutch::agent(PublishingAgent::class)
    ->for($user)
    ->permissions(PermissionMode::ApproveSensitive)
    ->create();

'permissions' => [
    'tools' => [
        'search_web' => 'read_only',
        'draft_email' => 'reversible',
        'send_email' => 'irreversible',
    ],
],

class PublishArticle implements Tool, SensitiveTool
{
    public function sensitivity(): ToolSensitivity
    {
        return ToolSensitivity::Irreversible;
    }
}

public function tools(): iterable
{
    return Clutch::policy([
        new SearchWeb,
        new DraftEmail,
        new PublishArticle,
    ]);
}

Clutch::agent(SupportAgent::class)->onlyTools(['search_orders', 'draft_reply'])->create();
Clutch::agent(SupportAgent::class)->withoutTools(['issue_refund'])->create();

app(PolicyEngine::class)->extend(function (ToolInvocation $invocation, ToolSensitivity $sensitivity) {
    return $invocation->tenantId && Team::find($invocation->tenantId)?->on_trial
        ? PolicyDecision::

use Clutch\Laravel\Skills\Skill;
use Clutch\Laravel\Skills\SkillRegistry;

app(SkillRegistry::class)->add(new Skill(
    name: 'refund-policy',
    description: 'How we decide and process a refund.',
    content: <<<'TEXT'
    Refunds under $50 are automatic. Above that, check the account age first...
    TEXT,
));

// config/clutch.php
'skills' => ['path' => resource_path('skills')],

Clutch::agent(SupportAgent::class)->withSkills(['refund-policy'])->create();

use Clutch\Laravel\Workflows\Workflow;

class OnboardCustomer extends Workflow
{
    protected static ?string $agent = ResearchAgent::class;

    public function handle(array $payload): mixed
    {
        $research = $this->step('research', fn () => $this->prompt(
            "Research {$payload['domain']} and summarise how they sell."
        )->text);

        $this->emit('researched', ['chars' => strlen($research)]);

        $decision = $this->pause('sign-off', ['research' => $research]);

        if (! $decision['approved']) {
            return ['skipped' => $decision['reason']];
        }

        return $this->step('provision', fn () => $this->provision($payload, $research));
    }
}

$run = OnboardCustomer::dispatch(['domain' => 'acme.com']);

// Later, from another request entirely.
Workflow::resume($run->id, ['approved' => true]);

$invoice = $this->step('charge', fn () => $this->stripe->charge($customer));

$data = $this->steps([
    'account' => fn () => $this->billing->account($id),
    'usage'   => fn () => $this->metrics->usage($id),
]);

$decision = $this->pause('sign-off', ['draft' => $draft], 'Check the pricing claim.');

Workflow::resume($run->id, ['approved' => false, 'reason' => 'Fix the pricing claim.']);

$draft = $this->prompt("Write it up:\n{$research}", WriterAgent::class)->text;

Workflow::resume($run->id, ['approved' => true]);

public function produces(): array
{
    return ['reports/*.md'];
}

public function handle(array $payload): mixed
{
    $this->stage(['brief.txt' => $payload['brief']]);

    // ... work happens, writing into the workspace ...

    $this->workspace()->put('reports/findings.md', $findings);
}

Clutch::agent(ResearchAgent::class)
    ->for($user)
    ->sliceAfterSeconds(240)   // below your worker's timeout
    ->create();

ProduceReport::start()
    ->for($user)
    ->sliceAfterSeconds(1500)   // below a 1800s worker timeout
    ->dispatch(['report_id' => $id]);

// config/clutch.php
'guards' => [
    'remind_after_repeats' => 3,   // tell the model it is going in circles
    'block_after_repeats' => 8,    // refuse, and say why
    'tool_timeout_seconds' => 60,  // bound one call, not just the whole run
    'tool_timeouts' => ['scrape_page' => 120],
],

// config/clutch.php
'spill' => [
    'enabled' => true,
    'threshold_bytes' => 8192,
    'preview_bytes' => 1024,
],

// config/clutch.php
'compaction' => [
    'enabled' => true,
    'trigger_at_fraction' => 0.7,  // of the token budget
    'keep_first' => 2,
    'keep_recent' => 8,
],

use Clutch\Laravel\ValueObjects\RunBudget;

$session = Clutch::agent(ResearchAgent::class)
    ->for($user)
    ->budget(new RunBudget(
        maxSteps: 40,
        maxToolCalls: 100,
        maxTokens: 250_000,
        maxCostUsd: 8.00,
        maxDurationSeconds: 900,
    ))
    ->create();

$run->retry(resetBudget: true);

// config/clutch.php
'pricing' => [
    'anthropic:claude-sonnet-4-5' => ['input' => 3.00, 'output' => 15.00],
    'openai:gpt-5' => ['input' => 1.25, 'output' => 10.00],
],

$run = Clutch::run($runId)->authorizeFor($user);

$run->cancel(reason: 'The request is no longer needed.');

public function handle(Request $request): string
{
    foreach ($this->pages() as $page) {
        if (RunContext::current()?->isCancelled()) {
            break;
        }

        $this->analyze($page);
    }
}

use Clutch\Laravel\Artifacts\Artifact;
use Clutch\Laravel\Runtime\RunContext;

$artifact = Artifact::fromStorage(disk: 's3', path: 'reports/content-gap-2026-08-24.pdf')
    ->name('Content gap report')
    ->mimeType('application/pdf')
    ->metadata(['pages' => 18]);

RunContext::current()->artifacts()->add($artifact);

Artifact::fromContents($markdown, 'reports/gap.md')->name('Content gap report');

$artifacts = $result->artifacts;
$artifacts = Clutch::run($runId)->artifacts;

use Clutch\Laravel\Contracts\IdempotentTool;
use Clutch\Laravel\Data\ToolInvocation;
use Laravel\Ai\Contracts\Tool;

final class PublishArticle implements Tool, IdempotentTool
{
    public function idempotencyKey(ToolInvocation $invocation): string
    {
        // Key the side effect, not the call. Two retries of "publish article 42"
        // must produce the same key even though the tool-call IDs differ.
        return 'publish-article:'.$invocation->arguments['article_id'];
    }

    // Standard Laravel AI tool methods...
}

use Clutch\Laravel\Events\ClutchEventRecorded;

Event::listen(ClutchEventRecorded::class, function (ClutchEventRecorded $event) {
    // Audit, analytics, metering, notifications, or your own broadcasting.
});

'events' => [
    'redact' => ['authorization', 'api_key', 'token', 'password', 'secret'],
],

class EmailEventSerializer implements EventSerializer
{
    public function serialize(array $payload): array
    {
        return [
            'tool' => $payload['tool'],
            'arguments' => ['recipient_count' => count($payload['arguments']['to'])],
        ];
    }
}

'events' => [
    'serializers' => ['send_email' => EmailEventSerializer::class],
],

$result = $session->prompt('Score this draft against our content rubric.');

$score = $result->structured['score'];

$session = Clutch::agent(ResearchAgent::class)->driver('laravel-ai')->create();

// A future coding-agent driver.
$session = Clutch::runtime('codex')
    ->for($user)
    ->workspace($repository)
    ->sandbox('e2b')
    ->create();

use Clutch\Laravel\Testing\DriverContractTests;

it('passes the harness driver contract', function () {
    $result = DriverContractTests::for(new MyDriver)->run();

    expect($result['passed'])->toContain('round-trips a checkpoint');
});

use Clutch\Laravel\Facades\Clutch;
use Clutch\Laravel\Runtime\ClutchResult;

Clutch::fake([
    'Draft a brief' => ClutchResult::text('The proposed brief...'),
]);

$session = Clutch::agent(ResearchAgent::class)->for($user)->create();

$session->queue('Draft a brief');

Clutch::assertSessionCreated(ResearchAgent::class);
Clutch::assertRunQueued('Draft a brief');
Clutch::assertRunCompleted();
Clutch::assertNothingAwaitingApproval();

Clutch::fake([
    ClutchResult::awaitingApproval(
        tool: 'publish_article',
        arguments: ['article_id' => 123],
    ),
]);

$run = $session->queue('Publish the approved article.');

Clutch::assertApprovalRequested('publish_article');

Clutch::fake([
    ClutchResult::text('Report ready.')
        ->withToolCall('search_web', ['q' => 'pricing'], '12 results')
        ->withArtifact(Artifact::fromContents($pdf, 'report.pdf')->name('Report')),

    ClutchResult::structured(['score' => 87]),
    ClutchResult::failure('The provider is down.'),
]);

return [
    'default_driver' => env('CLUTCH_DRIVER', 'laravel-ai'),

    'queue' => [
        'connection' => env('CLUTCH_QUEUE_CONNECTION'),
        'queue' => env('CLUTCH_QUEUE', 'agents'),
        'timeout' => 900,
    ],

    'permissions' => [
        'default' => PermissionMode::ApproveSensitive->value,
        'tools' => [
            'search_web' => 'read_only',
            'draft_email' => 'reversible',
            'send_email' => 'irreversible',
        ],
    ],

    'events' => [
        'broadcast' => true,
        'persist_deltas' => true,
        'redact' => ['authorization', 'api_key', 'token', 'password', 'secret'],
        'serializers' => [],
    ],

    'budgets' => [
        'max_steps' => 50,
        'max_tool_calls' => 100,
        'max_tokens' => 250_000,
        'max_cost_usd' => null,
        'max_duration_seconds' => 900,
    ],

    'retention' => [
        'events' => 90,
        'checkpoints' => 30,
        'artifacts' => 365,
    ],

    'routes' => [
        'enabled' => true,
        'prefix' => 'api/clutch',
        'middleware' => ['api', 'auth'],
    ],
];
bash
composer h tables.
php artisan vendor:publish --provider="Clutch\Laravel\ClutchServiceProvider"

# Laravel AI's conversation tables, if you have not published them already.
# Session context lives there, so this step is not optional.
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"

php artisan migrate
bash
php artisan queue:work redis --queue=agents
bash
php artisan make:clutch-agent ResearchAgent
bash
php artisan make:clutch-workflow OnboardCustomer