Run your Claude Code skills from a Laravel application.
The skills in your app's .claude/skills/ folder are the ones Claude Code runs when you type /name arguments. This package runs those same skills from PHP, in Anthropic-hosted Managed Agents sandboxes, with queued jobs, live progress, spend caps, and the agent's output files downloaded to your disk.
use function ClaudeManagedAgents\skill;
$run = skill('/one-page-site Kestrel Coffee, a coffee bar in Portland')
->budget(5)
->dispatch();- Your skill is uploaded to the Skills API the first time it runs, and again only when one of its files changes.
- An environment and an agent are created once and reused: the sandbox recipe, and an agent with your skill attached and a system prompt that tells it to behave as if you had typed the command in Claude Code, unattended.
- A session starts with your command as its first message, capped at your budget.
- A queued job follows it, storing each event (agent messages, tool calls, status changes) and the running cost, then queues itself again a few seconds later. No worker is held for the length of a run.
- When the session goes idle, everything the agent wrote to
/mnt/session/outputs/is downloaded to your disk and the run is marked completed.
Laravel events fire along the way, so your app can react without polling.
- PHP 8.3+, Laravel 12 or 13
- An Anthropic Console API key in a workspace with Managed Agents enabled. A claude.ai subscription login cannot be used to power an application.
- A real queue (database, Redis, SQS) and a running worker for
dispatch().run()works without one.
composer require maxai/claude-managed-agents
php artisan migrateANTHROPIC_API_KEY=sk-ant-...
QUEUE_CONNECTION=databasePublish the config if you want to change the defaults:
php artisan vendor:publish --tag=claude-agents-configTry it without an API key first: CLAUDE_AGENTS_DRIVER=fake simulates runs end to end, free.
use ClaudeManagedAgents\Facades\ClaudeAgents;
use function ClaudeManagedAgents\skill;
// Queue it and return straight away.
$run = skill('/release-notes '.$mergedChanges)->dispatch();
// Or run it here and wait, printing progress (Artisan commands, scripts).
$run = skill('/smoke-test hello')->run(function ($event, $run) {
echo $event->summary.PHP_EOL;
});
// The facade does the same.
$run = ClaudeAgents::skill('/one-page-site ...')->dispatch();Options, all optional:
skill('/one-page-site ...')
->budget(10) // hard cap in USD; null for none
->model('claude-opus-5-5') // default from config
->effort('high') // low, medium, high, xhigh (default), max
->environment('open-web') // a named environment from config
->until($rubric, maxIterations: 3) // grade against a rubric, revise until it passes
->for($user) // attach the run to any model
->withMetadata(['ticket' => 42])
->instructions('Use British English.')
->withRepository('https://github.com/acme/app', $token, 'main')
->dispatch();An AgentRun is an Eloquent model.
$run->status; // RunStatus enum: Pending, Starting, Running, Paused, Collecting, Completed, Failed, Cancelled
$run->cost(); // dollars spent so far, at list price
$run->last_message; // the agent's latest message
$run->messages(); // every agent message, oldest first
$run->events; // every stored event, with ->type, ->summary and ->payload
$run->files; // downloaded outputs
$run->output('index.html');
$run->result; // the agent's final message
$run->traceUrl(); // the session in the Anthropic Console
$run->raiseBudget(25); // a run paused at its budget resumes
$run->cancel(); // stop; outputs written so far are still collected
$run->sync(); // pull the latest events now, outside the queueEvents, for listeners:
| Event | When |
|---|---|
AgentRunStarted |
The session was created |
AgentRunProgressed |
A new session event arrived ($event->event->type, ->summary) |
AgentRunPaused |
The run reached its budget, or is waiting on an action |
AgentRunCompleted |
The session finished and its outputs are downloaded |
AgentRunFailed |
The run could not start, or its session ended in an error |
php artisan claude-agents:skills # what can run, and whether each is uploaded
php artisan claude-agents:run /smoke-test hello # run and follow it here
php artisan claude-agents:run /one-page-site "..." --queue --budget=5
php artisan claude-agents:status [run-id] [--sync]
php artisan claude-agents:budget {run-id} {dollars}
php artisan claude-agents:cancel {run-id}
php artisan claude-agents:publish [skill] [--force] # upload ahead of the first runAny Claude Code skill works, with three things to keep in mind:
- Outputs. Only files in
/mnt/session/outputs/come back. A skill that should work in both places can say: write to/mnt/session/outputs/when it exists, otherwise./outputs/. - Nobody answers questions. The agent is told to choose and say what it chose; a skill that depends on a conversation will not work unattended.
- The sandbox is not your laptop. It is Ubuntu with Python, Node, PHP 8.3, Playwright and Chromium preinstalled. A skill that calls your app's own code needs the repository mounted (
->withRepository()orCLAUDE_AGENTS_REPOSITORY) and its dependencies installed by the skill. Skill names may not contain "claude" or "anthropic".
The default environment is limited: package registries only. A skill that fetches arbitrary websites needs ->environment('open-web') (unrestricted egress) or a host allowlist in config/claude-agents.php.
use ClaudeManagedAgents\Facades\ClaudeAgents;
$fake = ClaudeAgents::fake()->outputs(['index.html' => '<h1>Hi</h1>']);
$run = skill('/one-page-site test')->dispatch()->refresh();
expect($run->output('index.html'))->toBe('<h1>Hi</h1>');
$fake->assertSkillUploaded('one-page-site');
$fake->assertSessionCreated(fn ($parameters) => $parameters['budget'] !== null);->pauseAtBudget(), ->eventsPerPoll(2), ->script([...]) and ->cost(1.5) shape the simulated session.
A run is billed to your API key at Anthropic's token prices plus the sandbox's running time. Most of a long run's cost is the conversation being re-read on every model request (cache reads), so it grows with how long the agent works. Always set a budget. The package records the session's list-price cost on the run; your invoice may differ if your organization has negotiated rates.
- Output files are written by a model that may have read untrusted web pages. Treat HTML outputs as untrusted: serve them with a
Content-Security-Policy: sandboxheader, never inline on your own origin. - Repository tokens passed to
withRepository()are stored encrypted and sent only to Anthropic's git proxy; they never enter the sandbox.
MIT.