maxai / claude-managed-agents
Run your Claude Code skills from a Laravel application, inside Anthropic-hosted Managed Agents sandboxes, with queued jobs, live progress and downloaded outputs.
Requires
- php: ^8.3
- anthropic-ai/sdk: ^0.57
- guzzlehttp/guzzle: ^7.8|^8.0
- illuminate/bus: ^12.0|^13.0
- illuminate/console: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/events: ^12.0|^13.0
- illuminate/filesystem: ^12.0|^13.0
- illuminate/queue: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- symfony/yaml: ^7.0|^8.0
Requires (Dev)
- laravel/pint: ^1.20
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-10 15:47:06 UTC
README
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();
How it works
- 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.
Requirements
- 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.
Installation
composer require maxai/claude-managed-agents php artisan migrate
ANTHROPIC_API_KEY=sk-ant-... QUEUE_CONNECTION=database
Publish the config if you want to change the defaults:
php artisan vendor:publish --tag=claude-agents-config
Try it without an API key first: CLAUDE_AGENTS_DRIVER=fake simulates runs end to end, free.
Running a skill
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();
Following a run
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 queue
Events, 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 |
Artisan
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 run
Writing skills for both Claude Code and your app
Any 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".
Networking
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.
Testing your app
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.
Costs
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.
Security
- 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.
License
MIT.