Download the PHP package cainydev/laragraph without Composer

On this page you can find all versions of the php package cainydev/laragraph. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laragraph

LaraGraph

[![Latest Version on Packagist](https://img.shields.io/packagist/v/cainydev/laragraph.svg?style=flat-square)](https://packagist.org/packages/cainydev/laragraph) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/cainydev/laragraph/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/cainydev/laragraph/actions?query=workflow%3Arun-tests+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/cainydev/laragraph.svg?style=flat-square)](https://packagist.org/packages/cainydev/laragraph)

Stateful, graph-based workflow engine for Laravel.
Build multi-step agent pipelines, human-in-the-loop processes, and parallel fan-out/fan-in tasks — all backed by your database and queue.

Inspired by LangGraph

Table of Contents


Installation

Publish and run the migration:

Publish the config file:


Core Concepts

LaraGraph models a workflow as a directed graph of nodes connected by edges. Each run of that graph is a WorkflowRun — a database record that tracks the current state, status, and active node pointers.

Term Meaning
Node A unit of work. Receives the current state, returns a mutation.
Edge A directed connection between two nodes, optionally conditional.
State A plain PHP array that accumulates mutations as nodes execute.
Pointer Tracks which nodes are currently in-flight for a run.
WorkflowRun The persisted record for a single execution of a workflow.

Execution is fully queue-driven. Each node runs as an independent ExecuteNode job, so parallel branches execute concurrently across your worker pool.


Building a Workflow

Workflows are classes that extend Workflow and define their graph in a definition() method:

You can also call compile() directly on a Workflow instance if you prefer building inline, but the class-based approach is recommended since workflows are stored by class name.

Nodes

A node is any class implementing Cainy\Laragraph\Contracts\Node:

handle() receives a typed NodeExecutionContext and the current full state. It returns an array of mutations — only the keys you want to change.

NodeExecutionContext

User state vs engine state: $state only contains keys your nodes write. Engine bookkeeping (spawn counters, interrupt markers, gate reasons, child-run ids, error summaries) lives on a separate workflow_runs.routing column and is never merged into $state. Read it via $context->routing when you need it.

Transitions

Workflow::START and Workflow::END are reserved entry and exit pseudo-nodes.

Nodes can be registered as class strings (resolved via the container) or as pre-built instances.

Conditional Edges

Pass a Closure as the third argument to ->transition():

Branch Edges

A branch edge uses a resolver to return one or more target node names dynamically at runtime:

The targets array is optional but recommended — it enables graph visualization without executing the resolver.

Parallel Branches

To execute multiple nodes in parallel from a single node, add multiple transitions from the same source:

branch-a and branch-b run as independent queue jobs. Use a BarrierNode as the merge node to wait for all branches before continuing.

Dynamic Fan-out with Send

To fan out over a dynamic list, return Send objects from a branch edge resolver:

Each Send dispatches an independent ExecuteNode job. The target node receives the payload via $context->isolatedPayload or the helper methods:

The same fan-out is available via the SendNode prebuilt (see Built-in Nodes).

Fan-out Patterns

Three canonical shapes cover almost every real workflow:

1. One-shot fan-out → barrier. Single worker node per item, then a BarrierNode. Use for independent work with no per-item downstream steps.

2. Per-item pipeline → child workflow → barrier. When each item needs a multi-step pipeline (enrich → qualify → classify → draft), make the pipeline its own Workflow and dispatch it as a sub-graph via Send::toWorkflow(). The payload becomes the child's initial state (parent state is not leaked), and each Send spawns its own isolated child run.

Why a sub-workflow rather than chaining sibling nodes in the parent? Send payloads are per-job and don't propagate through subsequent static edges (enrich → qualify would see payload = null). Inside a child workflow the payload becomes state, so enrich writes to state and qualify sees it on the next hop — no payload-threading needed.

3. Parent supervises long-running children. Same shape as (2), but use cascadeFailure(false) on the child workflow when you want a map-reduce style aggregate where one child's failure shouldn't fail the whole run. See Sub-graph Workflows below.


Running a Workflow

Starting a Run

Pass an optional metadata array as the third argument to attach correlation data that travels with the run without being visible to nodes:

The run is created synchronously. Node jobs are dispatched to your queue immediately after.

Controlling a Run

Lifecycle Hooks

Override any of these methods on your Workflow subclass to react to run lifecycle events. Hook exceptions are swallowed and never affect engine state.


State

State is a plain PHP array that persists in the workflow_runs.state column. Every node receives the full current state and returns a mutation — a partial array of keys to update.

The reducer determines how mutations are merged into the existing state.

Reducers

LaraGraph ships with three reducers:

Class Behaviour
SmartReducer (default) List arrays are appended. Scalars and associative arrays are overwritten.
MergeReducer Deep recursive merge for all keys.
OverwriteReducer Shallow array_merge — always overwrites.

SmartReducer is the right default for most agent workflows: message histories accumulate naturally, while scalar values like status or score simply overwrite.

Custom Reducer

Implement StateReducerInterface and bind it in your service provider, or attach it to a specific workflow:


Human-in-the-Loop

LaraGraph has first-class support for pausing workflows and waiting for human input.

interrupt_before

Pause the run before a node executes. On resume, the node runs normally.

interrupt_after

Pause the run after a node executes but before its outgoing edges are evaluated.

Resuming

Call Laragraph::resume() with any additional state to merge before the run continues:

Dynamic Pause from a Node

Any node can pause the run at runtime by throwing NodePausedException:

You can also pass state mutations to persist before pausing, and surface a human-readable gate reason that rides on the engine's routing column (not $state):

The HumanInterventionRequired event fires with (runId, nodeName, gateReason). Gate reason is also readable on the paused run via $run->routing['gate_reason'].


Node Contracts

Nodes can implement optional contracts to declare capabilities to the engine.

HasName

Give a node a stable identifier used in edge routing and graph visualization:

HasTags

Emit metadata alongside each node execution — useful for tracking token usage, model names, cost centers, or tenant IDs. Tags are automatically persisted to the workflow_node_executions table and broadcast on the NodeCompleted event:

The engine calls tags() after handle() returns, so the node can accumulate values during execution and expose them at the end.

Querying execution history

NodeExecution columns: run_id, node_name, attempt, tags (JSON), executed_at, error_class, error_message, error_trace, failed_at.

When a node fails after exhausting retries, the engine writes exactly one row with the error columns populated — no polluting state.error or parsing Laravel logs.

HasRetryPolicy

Define per-node retry behaviour with exponential backoff and optional jitter:

Restrict retries to specific exception types:

HasQueue

Route a node's job to a specific queue or connection:

HasMiddleware

Attach Laravel job middleware to a node's execution job:

HasLoop

Declare that a node should loop — driving tool execution cycles, polling, or any other repeated sub-task. The compiler automatically injects the loop edges at compile time.

When compiled, the engine injects a {name}.__loop__ node and guards existing exit edges with the negated condition. Use Workflow::toolNode('name') to reference the synthetic loop node in interrupt points:

IsFanInBarrier

Mark a node as a fan-in barrier. The engine tracks how many workers were dispatched into this node and how many have committed their results. It serialises concurrent arrivals under a database lock, and only the final arrival — the one that sees all predecessors complete — runs handle(). All earlier arrivals skip cleanly.

BarrierNode implements IsFanInBarrier out of the box. Implement it on any custom node that acts as a convergence point for parallel branches.


Built-in Nodes

GateNode

Pauses the workflow unconditionally until manually resumed. Use as a static approval gate.

When the gate triggers, a HumanInterventionRequired event fires with the reason. The reason is also stored on $run->routing['gate_reason'] (engine state — not merged into $state). Resume via Laragraph::resume($runId).

GateNode is a one-shot pause: its handle() unconditionally throws. For human-in-the-loop checkpoints where you still want the node's side effects to run first, use interruptAfter() on a regular node instead — see Human-in-the-Loop.

SendNode

Fan-out node — dispatches a Send for each item in a state list, sending each to the same target node with an isolated payload.

Inside WorkerNode, access the payload via $context->payload('query').

BarrierNode

Fan-in barrier — waits for all parallel workers to complete before allowing the downstream edge to fire. Zero configuration required.

The engine automatically tracks how many workers were dispatched into the barrier and how many have committed their results. Early arrivals skip cleanly (removing their pointer to maintain equilibrium). Only the final arrival — when all predecessors are fully complete — runs handle() and evaluates the downstream edges. The node body itself is a no-op; all logic lives in the engine.

Works with both transition fan-out and Send-based fan-out, including multiple sequential barriers in the same workflow.

HttpNode

Makes an HTTP request and stores the response in state. The URL supports {state.key} interpolation.

The response is stored as ['status' => 200, 'body' => [...], 'ok' => true] under responseKey.

For POST/PUT/PATCH requests, set bodyKey to a state key whose value will be sent as the request body:

DelayNode

Pauses execution for a given number of seconds, then continues.

On first execution the node stores a resume-after timestamp, dispatches a delayed queue job, and pauses. The job automatically calls Laragraph::resume() when the delay elapses — no scheduled command or polling required.

CacheNode

Reads from or writes to the Laravel cache. The cache key supports {state.key} interpolation.

NotifyNode

Dispatches a Laravel event with values from state as constructor arguments.


Prism Integration

LaraGraph ships with first-class support for Prism via the Cainy\Laragraph\Integrations\Prism namespace.

PrismNode

A general-purpose LLM node. Supports text generation and structured output — no tool loop injected by default. Use PrismToolNode (below) when you want tool-calling with automatic re-entry.

By default the assistant's response is appended to state['messages'].

Overridable hooks

Subclass when you need dynamic behaviour or structured output:

When schema() returns non-null, the node calls Prism's structured-output path and writes the result to state[outputKey()] instead of state['messages'].

Available overrides: provider(), model(), maxTokens(), temperature(), topP(), systemPrompt($state), prompt($state), messages($state), tools(), schema(), messagesKey(), outputKey(), applyProviderOptions().

PrismToolNode

Extends PrismNode and implements HasLoop — the compiler injects a synthetic .__loop__ tool-executor so the node re-enters itself after each tool call completes (classic ReAct loop).

The injected graph:

To interrupt before tool execution runs:

ToolNode (manual routing)

Abstract base for nodes that execute tool calls from state['messages'] without the automatic loop. Use when you want explicit edges around tool execution (for conditional routing, logging, approval gates, etc.).

Wire the edges yourself:


Laravel AI Integration

LaraGraph integrates with Laravel AI via the AsGraphNode trait.

AsGraphNode Trait

Add AsGraphNode to a standard Laravel AI agent to make it a Laragraph node:

Structured Output

If your agent implements HasStructuredOutput, the trait maps structured response keys directly to state mutation keys:

After execution, state['category'] and state['confidence'] are set directly.

Tool-Using Agents

Laravel AI agents implementing HasTools are automatically detected by the compiler — tool loop injection works exactly as with PrismNode:


Sub-graph Workflows

Any Workflow subclass implements Node and can be embedded inside another workflow. The sub-graph is identified by its class name — no snapshot serialization required.

When the engine executes a sub-graph node:

  1. A child WorkflowRun is created and linked via parent_run_id / parent_node_name.
  2. The child workflow starts normally — its nodes run as independent queue jobs.
  3. The parent run pauses at the sub-graph node.
  4. When the child completes, the engine resumes the parent automatically.
  5. The parent node returns the state delta from the child's final state as a mutation.

Embedded vs Send-dispatched

Sub-graphs behave differently depending on how they're reached:

Reached via Child initial state Delta merged back
Embedded (transition('sub', …)) Parent's full state Child's state diff vs parent's state
Send::toWorkflow('sub', $payload) The Send payload only Child's full final state

Use Send::toWorkflow() for per-item pipelines where parent state should not leak into the child. Use plain embedding when the child should see and operate on parent state (e.g. a reusable "research this topic" sub-graph).

Child Failure Cascade

By default, when a child workflow fails, the failure cascades to the parent: the parent is marked Failed, its pointers are cleared, and WorkflowFailed fires with the child's exception.

Opt out on a per-child-workflow basis by overriding shouldCascadeFailure():

Useful for map-reduce patterns where individual child failures should be aggregated rather than fatal.

Accessing parent metadata from a child

Inside a child node, use the context accessors:

$context->parentRunId, $context->parentNodeName, and $context->parentMetadata() are all null at the top level.


Recursion Limit

The engine tracks total node executions per run and throws RecursionLimitExceeded if the limit is hit.

The default limit is config('laragraph.recursion_limit', 100). Override it per workflow:

For legitimate fan-out workflows (e.g. 50 items, each running a 5-step pipeline), raise this to items × steps + headroom. The exception message includes a hint pointing at ->withRecursionLimit() when the limit is hit.


Events

LaraGraph fires events throughout the workflow lifecycle. All events implement ShouldBroadcast and are broadcast on the workflow channel when broadcasting is enabled.

Event Payload
WorkflowStarted runId, workflowKey
NodeExecuting runId, nodeName
NodeCompleted runId, nodeName, mutation, tags
NodeFailed runId, nodeName, exception
WorkflowCompleted runId, workflowKey
WorkflowFailed runId, exception, workflowKey
WorkflowResumed runId, workflowKey
HumanInterventionRequired runId, nodeName, reason

Broadcasting

Enable broadcasting in your .env:

Each run broadcasts on channel {prefix}{runId} (e.g. workflow.42). Authorize the channel in routes/channels.php as needed.


Configuration


Testing

LaraGraph works with the sync queue driver in tests — set QUEUE_CONNECTION=sync in your phpunit.xml and runs execute synchronously, making assertions straightforward:

For unit-testing individual nodes, use the makeContext() test helper:


License

The MIT License (MIT). Please see License File for more information.


All versions of laragraph with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
illuminate/contracts Version ^11.0||^12.0||^13.0
prism-php/prism Version ^0.99|^0.100
spatie/laravel-package-tools Version ^1.16
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package cainydev/laragraph contains the following files

Loading the files please wait ...