PHPackages                             twdnhfr/laravel-deepagents - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. twdnhfr/laravel-deepagents

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

twdnhfr/laravel-deepagents
==========================

A deep-agent harness for the Laravel AI SDK: an owned agent loop with planning, sub-agents, persistent memory, human-in-the-loop approval and automatic context management.

v0.6.0(1mo ago)729MITPHPPHP ^8.3CI passing

Since May 29Pushed 1w agoCompare

[ Source](https://github.com/twdnhfr/laravel-deepagents)[ Packagist](https://packagist.org/packages/twdnhfr/laravel-deepagents)[ Docs](https://github.com/twdnhfr/laravel-deepagents)[ GitHub Sponsors](https://github.com/twdnhfr)[ RSS](/packages/twdnhfr-laravel-deepagents/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (8)Dependencies (27)Versions (9)Used By (0)

Laravel Deep Agents
===================

[](#laravel-deep-agents)

[![Latest Version on Packagist](https://camo.githubusercontent.com/60fbc627fb1463236431d19d13c8242c01d4070e72d98821b0037bed4d461f8e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7477646e6866722f6c61726176656c2d646565706167656e74732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/twdnhfr/laravel-deepagents)[![Tests](https://camo.githubusercontent.com/f43c546b73e76e378e388286789ffdc9d9831febb1aad553b41a6c12d1771d76/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7477646e6866722f6c61726176656c2d646565706167656e74732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/twdnhfr/laravel-deepagents/actions?query=workflow%3Arun-tests+branch%3Amain)[![License](https://camo.githubusercontent.com/7e2453d9e028e8c03a58a62d47d05885a84915daadacb318af5b6ea9c13bf5ef/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f7477646e6866722f6c61726176656c2d646565706167656e74733f7374796c653d666c61742d737175617265)](LICENSE.md)

A **deep-agent harness** for the [Laravel AI SDK](https://github.com/laravel/ai) — an owned, resumable agent loop with planning, sub-agents, human-in-the-loop approval, multi-turn conversations, memory and automatic context management, built on top of `laravel/ai`.

Warning

**Early work in progress.** The runtime and core harness are built and tested — owned agent loop, human-in-the-loop, multi-turn, planning, sub-agents, memory and context management (summarization + tool-output offloading) over pluggable backends. Skills are planned; filesystem/shell tools and token streaming are out of scope for now (see [`TODO.md`](TODO.md) and [`docs/adr/`](docs/adr/)). APIs may still change before 1.0.

Note

Not affiliated with or endorsed by LangChain. An independent reimplementation for Laravel, inspired by the [`deepagents`](https://github.com/langchain-ai/deepagents) project.

What is Laravel Deep Agents?
----------------------------

[](#what-is-laravel-deep-agents)

The [Laravel AI SDK](https://github.com/laravel/ai) (`laravel/ai`) is an excellent **engine**: one unified, expressive API over many providers (OpenAI, Anthropic, Gemini, and more), with tool calling, structured output, streaming, embeddings and more. Point it at a model, hand it some tools, call `prompt()` — done.

But an *engine* is not an *agent harness*. The moment you ask an agent to do real, long-horizon work — research across many steps, read and write files, delegate subtasks to focused sub-agents, **pause for your approval before doing something destructive**, and later **pick up exactly where it left off** — you need a layer of opinionated machinery on top: planning, a virtual filesystem, sub-agents, automatic context management, human-in-the-loop, skills and memory.

In the Python/LangChain world that layer is [`deepagents`](https://github.com/langchain-ai/deepagents) — *"the batteries-included agent harness"*, itself an attempt to distill what makes Claude Code general-purpose. There was no equivalent for Laravel.

**Laravel Deep Agents is that layer.** It builds *on top of* `laravel/ai` and brings the deepagents feature set to PHP:

> The SDK stays the engine. This package adds the harness.

### Why a custom loop?

[](#why-a-custom-loop)

`laravel/ai` runs its model↔tool loop *inside* the provider — great for a one-shot `prompt()`, but it gives you no place to step in *between* the model choosing a tool and that tool running. That in-between is exactly where approval gates, permission checks and context compaction live.

So this package **owns the loop**: it drives one model turn at a time and decides for itself when to run a tool, when to pause for a human, and when it's done. The entire state of a run is a plain, serializable value object — so a run can pause, be stored in your database or a queued job, and resume in a completely different request.

The how-and-why is recorded as Architecture Decision Records in [`docs/adr/`](docs/adr/).

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 13
- [`laravel/ai`](https://github.com/laravel/ai) `^0.9` (configured with at least one provider/API key)

Installation
------------

[](#installation)

```
composer require twdnhfr/laravel-deepagents
```

The package auto-registers. Configure your provider and API key through the Laravel AI SDK as usual (e.g. `ANTHROPIC_API_KEY` in `.env` and the provider entry in `config/ai.php`).

Quickstart
----------

[](#quickstart)

Define a tool (the standard `laravel/ai` `Tool` contract), then build and run an agent:

```
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;

class GetWeather implements Tool
{
    public function name(): string
    {
        return 'get_weather';
    }

    public function description(): string
    {
        return 'Get the current weather for a city.';
    }

    public function handle(Request $request): string
    {
        return "It's 21°C and sunny in {$request['city']}.";
    }

    public function schema(JsonSchema $schema): array
    {
        return ['city' => $schema->string()->description('The city name.')->required()];
    }
}
```

```
use Twdnhfr\LaravelDeepagents\DeepAgent;

$state = DeepAgent::make()
    ->provider('anthropic')              // resolved from config/ai.php
    ->model('claude-sonnet-4-5')         // optional — defaults to the provider's default model
    ->instructions('You are a helpful weather assistant.')
    ->tool(new GetWeather)
    ->run('What should I wear in Berlin today?');

echo $state->finalText;
```

By default the agent runs **autonomously**: it calls tools and loops until it has a final answer.

Note

**One tool instance per agent.** Built-in tools receive run-scoped state (the `RunState`, the storage backend) by injection right before execution — an instance shared between two agents (e.g. a parent and a sub-agent) would leak state between their runs. Construct tools fresh per agent, as in the example.

### Planning with todos

[](#planning-with-todos)

Give the agent the built-in `write_todos` tool so it can keep a visible plan. The list lives on the run state:

```
$state = DeepAgent::make()
    ->provider('anthropic')
    ->withTodos()
    ->instructions('Plan your work with the todo list before acting.')
    ->run('Research and outline a short article on vector databases');

foreach ($state->todos as $todo) {
    echo "[{$todo['status']}] {$todo['content']}\n";
}
```

### Human-in-the-loop: approve before tools run

[](#human-in-the-loop-approve-before-tools-run)

Opt into approval mode and the agent **suspends before any tool call** instead of executing it. The suspended run is fully serializable, so you can store it, show the pending action to a human, and resume later — in another request or a queued job:

```
use Twdnhfr\LaravelDeepagents\DeepAgent;
use Twdnhfr\LaravelDeepagents\Runtime\RunState;

$agent = DeepAgent::make()
    ->provider('anthropic')
    ->tool(new DeleteStaleRecords)   // something you don't want running unsupervised
    ->requireApproval();

$state = $agent->run('Clean up records older than a year');

if ($state->isSuspended()) {
    // Show the human exactly what the model wants to do…
    foreach ($state->pendingToolCalls as $call) {
        info("Agent wants to call {$call['name']}", $call['arguments']);
    }

    // …and persist the run while you wait for their decision.
    $stored = $state->toJson();   // -> a DB column, cache entry, queued job, etc.
}

// Later, once approved (possibly in a different request):
$final = $agent->resume(RunState::fromJson($stored));

echo $final->finalText;
```

The human does not have to wave everything through. Record a **per-call decision** on the state before resuming — approve as-is (the default), execute with corrected arguments, or reject with a reason the model gets back as the tool's result so it can adjust its plan:

```
$state = RunState::fromJson($stored);

$state->approve('tc_1');                                  // optional — approved is the default
$state->edit('tc_2', ['path' => 'drafts/safe.md']);       // run with corrected arguments
$state->reject('tc_3', 'Never email customers directly.'); // skip; the reason becomes the result

$final = $agent->resume($state);
```

Decisions are plain data on the `RunState`, so they survive `toJson()`/`fromJson()` — collect them in a controller, persist, and resume in a queued job. A rejected call is never executed; the model sees `The user rejected this tool call: …` and reacts. With `edit()`, the model's original arguments stay visible on the assistant message in the history, so the change is auditable.

### Multi-turn conversations

[](#multi-turn-conversations)

`run()` starts a fresh run; `continue()` carries an existing run forward with the user's next message, so the agent keeps full prior context. The run state is serializable, so you can persist it between turns (session, DB, …):

```
$state = $agent->run('What is the weather in Berlin?');
// ...store $state->toJson()...

// next turn, same conversation:
$state = $agent->continue(RunState::fromJson($stored), 'And in Tokyo?');
echo $state->finalText; // resolves "And in Tokyo?" using the prior turn
```

A *suspended* run (pending tool approval) cannot be `continue()`d — it throws; `resume()` it first. Each `continue()` resets the `maxTurns` budget, which is otherwise tracked on the run state across suspend/resume.

### Sub-agents: delegate to an isolated context

[](#sub-agents-delegate-to-an-isolated-context)

Register a sub-agent and the parent gets a `task` tool to delegate self-contained work to it. The sub-agent runs as its own `DeepAgent` to completion and hands back only its final text:

```
$researcher = DeepAgent::make()
    ->provider('anthropic')
    ->instructions('You research a topic and return a concise summary.');

$state = DeepAgent::make()
    ->provider('anthropic')
    ->subAgent('researcher', 'Researches a topic in depth.', $researcher)
    ->run('Delegate research on vector databases, then summarize the findings.');
```

Important

**"Isolated" means the *context*, not the *workspace*.** A sub-agent gets a fresh conversation — it never sees the parent's message history or todos, and only its final text returns to the parent. But it **shares the parent's storage backend** (artifacts/memory) by default, mirroring deepagents' shared virtual filesystem. So:

- Artifacts a sub-agent writes are readable by the parent and its siblings — they all hit the same store. Two agents writing the same path **overwrite**each other; there is no per-sub-agent sandbox.
- The exception is **offloaded tool output** (`offloadLargeToolResults()`): it is written under the run-scoped path `runs/{runId}/tool/{callId}`, so runs sharing a persistent backend never collide — and a host can clean up after a run via `backend->list("runs/{$state->id}/")`.
- To give a sub-agent its own private store, set `->backend(...)` on it explicitly — an explicit backend is always kept, never replaced by the parent's.

How it works
------------

[](#how-it-works)

```
DeepAgent (fluent builder)              ← the public API
   │ configures & starts
   ▼
Runtime\Loop  ── drives one turn ──►  laravel/ai
   │  maxSteps: 0                       TextProvider::textGenerationLoop()->generate($messages)
   │  (autonomous | approval pause)      └─ any provider (OpenAI, Anthropic, Gemini, …)
   ▼
Runtime\RunState  ── json_encode ──►  DB / queue / HTTP body ── json_decode ──►  resume()

```

- **`DeepAgent`** — the fluent front door (`make()`, `provider()`, `model()`, `instructions()`, `tool()`/`tools()`, `withTodos()`, `subAgent()`, `memory()`, `summarize()`, `requireApproval()`, `maxTurns()`, `run()`, `resume()`, `continue()`).
- **`Runtime\Loop`** — drives the agent one model turn at a time using `maxSteps: 0`, the seam that returns the model's tool-call intention *without* executing it (verified across Anthropic, OpenAI and Gemini — see [ADR-0002](docs/adr/0002-maxsteps-zero-single-turn-seam.md)).
- **`Runtime\RunState`** — the serializable state of a run (history, pending tool calls, todos, status). The single source of truth that survives suspend → persist → resume.
- **`Contracts\Backend`** + **`Backends\StateBackend`** — the pluggable file-storage seam for the upcoming filesystem tools.

Status &amp; roadmap
--------------------

[](#status--roadmap)

AreaStateAgent loop (autonomous + per-tool approval), serializable `RunState`, HITL resume✅ built &amp; tested`DeepAgent` fluent builder + multi-turn (`continue()`)✅`write_todos` planning tool✅Sub-agents (`task`)✅Context management (summarization + tool-output offloading to artifacts)✅Memory (`AGENTS.md`) + BASE prompt assembly✅Safe tool execution + dangling-tool-call repair✅Pluggable backends (state, filesystem, database, cache) + config-driven default✅Filesystem &amp; shell tools🧊 deferred (see [adoption](docs/adoption.md))Skills, harness profiles, MCP⏳ plannedToken streaming through the loop❌ by design — see [ADR-0004](docs/adr/0004-no-token-streaming-through-the-loop.md)The full plan lives in [`TODO.md`](TODO.md).

Demo
----

[](#demo)

See every feature run offline (no API keys), via a scripted provider:

```
php examples/demo.php
```

It walks through autonomous tool use, planning, human-in-the-loop approval, context summarization and sub-agents. See [`examples/`](examples/).

### Example app: `deepagents-chat`

[](#example-app-deepagents-chat)

[**twdnhfr/deepagents-chat**](https://github.com/twdnhfr/deepagents-chat) is a small Laravel chat app built on this package — a live, end-to-end example. It shows the owned loop in a real request cycle: a tool-call trace, the human-in-the-loop approval flow, multi-turn conversations, a `DatabaseBackend`with tool-output offloading, and Markdown-rendered replies.

Testing
-------

[](#testing)

```
composer test
```

Credits &amp; inspiration
-------------------------

[](#credits--inspiration)

- [`langchain-ai/deepagents`](https://github.com/langchain-ai/deepagents) — the Python harness whose feature set and naming this package follows.
- [`laravel/ai`](https://github.com/laravel/ai) — the SDK this package is built on.
- [twdnhfr](https://github.com/twdnhfr) and [all contributors](../../contributors).

License
-------

[](#license)

The MIT License (MIT). See [LICENSE.md](LICENSE.md).

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance95

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 89.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

7

Last Release

43d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/13796752?v=4)[Tobi](/maintainers/twdnhfr)[@twdnhfr](https://github.com/twdnhfr)

---

Top Contributors

[![twdnhfr](https://avatars.githubusercontent.com/u/13796752?v=4)](https://github.com/twdnhfr "twdnhfr (25 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laravelaiagentsllmhuman-in-the-loopdeep-agents

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/twdnhfr-laravel-deepagents/health.svg)

```
[![Health](https://phpackages.com/badges/twdnhfr-laravel-deepagents/health.svg)](https://phpackages.com/packages/twdnhfr-laravel-deepagents)
```

###  Alternatives

[codewithdennis/filament-select-tree

The multi-level select field enables you to make single selections from a predefined list of options that are organized into multiple levels or depths.

330530.5k30](/packages/codewithdennis-filament-select-tree)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
