michal78 / laravel-pandora
Pandora — a Laravel-native agentic framework: agents, tools, approvals, automations, memory and a Livewire control center.
Requires
- php: ^8.3
- dragonmantank/cron-expression: ^3.4
- illuminate/broadcasting: ^13.0
- illuminate/bus: ^13.0
- illuminate/console: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/events: ^13.0
- illuminate/notifications: ^13.0
- illuminate/queue: ^13.0
- illuminate/support: ^13.0
- symfony/uid: ^7.0|^8.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/ai: ^1.3
- laravel/pint: ^1.27
- league/flysystem-aws-s3-v3: ^3.0
- livewire/livewire: ^4.1
- mockery/mockery: ^1.6
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
- guzzlehttp/guzzle: Required by the HTTP-based provider adapters.
- laravel/ai: Required by the laravel-ai provider adapter, which reaches every text provider the Laravel AI SDK supports (Bedrock, Azure, Mistral, Groq, xAI, DeepSeek, Cohere, ...).
- laravel/reverb: Required for realtime streaming of runs, tool calls and approvals.
- league/flysystem-aws-s3-v3: Required for workspaces and context files on S3-compatible object storage (AWS, DigitalOcean, Hetzner, MinIO, R2).
- livewire/livewire: Required for the Pandora control center UI.
Provides
None
Conflicts
None
Replaces
None
- dev-development / 0.3.x-dev
- dev-master / 0.2.x-dev
- v0.2.0
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-phase-9/release-1.0
- dev-phase-9/install-and-upgrade
- dev-feature/laravel-ai-adapter
- dev-ci/minio-from-source-and-phpstan
- dev-ci/minio-from-quay
- dev-phase-9/security-model-honesty
- dev-phase-9/audit-session-isolation
- dev-phase-9/audit-exactly-once
- dev-phase-9/faster-server-engine-cleanup
This package is auto-updated.
Last update: 2026-10-30 16:06:44 UTC
README
An agentic framework for Laravel applications.
Agents, tools, approvals, automations and memory — under explicit, auditable policy,
with a Livewire control center for operating them.
Installation · Quick start · Architecture · Security model · Roadmap
⚠ v0.2.0 — deliberately still
0.xWhat works today: define an agent, start a conversation, dispatch a queued run, stream it over Reverb, persist an immutable trace, reload without losing anything, cancel it, and inspect it in the control center — with tools under five layers of authorization and human approval gates on the risky ones, multi-provider routing with failover and budgets, automations on four engines, scoped memory and context that works with no vector database installed, delegation and MCP where nothing remote is approved by discovering it, workspaces on local or S3-compatible storage, and messaging channels where an unlinked identity gets no run, no session and no seat.
What does not exist yet: the rest of release hardening — the performance suite, the release documentation audit and a human walkthrough (Phase 9, at 25 of 34 criteria). Install and upgrade safety is done: the quick start below is run by the suite. The threat-model sweep is done: every T1–T15 mitigation has been removed one at a time and the failure recorded, which found two security defects (fixed in v0.1.3) and one concurrency defect (fixed in v0.1.4); install and upgrade testing found two more (fixed in v0.2.0). See
docs/roadmap.md.Verified by 2,021 tests across SQLite, MySQL 8.4, MariaDB 11, PostgreSQL 17, pgvector and MinIO — 1,922 in the default leg, the rest skipping rather than passing without object storage, pgvector or a server database engine · PHPStan level 8, no baseline · Pint.
Before depending on this, read
docs/product/support-statement.md— it names what is supported, what is excluded by design, and what ships known untested. The API is usable and in use; it is not yet promised.1.0.0is defined as the day Phase 9's criteria are met, not a date.
Why
Two kinds of thing exist today, and neither fits a Laravel application.
Personal agent daemons (OpenClaw, Hermes Agent) are excellent — for one operator, on one machine. OpenClaw's own security documentation is explicit that it "is not a hostile multi-tenant security boundary." You cannot put one in front of your customers.
LLM SDKs give you ->chat() and leave durability, approvals, tenancy, auditing, cost control and
resumability as an exercise for the reader.
Pandora takes the capabilities of the first and gives them the trust, tenancy and authorization model of the framework you already run on. An agent run is a durable, queued, resumable, authorized, audited unit of work — closer to a job with a state machine than to a chat request.
The idea in one screen
Expose an application action. It is an ordinary PHP class, with ordinary Laravel authorization:
final class IssueRefund extends Tool { public function name(): string { return 'issue_refund'; } public function description(): string { return 'Issue a refund for an existing order.'; } public function rules(): array { // The JSON schema shown to the model is generated from these, so the // interface it is told about and the one enforced cannot drift. return [ 'order_id' => 'required|string|exists:orders,id', 'amount_minor' => 'required|integer|min:1|max:100000', ]; } public function risk(): RiskLevel { return RiskLevel::High; // → requires human approval by default } public function authorize(ToolInput $input, ToolContext $context): bool { // The *acting user's* policy. An agent can never exceed the person it acts for. return Gate::forUser($context->user()) ->allows('refund', Order::find($input->string('order_id'))); } public function handle(ToolInput $input, ToolContext $context): ToolResult { $this->refunds->issue($input->string('order_id'), $input->integer('amount_minor')); return ToolResult::success('Refund issued.'); } }
Register it, then grant it — registering installs a tool; each agent must still be given it:
// config/pandora.php 'tools' => ['registered' => [App\Tools\IssueRefund::class]],
Give it to an agent, and run it:
$run = Pandora::agent('support') ->forUser($user) ->withContext(['order_id' => $order->getKey()]) ->stream() ->dispatch('Help this customer resolve their order problem.');
The request returns immediately. A queue worker performs the run. The browser streams it over Reverb. The refund pauses for a human approval — costing nothing while it waits, surviving a deploy — then resumes. Every step is in the trace. Every decision is in the audit log.
What makes it different
| Multi-tenant and multi-user from the first migration | Not a single-operator daemon. Tenant and session isolation are tested, not assumed. |
| Authorized against the actor, not the agent | A tool call is checked against the acting user's Laravel gates and policies. An agent cannot do what the user could not. |
| Runs pause for free | An approval pause holds no worker and no memory. It can wait three days across two deploys. |
| Resumable by construction | All continuation state is in the database. A worker crash loses at most one iteration. |
| Memory needs no vector database | Lexical retrieval is the shipped path across all four databases. A vector store is an accelerator, never an authority. |
| Reverb is optional | The database is authoritative; broadcasts are notifications. Turn realtime off and polling is still correct. |
| Redis and Horizon are optional | The core runtime needs a plain Laravel queue and nothing else. |
| Nothing is forced | Headless mode installs no routes, no views, no Livewire. |
Requirements
| PHP | 8.3 · 8.4 |
| Laravel | 13.x |
| Database | SQLite · MySQL 8.4 · MariaDB 11 · PostgreSQL 17 — all in CI |
| Queue | any Laravel queue backend |
| Optional | Livewire 4 (control center) · Reverb (streaming) · Redis/Horizon · pgvector |
Installation
composer require michal78/laravel-pandora php artisan pandora:install php artisan queue:work
That is the headless install — agents, tools and runs from your own code, with no routes and no
frontend. The control center at /pandora is Livewire, which Pandora suggests rather than
requires, so add it if you want the UI:
composer require livewire/livewire
Without it, no /pandora route is registered at all — the page 404s rather than erroring, and
php artisan pandora:status tells you which of the two you have.
Want the unstable branch instead? development is where everything lands before it is released, and
Composer will take it without changing your minimum-stability — requiring a dev- version sets the
stability flag for that package on its own:
composer require michal78/laravel-pandora:dev-development
The installer is idempotent, publishes config and migrations, explains Reverb / queue / scheduler setup, and deliberately creates no default agent.
Quick start
From a fresh install to an agent that calls a tool, with no API key — the default provider is
fake, which answers without a network. Every block below is run by the test suite
(tests/Feature/QuickStartTest.php) against tests/Fixtures/ExampleApp, so this section cannot
drift from what works.
A tool — where an agent touches your application:
<?php // app/Tools/LookupOrder.php namespace App\Tools; use Pandora\Tools\Enums\RiskLevel; use Pandora\Tools\Tool; use Pandora\Tools\ToolContext; use Pandora\Tools\ToolInput; use Pandora\Tools\ToolResult; final class LookupOrder extends Tool { public function name(): string { return 'lookup_order'; } public function description(): string { return 'Look up an order by its reference.'; } public function rules(): array { return ['reference' => 'required|string|max:32']; } public function risk(): RiskLevel { return RiskLevel::Low; } // The console run below acts as the system rather than as a user, which // the default authorize() refuses. This tool reads nothing private. public function authorize(ToolInput $input, ToolContext $context): bool { return $context->actor !== null; } public function handle(ToolInput $input, ToolContext $context): ToolResult { $orders = ['1234' => 'shipped on Tuesday']; $reference = (string) $input->string('reference'); return isset($orders[$reference]) ? ToolResult::success("Order {$reference} {$orders[$reference]}.") : ToolResult::failure('No order has that reference.'); } }
An agent that may use it:
<?php // app/Agents/SupportAgent.php namespace App\Agents; use Pandora\Agents\AgentBlueprint; use Pandora\Contracts\AgentDefinition; final class SupportAgent implements AgentDefinition { public function define(AgentBlueprint $agent): AgentBlueprint { return $agent ->name('Support') ->instructions('Help customers resolve support issues.') ->tools(['lookup_order']); } }
Register both:
// config/pandora.php -- in the file the installer published 'agents' => [ 'definitions' => [App\Agents\SupportAgent::class], ], 'tools' => [ 'registered' => [App\Tools\LookupOrder::class], ],
Run it:
php artisan pandora:agent:list
php artisan pandora:agent:run support "Where is order 1234?" --trace
The fake provider echoes your message back rather than calling the tool — it is not a model. Point
Pandora at a real one to see the agent decide to call lookup_order:
PANDORA_PROVIDER=openai PANDORA_MODEL=gpt-4o-mini PANDORA_OPENAI_API_KEY=sk-...
The quick-start guide goes further: queued runs from a controller, streaming, inspecting the trace, and testing your integration against the fake provider.
Control center
A Livewire control center ships with the package and looks finished the moment it is installed — its own logo, palette and design tokens, light and dark themes resolved before the first paint, and every colour meeting WCAG AA on the surface it sits on. Retheme it by overriding one layer of custom properties; replace the mark by editing one Blade component.
See docs/visual-identity.md and
docs/brand-guide.md.
// config/pandora.php 'ui' => ['brand' => 'Acme Agents', 'theme' => 'dark'],
Documentation
Guides — installation · quick start · agents · tools · providers · automations · memory · workspaces · MCP · channels · writing extensions
Architecture — overview · security model · execution model · provider model · database model · realtime model
Product — vision · feature parity · terminology
Decisions — ADRs — each with the alternatives and why they lost
Delivery — roadmap · changelog · acceptance plans · progress log · open questions
Security
Pandora executes model-directed actions inside your application. Read
docs/architecture/security-model.md before deploying it.
We do not claim to solve prompt injection. We build layered controls that bound what an injected
instruction can reach: least authority, approval gates, tool allowlists, argument validation, egress
control, budgets and audit. See SECURITY.md for the full statement of limitations —
including where policy restriction is not sandboxing.
Contributing
See CONTRIBUTING.md. Read the ADRs first — most design questions are already
answered there, including the ones where we chose the harder option on purpose.
Acknowledgements
OpenClaw and Hermes Agent shaped what users now expect from a self-hosted agent platform, and studying them publicly informed our parity matrix. Pandora shares no code, assets, wording or implementation details with either project — every capability here is an independent Laravel-native reimplementation.
License
MIT. See LICENSE.md.