Running Agent Workflows in PHP: Real-World Tests and Pitfalls of Six LangGraph Alternatives

If you want to run stateful, cyclical, resumable agent workflows in PHP like LangGraph, you have options. PHP has no official LangGraph port, but there are several implementations with different focuses. Pick the wrong one and it won’t fail to run, but you’ll pay for it later in state persistence, queue configuration, or PHP version compatibility. Below we break down each option, show how to run it, what breaks in practice, and how to work around it.

Can You Run Python LangGraph Directly in PHP?

No. Python LangGraph still installs with the usual pip command.

pip install -U langgraph

There is no official PHP package. What exists today is either a reimplementation of graph orchestration in PHP or an adapter that ports LangGraph’s core concepts. So you are not looking for a “PHP version of LangGraph.” You are looking for a way to build stateful, cyclical, resumable agent workflows in PHP.

Six Alternatives: How to Run Them and Where They Break

Neuron AI: Framework-Agnostic, but Environment Requirements Are Real

Neuron AI is the most complete LangGraph alternative in the PHP ecosystem. It runs on Laravel, Symfony, or plain PHP, sits alongside your existing application, and accesses local ORM and Redis directly. It covers multi-agent collaboration, human-in-the-loop, state persistence, RAG, and toolkits.

Its Laravel SDK takes a non-intrusive approach. It doesn’t force you to reorganize your code. Instead, it provides config, Artisan commands, and Facades as glue, while you still work with Neuron’s native classes. A good starting path is to handle ticket classification with a single agent, then gradually introduce RAG and Workflow, all within your existing Laravel structure.

After installation, the first thing to check is your PHP version. Neuron AI requires PHP >= 8.2 and Laravel >= 10.x.

php -v | grep "PHP 8.2" || echo "Need to upgrade PHP"

If your server runs under Swoole, there is one pitfall to handle first. Neuron uses Guzzle for HTTP requests by default, but Guzzle may not work correctly under Swoole’s coroutine environment. You need to switch to an asynchronous HTTP client. Neuron provides an HttpClientInterface with a default Guzzle implementation. For async scenarios, you can inject an Amp implementation, which requires installing amphp/http-client. If you ignore this, your agent calls to the LLM will hang or throw connection exceptions.

Another common deployment issue is the bcmath extension. Some server environments don’t have it installed by default. Neuron uses it for token counting and numerical operations. Missing it will only surface at runtime.

LaraGraph: Wrong Queue Config Means Nodes Never Execute

LaraGraph is built for Laravel projects. It adapts cyclical graphs to PHP’s stateless architecture. Each node runs as an independent ExecuteNode queue job. Concurrency and horizontal scaling rely directly on Laravel’s database and queue system.

Installation takes three steps.

composer require cainy/laragraph
php artisan vendor:publish --tag="laragraph-migrations"
php artisan migrate
php artisan vendor:publish --tag="laragraph-config"

Workflows are defined in the definition() method of a Workflow class. You register nodes with addNode(), connect them with transition(), and use Workflow::START and Workflow::END as entry and exit points.

The most common pitfall is queue configuration. LaraGraph executes entirely through queues. If QUEUE_CONNECTION is not set correctly, node jobs won’t be picked up by a worker, and the workflow will sit in its initial state forever. First check your .env.

QUEUE_CONNECTION=database

Do not write QUEUE_DRIVER=database. Laravel 10+ has deprecated that older syntax. Then generate the queue tables and migrate.

php artisan queue:table
php artisan queue:failed-table
php artisan migrate

After migrating, start a worker with php artisan queue:work. You should see Processing: ... output, which means jobs are actually being consumed. If there is no output, check three things: the spelling in .env, whether the jobs table exists, and whether your MySQL user has write permissions.

There is also a testing environment pitfall. LaraGraph uses Orchestra Testbench for package testing, but if the build section of testbench.yaml contains Artisan commands with quotes or complex arguments, they may not be parsed correctly. If it works locally but fails in CI, check this first.

langgraph-php: Installs on PHP 7.4, but Don’t Expect the Same Smooth Ride as Python

langgraph-php is a direct PHP port of Python LangGraph concepts. It includes StateGraph, nodes, edges, and checkpointing. Installation is simple.

