Work-order control plane for Laravel—with first-class AI-agent integration (MCP).
For AI agents, background services, and admin/dashboard flows that need strong guarantees around state, idempotency, auditability, and safe concurrent execution.
This package gives you a framework-native way to create, lease, validate, approve, and apply typed work orders. It ships with a built-in MCP (Model Context Protocol) server for AI agents, a thin HTTP API you can mount under any namespace, scheduled commands for auto-generating work, and clean extension points for custom types, validation, and execution.
Built-in MCP server: Connect AI agents (Claude/Claude Code, Cursor, etc.) via the MCP protocol to automatically discover tools, check out work, submit results, and poll decisions. The MCP server exposes the same services and validation as the HTTP API. The HTTP API remains available for non-MCP clients and custom integrations.
Modern AI systems do non-trivial backend work—research, enrichment, migrations, data syncs—often performed by external agents. You need:
- A single, auditable path for all mutations (no side doors).
- Safe, concurrent leasing with TTL + heartbeat.
- Typed work with per-type schemas, validators, and idempotent apply() logic.
- Idempotency and clear retry semantics.
- Easy agent UX via checkout → heartbeat → submit → approve/apply.
- Strong events/provenance/diffs for observability and compliance.
Laravel Work Manager provides exactly that.
- Typed Work Orders: per-type schema + planning + acceptance policy + apply hooks. (
Contracts\OrderType,Support\AbstractOrderType) - State machine: enforced order/item lifecycle + events. (
Services\StateMachine,Support\Enums) - Leasing & Concurrency: single checkout, TTL, heartbeat, reclaim, max attempts. (
Services\LeaseService) - Idempotency: header-based dedupe + stored responses. (
Services\IdempotencyService) - HTTP API: mountable controller with propose/checkout/heartbeat/submit/approve/reject/logs. (
Http\Controllers\WorkOrderApiController) - Scheduled commands: generator & maintenance. (
work-manager:generate,work-manager:maintain) - "Work-order-only" enforcement: opt-in middleware to block direct mutations (requires
X-Work-Order-IDheader). (Http\Middleware\EnforceWorkOrderOnly) - Auditability:
WorkEvent,WorkProvenance, and structuredDiff. - Documentation: MCP Server Integration, HTTP API, Creating Order Types, Partial Submissions, Docs Home
composer require gregpriday/laravel-work-manager
php artisan vendor:publish --tag=work-manager-config
php artisan vendor:publish --tag=work-manager-migrations
php artisan migrateRequirements: PHP 8.2+, Laravel 11 or 12, MySQL 8+ or Postgres 13+. Optional Redis lease backend (default: database; Redis recommended for higher concurrency). See Installation Guide for detailed requirements.
1. Install & migrate:
composer require gregpriday/laravel-work-manager
php artisan vendor:publish --tag=work-manager-migrations
php artisan migrate2. Register routes:
// routes/api.php
use GregPriday\WorkManager\Facades\WorkManager;
WorkManager::routes('agent/work', ['api', 'auth:sanctum']);3. Schedule maintenance:
// app/Console/Kernel.php
$schedule->command('work-manager:generate')->everyFifteenMinutes();
$schedule->command('work-manager:maintain')->everyMinute();4. Define an order type (see Creating Order Types):
// app/WorkTypes/UserDataSyncType.php
use GregPriday\WorkManager\Support\AbstractOrderType;
class UserDataSyncType extends AbstractOrderType
{
public function type(): string { return 'user.data.sync'; }
public function schema(): array { /* JSON schema */ }
public function apply(WorkOrder $order): Diff { /* Idempotent execution */ }
}5. Register your type:
// app/Providers/AppServiceProvider.php
WorkManager::registry()->register(new UserDataSyncType());See Quickstart Guide for a complete walkthrough.
// routes/api.php
use GregPriday\WorkManager\Facades\WorkManager;
// Mount all endpoints under /agent/work with your own middleware/guard
WorkManager::routes('agent/work', ['api', 'auth:sanctum']);Note: If you register in routes/api.php, Laravel automatically prefixes with /api, so this becomes /api/agent/work/*.
Or wire them manually to pick endpoints individually.
// app/WorkTypes/UserDataSyncType.php
use GregPriday\WorkManager\Support\AbstractOrderType;
use GregPriday\WorkManager\Models\WorkOrder;
use GregPriday\WorkManager\Models\WorkItem;
use GregPriday\WorkManager\Support\Diff;
final class UserDataSyncType extends AbstractOrderType
{
public function type(): string
{
return 'user.data.sync';
}
public function schema(): array
{
return [
'type' => 'object',
'required' => ['source', 'user_ids'],
'properties' => [
'source' => ['type' => 'string', 'enum' => ['crm', 'analytics']],
'user_ids' => ['type' => 'array', 'items' => ['type' => 'integer']],
],
];
}
// Laravel validation for agent submissions
protected function submissionValidationRules(WorkItem $item): array
{
return [
'success' => 'required|boolean',
'synced_users' => 'required|array',
'synced_users.*.user_id' => 'required|integer',
'synced_users.*.verified' => 'required|boolean|accepted',
];
}
// Custom verification logic
protected function afterValidateSubmission(WorkItem $item, array $result): void
{
// Verify all users in batch were processed
$expectedIds = $item->input['user_ids'];
$syncedIds = array_column($result['synced_users'], 'user_id');
if (count(array_diff($expectedIds, $syncedIds)) > 0) {
throw \Illuminate\Validation\ValidationException::withMessages([
'synced_users' => ['Not all users in batch were synced'],
]);
}
}
// Idempotent execution with database operations
public function apply(WorkOrder $order): Diff
{
$updatedCount = 0;
DB::transaction(function () use ($order, &$updatedCount) {
foreach ($order->items as $item) {
foreach ($item->result['synced_users'] as $syncedUser) {
$user = User::find($syncedUser['user_id']);
if ($user) {
$user->update($syncedUser['data']);
$updatedCount++;
}
}
}
});
return $this->makeDiff(
['updated_count' => 0],
['updated_count' => $updatedCount],
"Synced data for {$updatedCount} users"
);
}
// Post-execution cleanup
protected function afterApply(WorkOrder $order, Diff $diff): void
{
Cache::tags(['users'])->flush();
}
}This uses AbstractOrderType which provides:
- Default acceptance policy using Laravel validation
- Lifecycle hooks:
beforeApply(),afterApply() - Verification hooks:
submissionValidationRules(),afterValidateSubmission(),canApprove() - Helper methods:
makeDiff(),emptyDiff()
// app/Providers/AppServiceProvider.php
use GregPriday\WorkManager\Facades\WorkManager;
use App\WorkTypes\UserDataSyncType;
public function boot()
{
WorkManager::registry()->register(new UserDataSyncType());
}// app/Console/Kernel.php
$schedule->command('work-manager:generate')->everyFifteenMinutes();
$schedule->command('work-manager:maintain')->everyMinute();# Propose
curl -X POST /api/agent/work/propose \
-H "Authorization: Bearer <token>" \
-H "X-Idempotency-Key: propose-1" \
-d '{"type":"user.data.sync","payload":{"source":"crm","user_ids":[1,2,3]}}'
# Checkout → heartbeat → submit → approve
curl -X POST /api/agent/work/orders/{order}/checkout -H "X-Agent-ID: agent-1"
curl -X POST /api/agent/work/items/{item}/heartbeat -H "X-Agent-ID: agent-1"
curl -X POST /api/agent/work/items/{item}/submit \
-H "X-Idempotency-Key: submit-1" \
-d '{"result":{"success":true,"synced_users":[...],"verified":true}}'
curl -X POST /api/agent/work/orders/{order}/approve -H "X-Idempotency-Key: approve-1"WorkOrder: the high-level contract (type, payload, state, provenance).WorkItem: the unit an agent leases, heartbeats, and submits.
Eloquent models live under src/Models with enum casts for state.
OrderType defines the complete lifecycle of a work type:
// What is this work?
public function type(): string
// What data is required?
public function schema(): array
// How to break into items?
public function plan(WorkOrder $order): array
// Verification hooks (using AbstractOrderType):
protected function submissionValidationRules(WorkItem $item): array
protected function afterValidateSubmission(WorkItem $item, array $result): void
protected function canApprove(WorkOrder $order): bool
// Execution hooks:
protected function beforeApply(WorkOrder $order): void
public function apply(WorkOrder $order): Diff // Idempotent!
protected function afterApply(WorkOrder $order, Diff $diff): voidAbstractOrderType provides default implementations and hooks for:
- Laravel validation integration
- Custom verification logic
- Approval readiness checks
- Before/after execution hooks
AbstractAcceptancePolicy for teams that prefer validation separate from the type class.
See Lifecycle & Flow for complete hook documentation.
Strict transitions are enforced for orders and items:
queued → checked_out → in_progress → submitted → approved → applied → completed
Failed/rejected paths also supported. Events are written for every transition.
Single checkout per item, TTL + heartbeat. Expired leases are reclaimed by maintenance; items either re-queue or fail on max attempts.
Provide X-Idempotency-Key for propose/submit/approve/reject. The package stores key hashes and cached responses to make agent retries safe.
- Agent Submission: Laravel validation rules + custom business logic
- Approval Readiness: Cross-item validation before execution (implemented by
canApprove()in your acceptance policy)
Attach EnforceWorkOrderOnly middleware to any mutating endpoint in your app to ensure all writes flow through a valid work order. This is opt-in and requires the X-Work-Order-ID header (or _work_order_id request parameter). You can optionally specify allowed states, e.g., approved|applied.
Optional partial submissions let agents stream large results and finalize later. For complex work items (e.g., research tasks, multi-step processes), agents can submit results incrementally rather than all at once:
POST /items/{item}/submit-part— Submit an incremental part (validated independently)POST /items/{item}/finalize— Assemble all validated parts into final result
Enable in config: 'partials.enabled' => true (enabled by default, with configurable limits on max parts and payload size).
Benefits:
- Handle large/complex work without timeout issues
- Validate results incrementally as they're produced
- Resume work across sessions
- Track progress for long-running tasks
Each part is independently validated and stored. Once all parts are submitted, call finalize to assemble them into the final work item result. See examples/CustomerResearchPartialType.php for implementation details.
Mount under any prefix (e.g., /agent/work), then:
Note on route prefixes: If you mount routes in routes/api.php, Laravel automatically prefixes them with /api, so /agent/work/* becomes /api/agent/work/*.
Idempotency: Send X-Idempotency-Key header on enforced endpoints (propose, submit, submit-part, finalize, approve, reject) to get safe retries with cached responses.
POST /propose— create a work order (requirestype,payload)GET /orders/GET /orders/{id}— list/show ordersPOST /orders/{order}/checkout— lease next available itemPOST /items/{item}/heartbeat— extend leasePOST /items/{item}/submit— submit complete results (validated)POST /items/{item}/submit-part— submit partial result (for incremental work)POST /items/{item}/finalize— finalize work item by assembling all partsPOST /orders/{order}/approve— approve & apply (writes diffs/events)POST /orders/{order}/reject— reject (optionally re-queue for rework)POST /items/{item}/release— explicitly release leaseGET /items/{item}/logs— recent events/diffs
All implemented in WorkOrderApiController.
Auth/guard: configure in config/work-manager.php (routes.guard, default sanctum).
Idempotency header: X-Idempotency-Key (configurable).
work-manager:generate— runs your registered AllocatorStrategy/PlannerPort implementations to create new orders (e.g., "scan for stale data → create sync orders")work-manager:maintain— reclaims expired leases, dead-letters stuck work, and alerts on stale orders
Wire them in your scheduler; see Console/* for options.
The package includes a built-in MCP (Model Context Protocol) server for AI agents to interact with the work order system. This is the recommended integration method for AI IDEs and agents.
Choose one transport (stdio for local IDEs, HTTP for remote/production):
Local mode (for Cursor, Claude Desktop, etc.):
php artisan work-manager:mcp --transport=stdioHTTP mode (for remote agents/production):
php artisan work-manager:mcp --transport=http --host=0.0.0.0 --port=8090The MCP server runs one transport at a time and exposes the same services as the HTTP API.
HTTP mode with authentication (recommended for production):
# .env
WORK_MANAGER_MCP_HTTP_AUTH=true
WORK_MANAGER_MCP_AUTH_GUARD=sanctum # or any Laravel guard
WORK_MANAGER_MCP_STATIC_TOKENS=token1,token2 # optional: static tokens for dev/testingWhen auth is enabled, clients must include Authorization: Bearer <token> header on all requests.
MCP HTTP endpoints: GET /mcp/sse (server-sent events), POST /mcp/message (message endpoint). Enable Bearer auth in production and put it behind TLS/reverse proxy; use static tokens only for dev.
Note: MCP and REST auth are separate. MCP HTTP uses Bearer tokens configured above; REST API uses your Laravel guard (e.g., Sanctum) configured in routes.
The server exposes 13 tools that map 1:1 to Work Manager operations:
work.propose— Create new work orderswork.list— List orders with filteringwork.get— Get order detailswork.checkout— Lease work itemswork.heartbeat— Maintain leaseswork.submit— Submit complete resultswork.submit_part— Submit partial results (for incremental work)work.list_parts— List all parts for a work itemwork.finalize— Finalize work item by assembling partswork.approve— Approve and apply orderswork.reject— Reject orderswork.release— Release leaseswork.logs— View event history
Cursor IDE - Add to .cursorrules:
{
"mcp": {
"servers": {
"work-manager": {
"command": "php",
"args": ["artisan", "work-manager:mcp", "--transport=stdio"],
"cwd": "/path/to/your/laravel/app"
}
}
}
}Claude Desktop - Add to config:
{
"mcpServers": {
"work-manager": {
"command": "php",
"args": ["/path/to/app/artisan", "work-manager:mcp"],
"env": { "APP_ENV": "local" }
}
}
}See MCP Server Integration Guide for complete documentation including production deployment, authentication setup, security, and troubleshooting.
Publish and edit config/work-manager.php. Key sections:
- Routes: base path, middleware, guard
- Lease: TTL, heartbeat intervals, backend (database or redis)
- Retry: max attempts, backoff, jitter
- Idempotency: header name & enforced endpoints
- Partials: enable/disable partial submissions, max parts per item, payload size limits
- State Machine: allowed transitions
- Queues: queue connections and names
- Metrics: driver (log, prometheus, statsd) and namespace
- Policies: map abilities to gates/permissions
- Maintenance: thresholds for dead-lettering and alerts
- MCP: HTTP authentication (enabled, guard, static tokens), CORS settings
protected function submissionValidationRules(WorkItem $item): array
{
return [
'user_id' => 'required|exists:users,id',
'email' => 'required|email|unique:users',
'data' => 'required|array',
];
}Subscribe to lifecycle events:
use GregPriday\WorkManager\Events\WorkOrderApplied;
Event::listen(WorkOrderApplied::class, function($event) {
Log::info('Order applied', [
'order_id' => $event->order->id,
'diff' => $event->diff->toArray(),
]);
});Available events:
WorkOrderProposed,WorkOrderPlanned,WorkOrderCheckedOut,WorkOrderApproved,WorkOrderApplied,WorkOrderCompleted,WorkOrderRejectedWorkItemLeased,WorkItemHeartbeat,WorkItemSubmitted,WorkItemFailed,WorkItemLeaseExpired,WorkItemFinalizedWorkItemPartSubmitted,WorkItemPartValidated,WorkItemPartRejected
protected function afterApply(WorkOrder $order, Diff $diff): void
{
ProcessData::dispatch($order)->onQueue('work');
SendNotifications::dispatch($diff)->onQueue('notifications');
}public function apply(WorkOrder $order): Diff
{
return DB::transaction(function () use ($order) {
foreach ($order->items as $item) {
// Insert/update records
Model::create($item->result['data']);
}
return $this->makeDiff($before, $after);
});
}- Database inserts: Database Record Insert Example — batch inserts + verification + idempotent apply
- User data sync: User Data Sync Example — external API sync with per-batch items
- Quick start: Quickstart Guide — 5-minute getting started guide
- Lifecycle walk-through: Lifecycle & Flow — every hook and event documented
- Architecture: Architecture Overview — system design and data flows
- Require auth on all mounted routes (default
auth:sanctum) - Use idempotency keys for all mutating calls from agents
- Attach EnforceWorkOrderOnly to any legacy mutation routes to prevent side-door writes
- Record provenance: agent name/version, request fingerprints
- Emit/ship events/diffs to your SIEM/observability stack
Pest/PHPUnit setup is included. Add feature tests for your custom types covering:
- Proposal → checkout → heartbeat → submit → approve/apply
- Rejection and resubmission paths
- Lease expiration and retry logic
- Idempotency behavior
composer test
# Run with coverage
vendor/bin/pest --coverageNote: Some edge case tests are currently skipped pending investigation (e.g., lease conflict detection, order readiness checks). These are marked with markTestSkipped and do not affect core functionality.
- ✅ Core models, leases, state machine, idempotency, controller & commands
- ✅ Abstract base classes (
AbstractOrderType,AbstractAcceptancePolicy) - ✅ Complete lifecycle hooks with Laravel validation integration
- ✅ Comprehensive examples and documentation
- ✅ MCP server with stdio and HTTP transports
- ✅ Partial submissions for incremental work item results
- ✅ Optional Redis lease backend (see config:
'lease.backend' => 'redis') - ✅ Database metrics tracking for monitoring work orders and items
- 🔜 OpenAPI docs generator for mounted routes
Laravel Work Manager includes comprehensive, production-ready documentation covering all aspects of the package.
→ View Complete Documentation | → Documentation Index
The documentation is organized into the following sections:
- Getting Started - Introduction, requirements, installation, and quickstart guide
- Concepts - Core architecture, lifecycle, state management, and security
- Guides - Practical how-to guides for building and deploying
- Examples - Real-world implementations with complete working code
- Reference - Complete API, configuration, routes, events, and schema reference
- Troubleshooting - Common errors, FAQ, and known limitations
- Contributing - How to contribute, security policy, and community support
New to Laravel Work Manager? Follow this learning path:
- Introduction - Understand what it does and why you need it
- Installation - Install and configure the package
- Quickstart Guide - Build your first order type in 5 minutes
- Basic Usage Example - See a complete working example
- Creating Order Types - Complete guide to building custom order types
- MCP Server Integration - Connect AI agents via Model Context Protocol
- HTTP API Reference - Complete REST API documentation
- Partial Submissions - Incremental work submission for complex tasks
- Events & Listeners - React to lifecycle events
- Deployment Guide - Production deployment and scaling
For questions, issues, or feature requests, visit the GitHub issue tracker.
We welcome contributions! Please see CONTRIBUTING.md for:
- How to report bugs
- How to suggest features
- Development setup
- Running tests
- Coding standards
- Pull request process
Key components (see Architecture Overview for system design):
src/Http/Controllers/WorkOrderApiController.php— API endpointssrc/Services/{WorkAllocator,WorkExecutor,LeaseService,IdempotencyService,StateMachine}— core servicessrc/Support/{AbstractOrderType,AbstractAcceptancePolicy,Enums,Diff,Helpers}— primitives & base classessrc/Models/{WorkOrder,WorkItem,WorkEvent,WorkProvenance,WorkIdempotencyKey}— Eloquent modelssrc/Console/{GenerateCommand,MaintainCommand}— scheduler commands
MIT © Greg Priday. See LICENSE.md.
Need help?
- Documentation: Start with the FAQ and Common Errors
- Issues: Report bugs or request features on GitHub
- Security: Report vulnerabilities via email (see Security Policy)
- Commercial Support: Contact greg@siteorigin.com for consulting and priority support
See Support and Community for more resources.
This README reflects the current package structure implementing a complete work order control plane with lifecycle hooks, Laravel integration, and comprehensive documentation.