Skip to content
Back to skills

Neuron Agent

ASecurity

Create, configure, and extend Neuron AI agents in PHP using providers, tools, chat history, memory, and custom workflow nodes. Use for Neuron Agent implementation, configuration, conversation lifecycle, and input/output extensions.

  • 2,122 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 4, 2026
ai-agentsphpshellbashsqlnodeexpressawsazuretestingapi

Works with

  • cursor
  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add neuron-core/neuron-ai --skill neuron-agent --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Neuron Agent?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Neuron Agent
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/neuron-core-neuron-agent/badge)](https://www.skillsdirectory.com/skills/neuron-core-neuron-agent)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: neuron-agent
description: Create, configure, and extend Neuron AI agents in PHP using providers, tools, chat history, memory, and custom workflow nodes. Use for Neuron Agent implementation, configuration, conversation lifecycle, and input/output extensions.
---

# Neuron AI Agent

`Agent` is a composition built on `Workflow`. Extend it when its defaults fit, use its entry/exit hooks for additional stages, or compose a Workflow from the standalone components when control flow differs substantially. This skill covers the current major-version APIs; use the implementation's class names and signatures rather than older examples.

Use `Agent::submitApprovalDecisions($decisions)` for tool approval and `Agent::submitToolResults($results)` for deferred tool results. Both accept maps keyed by tool call ID and stage a continuation; finish with `run()` for an `AgentState` or `events()` for a stream.

## Core Agent Structure

A Neuron agent extends the `Agent` class and implements key methods:

```php
use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\SystemMessage;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: $_ENV['ANTHROPIC_API_KEY'],
            model: $_ENV['ANTHROPIC_MODEL'],
        );
    }

    protected function instructions(): SystemMessage|string
    {
        return new SystemMessage("You are a helpful AI assistant.");
    }
}
```

## Workflow Boundaries and Custom Nodes

The default path is:

```text
AgentStartEvent → AgentStartNode → inference ⇄ tools
    final response → AgentOutputEvent → AgentEndNode → StopEvent
```

`ChatNode` handles chat and streaming; `StructuredOutputNode` handles structured inference. Tools may pause for approval or deferred results before returning to inference. `AgentOutputEvent` means the final answer is available for output processing; it is not a terminal event.

- `nodes()` rebuilds the graph from the current configuration each execution segment. `entryNodes()` defaults to `AgentStartNode`; `exitNodes()` defaults to `AgentEndNode`.
- A node's `__invoke(EventType $event, AgentState $state, AgentResources $resources)` receives one exact routed event type; the third parameter is optional. `addNode()` registers a handler; array order does not connect nodes, and duplicate handlers are rejected.
- For preprocessing, override `startEvent()` with an `AgentStartEvent` subclass, register a node for it in `entryNodes()`, and return the original `AgentStartEvent` to retain `parent::entryNodes()`. This keeps request initialization and tool-run resets in `AgentStartNode`. Replacing that initialization entirely requires handling those responsibilities yourself.
- For postprocessing, override `exitNodes()` with a node accepting `AgentOutputEvent`. Return an application event to continue through another node, or `StopEvent` to finish. Replace the default ending: including `parent::exitNodes()` alongside another output handler creates a duplicate.
- `AgentState::$request` holds instructions, messages, and run options. Events route execution; the segment's provider, chat history, instructions and tools arrive in `AgentResources`, built once per segment, so nodes take no collaborators in their constructors. `getMessage()` reads the provider response, while extra artifacts can use state keys.

For example, an Agent subclass can own speech synthesis without replacing inference nodes:

```php
protected function exitNodes(): array
{
    return [new TextToSpeechNode($this->textToSpeech())];
}
```

Read [references/workflow-extension.md](references/workflow-extension.md) when implementing custom output nodes. It contains the complete TTS node, protected provider hook, and an executable example with fake providers, plus input-extension and recovery considerations.

Agent provider, history, instructions, tools and tool-execution settings may change while streaming. Changes configure subsequent segments; the active segment retains its resolved resources, graph and conversation address. Resume still restores recorded input and instructions.

Provider/toolkit hooks are lazy; explicit setters take precedence over their corresponding default hooks. Construct graph collaborators in the hooks so reconstructed runs receive live dependencies. Fluent setters return `static`, so a chain keeps the concrete type, subclass members included.

## Agent Execution Methods

Each verb returns the type its nature produces — the same eager/lazy split a plain
Workflow uses:

| Method | Returns |
|--------|---------|
| `chat($messages)` | `AgentState` — starts a new run and consumes it eagerly |
| `stream($messages)` | `Generator` — starts a new run; `getReturn()` is the `AgentState` |
| `structured($messages, $class)` | The typed output — starts a new run and consumes it eagerly |
| `run()` | `AgentState` — inherited eager Workflow terminal |
| `events()` | `Generator` — inherited pull-stream Workflow terminal; `getReturn()` is the `AgentState` |
| `submitApprovalDecisions($decisions)` | Stages tool approval decisions keyed by call ID; finish with `run()` or `events()` |
| `submitToolResults($results)` | Stages deferred tool results keyed by call ID; finish with `run()` or `events()` |

`chat()` runs eagerly and returns the final state directly (no separate `->run()` step).
Pass `stream: true` to stream the provider's answer to a configured channel, as a queue
worker does (see the **neuron-streaming** skill).
Read the assistant message off it with `getMessage()`, and read an approval pause with
`isInterrupted()` / `getInterruptRequest()` — the same surface a plain `WorkflowState`
exposes.

The inherited `run()` / `events()` terminals execute staged intent. Without a
staged operation they start or recover a failed run. `resume($payload, ...)` stages
an explicit continuation. Agent new-turn methods select a fresh execution internally. `resume()->run()` is an inputless continuation for due timers
or crash recovery. To recover a failed turn, call `run()` or `events()`;
calling `chat()` instead explicitly starts a new turn and supersedes it. Agent tool approval accepts decisions keyed by tool call ID through
`submitApprovalDecisions($decisions)->run()` or
`submitApprovalDecisions($decisions)->events()`. Deferred tool results use
`submitToolResults($results)->run()` or `submitToolResults($results)->events()`.
Neither method starts a new user turn.

`Agent` specializes the generic `Workflow<AgentState>` contract, so inherited
`run()`, `events()`, and configured `setState()` seeds retain the concrete
`AgentState` type without Agent forwarding methods or local type assertions.

### Chat Mode (Synchronous)

For standard back-and-forth conversations:

```php
$agent = MyAgent::make()->setThreadId('demo');

$response = $agent->chat(
    new UserMessage("Hello!")
)->getMessage();

echo $response->getContent();
```

### Stream Mode (Real-time)

`stream()` returns a `Generator` that yields live output as the run progresses: provider
chunks (`TextChunk`, `ReasoningChunk`, `ToolCallChunk`, ...) plus portable progress
events yielded by workflow nodes. Read the final `AgentState` from `getReturn()`.

```php
use NeuronAI\Chat\Messages\Stream\Chunks\TextChunk;

$stream = $agent->stream(new UserMessage("Hello"));

foreach ($stream as $item) {
    if ($item instanceof TextChunk) {
        echo $item->content;
    }
}

$state = $stream->getReturn();
```

To shape the output for a UI protocol (Vercel AI SDK, AG-UI) attach an adapter with
`setStreamAdapter()`; to push output to a websocket or queue consumer instead of the
HTTP response attach a channel with `setChannel()`. Both are covered in full by the
**neuron-streaming** skill.

### Structured Output Mode

For extracting structured data from a natural language:

```php
use NeuronAI\StructuredOutput\SchemaProperty;

class Person
{
    #[SchemaProperty(description: 'The user name', required: true)]
    public string $name;

    #[SchemaProperty(description: 'What the user loves to eat')]
    public string $preference;
}

$person = $agent->structured(
    new UserMessage("I'm John and I like pizza!"),
    Person::class
);
```

## Providers

`provider()` returns any `AIProviderInterface`; `setAiProvider()` on an instance takes precedence over the hook. Key-based vendors share the shape `new OpenAI(key: $_ENV['OPENAI_API_KEY'], model: $_ENV['OPENAI_MODEL'])` under their own class; Ollama takes a `url` instead of a key. Shipped: Anthropic, OpenAI (Chat Completions and Responses API), Azure OpenAI, Gemini, Anthropic and Gemini on Vertex AI, AWS Bedrock, Mistral, Ollama, Cohere, Deepseek, Grok, HuggingFace, ZAI, Alibaba DashScope, any OpenAI-compatible endpoint through `OpenAILike`, and speech-to-text, text-to-speech, and image providers behind the same interface.

Read [references/providers.md](references/providers.md) when the user names a vendor other than Anthropic or OpenAI, needs vendor request parameters (thinking, temperature, response options), a self-hosted or OpenAI-compatible endpoint, cloud-platform credentials, a custom HTTP client, or a speech or image provider. It lists every class with its namespace and constructor arguments.

## Tools Integration

### Adding Built-in Toolkits

```php
use NeuronAI\Tools\Toolkits\Calendar\CalendarToolkit;
use NeuronAI\Tools\Toolkits\Calculator\CalculatorToolkit;
use NeuronAI\Tools\Toolkits\FileSystem\FileSystemToolkit;

protected function tools(): array
{
    return [
        FileSystemToolkit::make(scope: '/srv/agent-workspace'),
        CalendarToolkit::make(),
        CalculatorToolkit::make(),
    ];
}
```

`FileSystemToolkit` is the exact class spelling and namespace. The scope directory must already exist when the tools are built; provision the example directory or supply an existing application workspace. `scope` confines file-tool paths to a directory; `null` leaves them unrestricted. `BashTool` validates its working directory but cannot confine the command itself. Configure process isolation when shell confinement is required, or exclude `BashTool` when shell execution is not needed.

Toolkits contribute their `guidelines()` to Agent instructions. `only()` and `exclude()` take arrays of tool class names; `with()` customizes a provided tool instance:

```php
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Tools\Toolkits\FileSystem\BashTool;
use NeuronAI\Tools\Toolkits\FileSystem\DeleteFileTool;

$files = FileSystemToolkit::make(scope: '/srv/agent-workspace')
    ->exclude([BashTool::class])
    ->with(DeleteFileTool::class, fn (DeleteFileTool $tool): ToolInterface => $tool->requireApproval());
```

### Available Toolkits

- **FileSystemToolkit** — read, write, edit, delete, glob, grep, parse documents, and execute shell commands.
- **CalendarToolkit** — date/time formatting, arithmetic, comparisons, period boundaries, and timezone conversion. It does not connect to a calendar service or schedule workflow wakeups.
- **CalculatorToolkit** — expression evaluation, exact integer arithmetic, and statistics. Exact integer tools require `ext-bcmath`.
- **MySQLToolkit** / **PGSQLToolkit** — schema inspection, selects, and writes for MySQL/PostgreSQL.
- **TodoPlanningToolkit** — a `write_todos` tool the model uses to plan multi-step work and report progress. The list lives in the conversation, so it survives interruptions and later turns without extra state.
- **TavilyToolkit** — web search, extraction, and crawling.
- **JinaToolkit** — web search and URL reading; reranking is a separate RAG component.
- **SupadataYouTubeToolkit** — video metadata/transcripts and channel/playlist lookup. Deprecated: it will be removed in the next major version.
- **ZepLongTermMemoryToolkit** — Zep graph search and ingestion exposed as tools; available independently of RAG conversation retrieval. Deprecated: it will be removed in the next major version.

The HTTP toolkits (Tavily, Jina, Supadata, Zep) take an optional `httpClient` as their last constructor argument and pass it to their tools, so tests and framework adapters control the transport.

`NeuronAI\Tools\Toolkits\AWS\SESTool` is a standalone email tool, not a `SESToolkit`. Deprecated: it will be removed in the next major version.

### Creating Custom Tools

Use **neuron-tool** for full tool schemas, deferred execution, and multimodal results. Define tool metadata as properties; the base `Tool` has no constructor:

```php
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolProperty;
use NeuronAI\Tools\PropertyType;

class WeatherTool extends Tool
{
    protected string $name = 'get_weather';

    protected ?string $description = 'Get the current weather for a location';

    /**
     * @return ToolProperty[]
     */
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'location',
                type: PropertyType::STRING,
                description: 'The city name',
                required: true,
            ),
        ];
    }

    public function __invoke(string $location): string
    {
        // Call weather API and return result
        return "The weather in {$location} is sunny, 72°F";
    }
}
```

## Agent Instructions

Agent instructions are a `SystemMessage` (`instructions()` returns `SystemMessage|string` — a plain string is wrapped automatically). A `SystemMessage` carries one or more `SystemContent` blocks; mark a block with `->cache()` to enable provider prompt caching on it:

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

protected function instructions(): SystemMessage|string
{
    return new SystemMessage([
        // Large static context: cache it to reduce cost and latency.
        (new SystemContent("You are a data analyst expert in creating reports. ..."))->cache(),
        // Dynamic part, left uncached.
        new SystemContent("Today is " . date('Y-m-d')),
    ]);
}
```

`SystemMessage::cache()` marks all of the message's blocks as cached at once.

Instructions can also be set fluently:

```php
$agent->setInstructions('You are a helpful assistant.');
```

## Chat History & Thread Identity

Conversations are stored through a **message store**, and the Agent opens a working history over it for every execution segment. Declare the conversation identity with `make(workflowId:)` or `setThreadId()`, and give the Agent a store:

```php
use NeuronAI\Chat\History\SQLMessageStore;

$agent = MyAgent::make(workflowId: $threadId);
$agent->setMessageStore(new SQLMessageStore($pdo));
$state = $agent->chat(new UserMessage($input));
```

A store names the thread on every call and keeps no conversation state, so one instance can serve the whole application: bind it once in the container, or return it from the `messageStore()` hook. `getThreadId(): ?string` reads the resolved conversation identity. The framework never makes one up: executing, inspecting or answering an Agent without one, or reading its chat history, throws an `AgentException` naming `setThreadId()`.

An Agent shared by a container (a singleton, a worker-lifetime service) serves each request through a bound copy: `$agent->for($threadId)->chat(...)`. `for()` never modifies the shared agent, so configure that one when it is built and put per-request settings on the copy. Hooks run once per copy, so give a shared agent durable stores with setters: in-memory defaults belong to one copy.

Size the conversation sent to the model to the provider's model with the `contextWindow()` hook (50,000 tokens by default), or with `setContextWindow()` when the Agent is configured from outside:

```php
protected function contextWindow(): int
{
    return 190_000;
}

// Or, without a subclass:
$agent->setContextWindow(190_000);
```

The window covers the whole request: instructions, tool definitions and messages, as the provider's usage reports them. Keep it at least 5% below the model's limit, as 190,000 is for a 200,000-token model: rather than drop a whole turn, the history may keep up to 5% more than the window.

`getChatHistory()->getMessages()` returns the model context. To render a whole conversation, read its transcript from the store; message IDs are stable, so they serve as UI keys and page cursors:

```php
$page = $store->loadAll($threadId, limit: 50);
$older = $store->loadAll($threadId, limit: 50, before: $page[0]->getId());
```

The default in-memory store lives as long as the Agent instance. For later-process continuation, declare the thread before ignition and configure a durable store and workflow persistence.

### Conversation memory

Conversation memory is a RAG composition: `SemanticMemoryRetrieval` accepts the authorized thread IDs and builds its filters; `CompositeRetrieval` combines it with ordinary document retrieval. Attach `ConversationIngestionNode` through `exitNodes()` to create conversation documents after a completed response, in a dedicated conversation store. Creation and recall are independent opt-ins.

See [conversation memory](references/conversation-memory.md) for complete retrieval, ingestion, recovery and deletion examples. `resetConversation()` clears the working history and pending execution; delete conversation documents explicitly through the vector store.

### Message Stores
- `InMemoryMessageStore` - Default, process memory
- `FileMessageStore` - One JSON file per thread, named with the thread ID's letter case: on macOS and Windows, IDs that differ only by case share a file
- `SQLMessageStore` - Database-backed (PDO). On MySQL and MariaDB, declare `thread_id` and `message_id` as `VARBINARY` (the class docblock shows the table): the default collations ignore case and accents, so `user-Alice` and `user-alice` would share a thread
- `EloquentMessageStore` - Laravel Eloquent integration. On MySQL and MariaDB, create the ID columns with `$table->binary('thread_id', 255)` and `$table->binary('message_id', 64)` for the same reason; keep `string()` on PostgreSQL and SQLite

## Content Blocks (Multi-modal)

Agents support multiple content types:

```php
use NeuronAI\Chat\Messages\ContentBlocks\TextContent;
use NeuronAI\Chat\Messages\ContentBlocks\ImageContent;
use NeuronAI\Chat\Enums\MediaType;
use NeuronAI\Chat\Enums\SourceType;

$message = new UserMessage([
    new TextContent('Analyze this image:'),
    new ImageContent(
        content: 'https://example.com/image.jpg',
        sourceType: SourceType::URL,
        mediaType: MediaType::JPEG
    ),
]);
```

Use the `MediaType` enum for common MIME types (images, documents, audio, video). The `mediaType` parameter also accepts a plain string (`'image/x-custom'`) for types not covered by the enum.

## CLI Generation

Use the Neuron CLI to generate an agent boilerplate:

```bash
vendor/bin/neuron make:agent MyCustomAgent
```

## Common Patterns

### Tool Approval
Human oversight of tool execution is built into `ToolNode` — nothing to attach.
Each tool declares its own risk via the protected `approvalPolicy(): bool|string`
hook (a string counts as `true` and doubles as the approval reason shown to the approver),
and you override the declaration per tool where you attach it:

```php
protected function tools(): array
{
    return [
        DeleteFileTool::make()->requireApproval(),        // force the gate
        RiskyThirdPartyTool::make()->suppressApproval(),  // waive a declared gate
    ];
}
```

Use the **neuron-tool-approval** skill for the complete flow: rendering the approve/deny
UI from chat history, submitting decisions, and building a single endpoint that handles
both conversation turns and approval resumes.

### Observability

Subscribe through the PSR-14 event system; a `LogListener` adapts an application-provided PSR-3 logger:

```php
use NeuronAI\Observability\LogListener;
use NeuronAI\Observability\ObservabilityEvent;

$agent->subscribe(ObservabilityEvent::class, new LogListener($logger));
```

Use **neuron-monitoring** for event selection, external dispatchers, and Neuron Cloud integration.

### Parallel Tool Calls
Execute local tools in parallel (requires `pcntl`, `posix` and `spatie/fork`):

```php
$agent->parallelToolCalls(true);
```

Each child process opens its own HTTP connections. Use the `beforeChild` and `afterChild` callbacks for the application's own process-bound resources, such as database connections.

## Persistence and Durability

Agent is built on Workflow, so it inherits the same persistence system. Enable persistence to make agent executions **survive crashes** and **continue after interruptions** (e.g., a tool-approval suspension).

```php
use NeuronAI\Workflow\Persistence\FilePersistence;

$response = MyAgent::make(workflowId: $threadId)
    ->setPersistence(new FilePersistence('/path/to/storage'))
    ->chat(new UserMessage('Hello'))
    ->getMessage();
```

When the agent suspends (e.g., waiting for tool approval), no exception is thrown —
`chat()` returns an `AgentState` marked interrupted. The **thread is the workflow ID**:
the run's durable records live in the partition named by the threadId itself, so a
later continuation built from the threadId alone finds the pending run — no
workflow coordination ID needs to be stored by the application:

```php
$state = MyAgent::make(workflowId: $threadId)
    ->setMessageStore(new SQLMessageStore($pdo))
    ->setPersistence(new FilePersistence('/path/to/storage'))
    ->chat(new UserMessage('Delete file /tmp/old.log'));

if ($state->isInterrupted()) {
    // Render the current request/history; a paused run has no final answer yet.
    // ... user approves/rejects ...

    // A new execution cycle (e.g. the approve endpoint): the thread alone
    // identifies the run; decisions are keyed by tool callId.
    $state = MyAgent::make(workflowId: $threadId)
        ->setMessageStore(new SQLMessageStore($pdo))
        ->setPersistence(new FilePersistence('/path/to/storage'))
        ->submitApprovalDecisions(['call_123' => 'approve'])
        ->run();
}

$response = $state->getMessage();
```

`submitApprovalDecisions()` stages decisions for the current approval request,
so application code needs only the thread ID
and decisions keyed by tool call ID. Call `events()` instead of `run()` to
iterate the continued segment's output yourself. Either way, the answer streams
only if the turn started with `stream()` or `chat(..., stream: true)`.

Deferred tools continue through the same thread identity:

```php
$state = MyAgent::make(workflowId: $threadId)
    ->submitToolResults([
        'call_123' => ['result' => ['title' => 'Example']],
        'call_456' => ['error' => 'Browser operation cancelled'],
    ])->run();
```

Reconstruct the same durable history and persistence as for the original turn.
Each entry contains exactly one JSON-compatible `result` or string `error`.
Partial results accumulate; the returned state may be interrupted again.
As with approvals, `events()` iterates the continuation's output, and the answer
streams only if the turn started streaming. Raw AG-UI or Vercel payloads use
`submitInputs($payload, $translator)` as described in **neuron-frontend-integration**.

Other interruption types use the generic Workflow API: application-controlled
event waits use `signal($name, $payload)->run()`, due timers and inputless
recovery use `resume()->run()`, and durable platform SDKs pass run and execution-attempt fences to
`resume($payload, expectedRunId: $runId, expectedExecutionAttempt: $attempt)->run()` or `->events()`. A background, workflow-ID-first
continuation uses `make(workflowId:)`; the Agent's thread ID then arrives from
the ignition record and is bound into history by the framework.

Available backends: `FilePersistence`, `DatabasePersistence`, `EloquentPersistence`, and `RedisPersistence` (optional `ext-redis`, using a connected `\Redis` client). See the **neuron-workflow** skill for full details on persistence and continuation, and the **neuron-tool-approval** skill for the complete approval flow (UI rendering, decision payloads, unified endpoint).

### Failed turns, pending approvals, and the lease

A failed first inference does not commit its inbound message. Later failures can
leave earlier successful work in history: tool-loop inputs may be committed,
and a failed output node can leave the completed text exchange in history.
Failure does not roll back committed writes.

`run()` / `events()` recover a failed turn using committed steps and memos;
`resume()->run()` is an explicit continuation. A new `chat()` supersedes the
failed run with a new user turn. A failed node may repeat an external operation
if its result was not durably recorded; use `memoize()` inside custom nodes and
external idempotency where needed.

A **pending approval** does lock the thread: a `chat()` while the run is
suspended throws `RunInFlightException`, whose `interrupt` property carries the
pending `ApprovalRequest`. Catch it
to re-render the pending decision, and settle it with `submitApprovalDecisions($decisions)`
(decline decisions are the cancel path).

`abandon()` discards an eligible paused, failed, or dead run without starting
another and returns `false` when none exists. Agent refuses abandonment while
history ends in an unanswered tool call, including approval and deferred-result
waits. Settle that call first, or use `resetConversation()` to abandon the run
and clear chat history. Reset bypasses the unanswered-call check,
but still respects live execution leases and retained-completion guards.

Every Agent run holds a **ten-minute lease** by default. A process killed with
no chance to record its failure (memory limit, `max_execution_time`, an
OOM-killed container) leaves the thread `running`; once the lease deadline
passes, the next `chat()` supersedes the dead run instead of refusing. Raise it
above your slowest provider or tool call with `setLeaseTimeout()` or by
overriding `leaseTimeout()`; `null` disables it, in which case a killed
process strands the thread until `resume()->run()` takes it over. A suspended run holds
no lease. Default tool approval has no deadline; custom wait deadlines still
need an inputless continuation to be evaluated.

Successful runs release their persistence partition by default. For durable
completion delivery, opt into `retainCompletionUntilAcknowledged()`, retrieve
retained state with `resume()->run()`, and release that exact generation with
`acknowledge($runId)`. Retained completion blocks a new turn until
acknowledged. File persistence is for controlled single-process use; choose
`DatabasePersistence`, `EloquentPersistence`, or `RedisPersistence` for
multi-process coordination. Redis requires persistence and eviction settings that
preserve active and retained runs. Reconstruct the same history, providers, and
tools on continuation.

## Key Decisions

When helping users build agents:

1. **Choose execution mode** based on requirements:
   - `chat()` for standard conversations
   - `stream()` for real-time streaming
   - `structured()` for data extraction

2. **Add tools** when the agent needs to:
   - Access external systems (databases, APIs)
   - Perform calculations
   - Search the web
   - Send emails

3. **Configure chat history** when:
   - Long-running conversations need persistence
   - Multiple sessions should share history
   - Conversation context needs to be shared across agents

4. **Use middleware** to edit the working request, such as summarization or tool selection. Target `InferenceNode::class` to cover both chat/stream and structured inference; matching is subclass-aware. `AgentMiddleware` offers typed hooks for nodes implementing `AgentNodeInterface`, with the segment's provider, history, instructions and tools in `AgentResources`. Register a middleware that adds tools, such as `ToolSearchMiddleware`, with `addGlobalMiddleware()`, so a continuation that starts at the tool node finds the tools the model was offered. Ordinary custom `Node`s and the boundary nodes need `WorkflowMiddleware` unless they implement that interface. Middleware `after()` returns `void` and cannot replace the routing event.

5. **Use workflow nodes** for I/O and flow control: speech providers, output processing, and interruptions. Tool approval already lives in `ToolNode`. Prefer `entryNodes()` / `exitNodes()` for boundary extensions and **neuron-workflow** for bespoke graphs.

6. **Control tool execution** with `toolMaxRuns($num)` (default 10 per tool across a run, including resumes), or per-tool limits. Escaped tool exceptions fail the run unless a configured `toolErrorHandler()` returns a conversational result; returned `ToolOutput::error()` lets the model handle an expected failure. See **neuron-tool** for input casting, errors, and deferred tools.

## Related Skills

- Use the **neuron-laravel-integration** or **neuron-symfony-integration** skill to wire
  agents into a Laravel or Symfony app: container, tables, controllers, jobs, tests.
- Use the **neuron-evaluation** skill to test agents with dataset-driven
  evaluations, assertions, AI judges, and multi-turn conversation testing.
- Use the **neuron-monitoring** skill to debug and monitor agents with the
  observability event system, from local logging to production tracing on
  Neuron Cloud.

Files in this skill

  • SKILL.md28.8 KB
  • references/conversation-memory.md5.6 KB
  • references/providers.md9 KB
  • references/workflow-extension.md6.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.