composer require langgraph/langgraph-php

Composer requires PHP >= 7.4, which is a lower barrier than the previous two options. Basic usage involves defining a state class, registering nodes with addNode(), connecting them with addEdge(), setting setEntryPoint() and setFinishPoint(), then compile() and execute().

The pitfall is in the word “direct.” langgraph-php maps the API closely, but if you write code with Python LangGraph thinking, you’ll hit two problems. First, PHP lacks Python’s decorators and type hinting system. Node function signatures and state passing work differently. Copying Python code will cause type errors. Second, conditional edge functions return strings, and the graph does not validate at compile time whether the returned string matches a registered edge name. A typo only surfaces at runtime. Run a complete flow after writing conditional edges. Don’t wait for production to discover a wrong route.

Also, langgraph-php’s checkpointing is less complete than the Python version. State persistence granularity is coarser. If your workflow needs to pause and resume at arbitrary nodes, this option currently does not match Python LangGraph’s precision.

Nexus AI Workflows: Highest PHP Version Requirement, but Clearest Persistence Options

Nexus AI Workflows is a graph workflow orchestration engine. It provides a WorkflowBuilder API and supports eight node types, including condition, loop, parallel, and sub-workflow. It also offers resumable execution, middleware, and observability.

Its PHP version requirement is 8.3 or higher, the highest among the six options. Install with Composer.

composer require token27/nexus-ai-workflows

Your first workflow starts by setting up a driver registry, registering an OpenAI driver, then defining nodes and transitions with WorkflowBuilder, and finally executing with WorkflowRunner.

Persistence is handled more clearly here. It provides three storage implementations: FileWorkflowStore saves snapshots as JSON files, CacheWorkflowStore saves to a PSR-16 compatible cache, and InMemoryWorkflowStore keeps everything in memory. Use InMemory for development speed, then switch to File or Cache for production.

If you use this in a Laravel environment, note that WorkflowContext is immutable. Each node execution returns a new context rather than modifying the original. This design helps with parallel nodes because each Fiber gets its own copy and won’t step on shared state. But if you are used to modifying arrays directly, you’ll need to adjust to returning new objects.

PocketFlow PHP: About 700 Lines, but Upgrades Bring Breaking Changes

PocketFlow PHP is the lightest of the six. It ports the core of Python PocketFlow to PHP. The entire framework is about 700 lines across 14 PSR-4 files.

There are only three core abstractions: Node (prep() → exec() → post()), Flow (a directed graph where string Actions decide transitions), and SharedStore (an object that runs through the entire flow). You don’t need to learn a new set of concepts. Understand these three and you can start writing.

Installation and initialization are fastest with the template.

git clone https://github.com/weise25/pocketflow-php-template.git my-app
cd my-app
composer install

Then describe what you want in an AI coding tool that supports the Agent Skills standard. The tool will generate nodes.php, flow.php, and main.php based on built-in skills.

When upgrading to version 0.2.0, watch for a breaking change. Version 0.1.x used stdClass as the state container. Version 0.2.0 replaced it with the SharedStore class. All run(), prep(), and post() signatures changed. If your project was built during the 0.1.x era, you’ll need to replace this type globally, or you’ll get type errors.

Also, PocketFlow itself does not provide database persistence. SharedStore is an in-memory object that disappears after the flow finishes. If you need persistence, you must write your own storage logic inside nodes.

Pagent: Pest Style, but the File Adapter Has a Context Window Pitfall

Pagent targets teams that like Pest-style declarative APIs. It includes automatic tool calling, multi-agent workflows, evaluation, and OpenTelemetry tracing. It requires PHP 8.3 or higher and Composer 2.x.

It provides a file memory adapter for persisting conversations. Configuration is chained on the Agent.

agent('windowed-bot')
    ->memory('file', ['path' => 'storage/sessions'])

There is an easily overlooked pitfall here. The file adapter appends every conversation turn to the file without automatic truncation. As turns accumulate, the file grows, and every load reads the entire history into the context window. Pagent provides a context window configuration to limit the read range, but you must enable it manually. Without this parameter, token consumption will spiral out of control after a few dozen turns, and LLM response quality will degrade because the context is stuffed with irrelevant history.

