Skip to content

AI-agent oriented work order control plane for Laravel. Define typed work orders; lease, validate & approve items; enforce idempotency; track full audit trail. Ships a REST API + MCP server, strict state machine, partial submissions, and production-ready docs.

License

gregpriday/laravel-work-manager

Repository files navigation

Laravel Work Manager

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.


Why this exists

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.


What you get

  • 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-ID header). (Http\Middleware\EnforceWorkOrderOnly)
  • Auditability: WorkEvent, WorkProvenance, and structured Diff.
  • Documentation: MCP Server Integration, HTTP API, Creating Order Types, Partial Submissions, Docs Home

Installation

composer require gregpriday/laravel-work-manager
php artisan vendor:publish --tag=work-manager-config
php artisan vendor:publish --tag=work-manager-migrations
php artisan migrate

Requirements: 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.


Quick start

1. Install & migrate:

composer require gregpriday/laravel-work-manager
php artisan vendor:publish --tag=work-manager-migrations
php artisan migrate

2. 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.


Detailed examples (5 steps)

1) Register routes (choose your namespace)

// 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.

2) Define an order type

// 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()

3) Register your type

// app/Providers/AppServiceProvider.php
use GregPriday\WorkManager\Facades\WorkManager;
use App\WorkTypes\UserDataSyncType;

public function boot()
{
    WorkManager::registry()->register(new UserDataSyncType());
}

4) Schedule jobs

// app/Console/Kernel.php
$schedule->command('work-manager:generate')->everyFifteenMinutes();
$schedule->command('work-manager:maintain')->everyMinute();

5) Call the API (as an agent)

# 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"

Core concepts

Work Order & Work Item

  • 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.

Types & Lifecycle Hooks

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): void

AbstractOrderType 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.

State machine

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.

Leasing

Single checkout per item, TTL + heartbeat. Expired leases are reclaimed by maintenance; items either re-queue or fail on max attempts.

Idempotency

Provide X-Idempotency-Key for propose/submit/approve/reject. The package stores key hashes and cached responses to make agent retries safe.

Verification (Two-Phase)

  1. Agent Submission: Laravel validation rules + custom business logic
  2. Approval Readiness: Cross-item validation before execution (implemented by canApprove() in your acceptance policy)

"Work-order-only" enforcement

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.

Partial Submissions

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.


HTTP API overview

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 (requires type, payload)
  • GET /orders / GET /orders/{id} — list/show orders
  • POST /orders/{order}/checkout — lease next available item
  • POST /items/{item}/heartbeat — extend lease
  • POST /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 parts
  • POST /orders/{order}/approve — approve & apply (writes diffs/events)
  • POST /orders/{order}/reject — reject (optionally re-queue for rework)
  • POST /items/{item}/release — explicitly release lease
  • GET /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).


Scheduled automation

  • 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.


MCP Server (Recommended for AI Agents)

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.

Quick Start

Choose one transport (stdio for local IDEs, HTTP for remote/production):

Local mode (for Cursor, Claude Desktop, etc.):

php artisan work-manager:mcp --transport=stdio

HTTP mode (for remote agents/production):

php artisan work-manager:mcp --transport=http --host=0.0.0.0 --port=8090

The 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/testing

When 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.

Available MCP Tools

The server exposes 13 tools that map 1:1 to Work Manager operations:

  • work.propose — Create new work orders
  • work.list — List orders with filtering
  • work.get — Get order details
  • work.checkout — Lease work items
  • work.heartbeat — Maintain leases
  • work.submit — Submit complete results
  • work.submit_part — Submit partial results (for incremental work)
  • work.list_parts — List all parts for a work item
  • work.finalize — Finalize work item by assembling parts
  • work.approve — Approve and apply orders
  • work.reject — Reject orders
  • work.release — Release leases
  • work.logs — View event history

Integration Examples

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.


Configuration

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

Laravel Integration

Laravel Validation

protected function submissionValidationRules(WorkItem $item): array
{
    return [
        'user_id' => 'required|exists:users,id',
        'email' => 'required|email|unique:users',
        'data' => 'required|array',
    ];
}

Laravel Events

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, WorkOrderRejected
  • WorkItemLeased, WorkItemHeartbeat, WorkItemSubmitted, WorkItemFailed, WorkItemLeaseExpired, WorkItemFinalized
  • WorkItemPartSubmitted, WorkItemPartValidated, WorkItemPartRejected

Laravel Jobs/Queues

protected function afterApply(WorkOrder $order, Diff $diff): void
{
    ProcessData::dispatch($order)->onQueue('work');
    SendNotifications::dispatch($diff)->onQueue('notifications');
}

Database Operations

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);
    });
}

Examples


Security & compliance

  • 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

Testing

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 --coverage

Note: 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.


Roadmap

  • ✅ 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

Documentation

Laravel Work Manager includes comprehensive, production-ready documentation covering all aspects of the package.

📚 Complete Documentation

→ 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

🚀 Quick Start Path

New to Laravel Work Manager? Follow this learning path:

  1. Introduction - Understand what it does and why you need it
  2. Installation - Install and configure the package
  3. Quickstart Guide - Build your first order type in 5 minutes
  4. Basic Usage Example - See a complete working example

📖 Popular Topics

For questions, issues, or feature requests, visit the GitHub issue tracker.


Contributing

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 endpoints
  • src/Services/{WorkAllocator,WorkExecutor,LeaseService,IdempotencyService,StateMachine} — core services
  • src/Support/{AbstractOrderType,AbstractAcceptancePolicy,Enums,Diff,Helpers} — primitives & base classes
  • src/Models/{WorkOrder,WorkItem,WorkEvent,WorkProvenance,WorkIdempotencyKey} — Eloquent models
  • src/Console/{GenerateCommand,MaintainCommand} — scheduler commands

License

MIT © Greg Priday. See LICENSE.md.


Support

Need help?

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.

About

AI-agent oriented work order control plane for Laravel. Define typed work orders; lease, validate & approve items; enforce idempotency; track full audit trail. Ships a REST API + MCP server, strict state machine, partial submissions, and production-ready docs.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages