> For the complete documentation index, see [llms.txt](https://docs.neuron-ai.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neuron-ai.dev/neuron-v4/overview/upgrade.md).

# Upgrade

## Upgrade to v4 from v3

In this new major version the public APIs of Neuron components weren't changed dramatically (we minimized the impact as much as possible). We've focused on improving the most important component on which the entire framework is based, Workflow. Since it's the foundation of the entire architecture, the changes implemented in this new version may impact your code, especially if you use advanced patterns like interrupts and persistence.

We also took advantage of this release to fix other critical issues emerged in the v3 like the Tool Approval flow in the Agent, and other design improvements to have more freedom to evolve the framework with less breaking changes in the future.

We continue to work to provide the best possible developer experience, to help you create successful AI products in PHP.

### Agentic Upgrade

We documented the entire upgrade process in a dedicated directory <https://github.com/neuron-core/neuron-ai/tree/4.x/upgrade>. You can point your coding agent to this directory and it will automatically receive the instructions needed to adapt your code to this new version.

## Updating Dependencies

You should update the following dependencies in your application's `composer.json` file:

* **neuron-core/neuron-ai** to **^4.0**

The `inspector-php` package was removed from default dependencies. So you have to install it in your application if you want to connect your agent to the [Inspector](https://inspector.dev/) monitoring dashboard:

```shellscript
composer require inspector-apm/inspector-php
```

## High Impact Changes

### Tool becomes abstract

The `Tool` class is no longer a concrete class and can no longer be used directly. Its design is now intended to be extendable, allowing you to implement your own tools with less code and more flexibility.

We also removed the constructor from the abstract class so you need to specify tool name and description as normal class properties instead of calling the parent constructor. You are free to use a class constructor only if you want to pass external dependencies to the tool:

```php
class MyTool extends Tool
{
    protected string $name = 'my_tool';
    
    protected ?string $description = 'What the tool does.';
    
    public function __construct(protected string $apiKey){}
    
    public function __invoke()
    {
        ...
    }
}
```

### Remove WorkflowHandler

The Workflow component was subject of an important refactoring in order to simplify its usage and public APIs. Working with Workflow in the previous version, you were need to call the `init()` method to get the `WorkflowHandler` instance and than call `run()` or `events()` on the handler to finally execute the workflow:

```php
$handler = MyWorkflow::make()->init();

// One shot run
$finalState = $handler->run();

// Stream events
foreach($handler->events() as $chunk) {
    // ...
}
$finalState = $handler->getResult();
```

Following a drastic simplification of the workflow execution logic, the handler is no longer necessary and it is possible to invoke the two methods `run()` and `events()` directly in the workflow.

```php
// One shot run
$finalState = MyWorkflow::make()->run();

// Stream events
$generator = MyWorkflow::make()->events();
foreach($generator as $chunk) {
    // ...
}
$finalState = $generator->getResult();
```

### Workflow Interrupt/Resume

The architecture of the workflow execution and its interruption capabilities was redisigned to make it easier to manage interruption and tool approval, but also open the doors for the implementation of durable, crash proof, agentic workflows.

#### Remove WorkflowInterrupt exception

In case of interruption the Workflow doesn't throw the special `WorkflowException` to inform the caller script about the interruption. It just return an "interrupted" state:

```php
$state = $workflow->run();

if ($state->isInterrupted()) {
    // Use the information in the request
    $request = $state->getInterruptRequest();
    // The resume token is auto-generated and available from the workflow instance
    $workflowId = $workflow->getWorkflowId();
}
```

No more try/catch block.

This change the [Tool Approval](/neuron-v4/agent/tool-approval.md) flow. Check out the documentation in case you are using this middleware in your agents.

#### Resume Payload

The interruption request you propagate from the node is now only a signal to carry information from the node to the outside caller script. To resume the workflow you no longer need to pass the request back to the workflow. The resume payload is now just a simple array:

```php
// Example of a node calling interrupt()
class InterruptableNode extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): NextEvent
    {
        $payload = $this->interrupt(new ApprovalRequest('human input needed'));
        $state->set('received_feedback', $payload);
        return new NextEvent();
    }
}

// Define the inbound payload — it will be returned by the interrupt() method
$payload = ['action_id' => 'approve'];

$finalState = $workflow->resume($payload);
```

### Agent Instructions

Agent instructions must be an instance of a `SystemMessage`. You can just pass the string to the constructor to make it compatible with this new version:

```php
use NeuronAI\Chat\Messages\SystemMessage;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function instructions(): SystemMessage
    {
        return new SystemMessage(<<<TEXT
            You are an AI Agent specialized in writing YouTube video summaries.
            Get the url of a YouTube video, or ask the user to provide one.
            Use the tools you have available to retrieve the transcription of the video.
            Write a summary in a paragraph without using lists. Use just fluent text.
        TEXT);
    }
}
```

### Tool Approval

The tool approval flow was entirely rewritten. The Agent class now manages the entire process. You just need to take care of rendering the UI so users can decide whether to approve or reject a tool call.

The status of tools requiring approval is stored into the chat history within the last `ToolCallMessage`. This allows you to design the UI to just render the messages in the chat history, and when it meets a `ToolCallMessage` you can check the approval status of the tools to show the Approve/Deny actions, or the normal tool call already happened.

<a href="/pages/SzeKvZMM3OYeEQzCgw7A" class="button primary" data-icon="arrow-right-long">Tool Approval</a>

#### New Tool `requiresApproval()` method

We introduced the `requiresApproval()` method on the Tool class to determine whether approval is needed based on the tool call's arguments:

```php
class MyTool extends Tool
{
    ...,
    
    public function requiresApproval(array $inputs): bool
    {
        return $inputs['amount'] > 100;
    }
}
```

To activate the tool approval flow you always need to attach the [ToolApproval](/neuron-v4/agent/tools.md#tool-approval) middleware to the Agent. Custom approval policy on the middleware have precedence over the one defined in the tool's `requiresApproval()` method.

#### ToolApproval middleware callback

In the previous version the `ToolApproval` middleware used to receive the array of input parameters with which the tool was invoked. Now it receives the concrete tool instance.

```php
new ToolApproval(
    tools: [
        ByTickets::class => function (ToolInterface $tool): bool {
            return $tool->getInput('amount') > 100;
        }
    ]
);
```

Custom approval policy on the middleware have precedence over the one defined in the tool's `requiresApproval()` method.

## Medium Impact

### Monitoring

The framework is transitioning to the [PSR-14 Event Dispatcher](https://www.php-fig.org/psr/psr-14/) interface, instead of the PHP native \SplObserver. We kept the existing interfaces in place, and also the `LogObserver` usgin adapters, but they are marked as `@deprecated`.

This will make it easier to integrate Neuron agents and agentic workflows in general with other existing frameworks and applications.

<a href="/pages/TjLfzzT2yuogGE2zejCa" class="button primary" data-icon="arrow-right-long">Monitoring</a>

### Providers return ProviderResponse

AI provider methods `chat()` and `stream()` now return `ProviderResponse` instead of `Message`. The `ProviderResponse` wraps the assistant message and provides access to the raw HTTP response body and headers.

**This only affects standalone provider usage.** When providers are used inside an Agent (via `chat()`, `stream()`, or `structured()` on the Agent itself), no changes are needed — the Agent handles the `ProviderResponse` internally.

You only need to refactor code that calls provider methods directly, such as in scripts, controllers, commands, or custom workflows.

```php
$response = $provider->chat(new UserMessage(...));

// Get the assistance Message
$response->message();

// Get the raw provider body
$response->body();

// Get the headers
$response->headers();
```

### Changes on AIProviderInterface

We removed `messageMapper()` and `toolPayloadMapper()` from the `AIProviderInterface`, and the new `getModel()` methos was introduced. If you have any custom provider implementation that directly use this interface, you need to adjust it properly.

```php
interface AIProviderInterface
{
    public function getModel(): string;

    public function systemPrompt(string|array|null $prompt): AIProviderInterface;

    public function setTools(array $tools): AIProviderInterface;

    public function chat(Message ...$messages): ProviderResponse;

    public function stream(Message ...$messages): Generator;

    public function structured(array|Message $messages, string $class, array $response_schema): ProviderResponse;

    public function setHttpClient(HttpClientInterface $client): AIProviderInterface;
}
```