As with other agent memory systems in the PHP ecosystem, stuffing unlimited conversation history into the context window is the most common failure mode in production. The file adapter is simple to use, but remember to set the window limit.

Two Pitfalls Common to All Alternatives

State Serialization Fails with Closures

No matter which option you use, if your workflow state needs persistence, you will hit PHP’s serialization limits. Put a closure into state, and serialization throws Exception: Serialization of 'Closure' is not allowed.

PHP closures may capture external variables and bound context. The language explicitly forbids serializing Closure objects. This is especially easy to trigger in Laravel queues because the queue driver serializes the entire job payload. If your node properties contain Service injections, serializing the job will also fail.

The solution is to avoid closures inside nodes. Write the logic you need as class methods or invokable classes. Store only arrays, strings, and numbers in state. If an object must go into state, implement __sleep() to clear closure properties before serialization.

Neuron AI’s documentation also explicitly states that workflow state does not support Closure serialization. If an object conflicts with PHP’s standard serialization flow, you must convert it before storing it in state.

Wrong Durable Storage Means State Loss After Deployment

PHP’s request lifecycle is short. Laravel requests “boot, serve, destroy.” Sessions cannot stay in memory like Node.js Mastra. Every request must rebuild the session from a store.

The prism-harness package offers a useful pattern. It splits state into two slots: ephemeral slots for current mode, selected model, and run bookkeeping, which fall back to defaults if lost; durable slots for threads and pending tool calls, which are truly lost if dropped. It defaults both slots to the database, but recommends pointing ephemeral to Redis in production and keeping durable in the database.

It also has a protection mechanism. If a store reports itself as volatile, meaning it might be flushed, and you point it to a durable slot, the package throws UnsafeStateConfiguration at resolution time and gives you two ways out. Redis in Laravel is usually treated as a cache, and caches are allowed to be cleared. One project put XP deduplication data in the cache. A single cache:clear silently reissued all rewards, with no trace in the logs. The same mistake applied to tool approvals would drop a half-executed operation waiting for human confirmation.

The conclusion is clear. Do not put durable state in Redis. Use a database, or at least confirm that the storage will not be wiped by commands like cache:clear.

Pre-Launch Checklist

  • •
    Confirm PHP version. Neuron AI needs 8.2+, Nexus AI Workflows needs 8.3+, Pagent needs 8.3+, langgraph-php only needs 7.4+.
  • •
    For LaraGraph on Laravel, set QUEUE_CONNECTION=database, run migrations, start queue:work, and confirm jobs are being consumed.
  • •
    Under Swoole with Neuron AI, replace Guzzle with an async HTTP client or requests will hang.
  • •
    Do not put closures in workflow state. Write logic as classes and store only serializable data.
  • •
    Use a database for durable state, not Redis. Redis can be cleared by cache:clear at any time.
  • •
    For Pagent file memory, set a context window limit or token usage will spiral after many turns.
  • •
    For langgraph-php conditional edges, the return value is not validated at compile time. Run a full flow to confirm routing.
  • •
    When upgrading PocketFlow PHP from 0.1.x to 0.2.0, replace all stdClass with SharedStore.

FAQ

Why are LaraGraph nodes never executing?
Check that .env has QUEUE_CONNECTION=database, that queue tables are migrated, and that queue:work is running.

What do I do if Neuron AI requests hang under Swoole?
The default Guzzle HTTP client does not work in Swoole’s coroutine environment. Switch to an Amp implementation and install amphp/http-client.

What error appears when I put a closure in workflow state?
PHP throws Serialization of 'Closure' is not allowed. Replace closures with class methods or invokable classes.

Why can’t durable state go in Redis?
Redis in Laravel is usually used as a cache. A single cache:clear wipes the state. prism-harness rejects this configuration outright.

What are the limits of Pagent file memory?
It does not automatically truncate conversation history. You must configure a context window manually, or token usage will spiral.

What happens if I write a wrong conditional edge in langgraph-php?
It does not fail at compile time. It only surfaces at runtime. Run a full flow to verify.

What should I watch for when upgrading PocketFlow PHP to 0.2.0?
stdClass was replaced with SharedStore. All node method signatures changed. Replace the type globally.