PHPackages                             jordandalton/laravel-tackle - 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. jordandalton/laravel-tackle

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

jordandalton/laravel-tackle
===========================

An interactive, terminal-based AI coding assistant for Laravel — a Claude Code native to your Laravel app.

v0.3.0(today)10MITPHPPHP ^8.3

Since Jun 13Pushed todayCompare

[ Source](https://github.com/JordanDalton/laravel-tackle)[ Packagist](https://packagist.org/packages/jordandalton/laravel-tackle)[ RSS](/packages/jordandalton-laravel-tackle/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (7)Versions (8)Used By (0)

Laravel Tackle
==============

[](#laravel-tackle)

**An AI agent harness for Laravel.**

Tackle is the runtime layer that lets AI agents operate inside your Laravel application — reading code, executing tools, running tests, and taking action, with safety boundaries enforced at the framework level.

Think of it the way you think of Claude Code, Codex, or GitHub Copilot — but purpose-built for Laravel and installed directly into your app via Composer. The harness ships with three built-in agents and a full tool infrastructure you can extend or build on top of:

- **`ai:code`** — an interactive coding agent that reads your codebase, edits files, runs tests, and formats code
- **`ai:review`** — a read-only agent that reviews git diffs and surfaces real issues with severity levels
- **`ai:explain`** — explains what a file, class, or method does in plain English
- **`ai:test`** — generates a Pest test file for any class or method
- **Self-healer** — an autonomous agent that listens for failed jobs and scheduled tasks, diagnoses the exception, patches the code, and opens a PR or applies the fix — without you lifting a finger

Every agent runs through the same tool infrastructure and safety layer. You can add your own tools, write new agents, and swap the default agent entirely — all without forking the package.

Built on top of [`laravel/ai`](https://github.com/laravel/ai).

---

Contents
--------

[](#contents)

- [Before you start](#before-you-start)
- [Requirements](#requirements)
- [Installation](#installation)
- [Environment variables](#environment-variables)
- [Usage](#usage)
- [Tips for better results](#tips-for-better-results)
- [Session memory](#session-memory)
- [Configuration](#configuration)
- [Built-in tools](#built-in-tools)
- [Self-healing queue workers](#self-healing-queue-workers)
    - [Scheduled command healing](#scheduled-command-healing)
    - [Per-class opt-out](#per-class-opt-out)
    - [Audit log](#audit-log)
- [Code review](#code-review)
- [Explain code](#explain-code)
- [Generate tests](#generate-tests)
- [Health check](#health-check)
- [Replay a healing attempt](#replay-a-healing-attempt)
- [Limitations](#limitations)
- [Customization](#customization)
    - [Adding your own tools](#adding-your-own-tools)
    - [Swapping the agent entirely](#swapping-the-agent-entirely)
    - [Changing the model or provider](#changing-the-model-or-provider)
- [Safety](#safety)
- [Troubleshooting](#troubleshooting)
- [Known risks](#known-risks)
- [Development](#development)

---

How the harness works
---------------------

[](#how-the-harness-works)

Tackle has three layers:

LayerWhat it is**Tools**Action primitives agents can call — `ReadFile`, `EditFile`, `RunTests`, `RunShell`, etc. Every tool goes through `PathGuard` and shell policy before executing.**Agents**Classes implementing `CodingAgent` that receive a prompt, call tools, and return a result. Tackle ships three; you can add your own.**Safety**`PathGuard` blocks reads/writes outside the workspace and protected paths. `BudgetTracker` aborts the session when estimated spend exceeds the limit. Shell modes gate command execution. All enforced in PHP — not advisory.The self-healer adds a fourth piece: an event-driven runtime that spins up an agent autonomously in an isolated git worktree whenever a job or scheduled task fails. It is the same harness, running unattended.

---

Before you start
----------------

[](#before-you-start)

Run through this checklist once before your first session:

- Commit or stash any in-progress work — the agent will modify files, and a clean git state is your undo button.
- Set `ANTHROPIC_API_KEY` in `.env` (or the key for your chosen provider).
- Publish the `laravel/ai` config: `php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"`
- Publish the Tackle config: `php artisan vendor:publish --tag="laravel-tackle-config"`
- Run `php artisan ai:code` and type a small test task to confirm everything connects.

---

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

[](#requirements)

- PHP ^8.3
- Laravel ^12.0
- [`laravel/ai`](https://github.com/laravel/ai) ^0.1 (pinned — fast-moving package, see [Known Risks](#known-risks))

---

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

[](#installation)

```
composer require jordandalton/laravel-tackle
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan vendor:publish --tag="laravel-tackle-config"
```

The service provider and `ai:code` command register automatically via Laravel package auto-discovery.

### API key setup

[](#api-key-setup)

Tackle uses Anthropic (Claude) by default. Add your key to `.env`:

```
ANTHROPIC_API_KEY=sk-ant-...
```

The `config/ai.php` published above already includes the `anthropic` provider block — just add the env var and you're ready.

---

Environment variables
---------------------

[](#environment-variables)

All config options can be set via `.env`. Nothing requires editing a PHP file.

VariableDefaultDescription`ANTHROPIC_API_KEY`—Your Anthropic API key (required for the default provider)`AI_CODE_PROVIDER``anthropic`Provider name — must match a key in `config/ai.php``AI_CODE_MODEL``claude-sonnet-4-6`Model to use`AI_CODE_MAX_STEPS``40`Max tool-call cycles per agent turn`AI_CODE_BUDGET``1.00`Hard spend limit in USD per session`AI_CODE_SHELL``approve`Shell mode: `off` | `allowlist` | `approve` | `yolo``AI_CODE_MEMORY``file`Session persistence: `none` | `file` | `database``AI_CODE_HEALING_ENABLED``false`Enable the self-healing queue worker feature`AI_CODE_HEALING_MODE``pr`Healing mode: `pr` | `patch``AI_CODE_HEALING_QUEUE``healer`Queue name for the `HealJobFailure` job`AI_CODE_HEALING_THRESHOLD``1`Failures before healing is triggered`AI_CODE_HEALING_BASE_BRANCH``main`Base branch for fix pull requests`GITHUB_TOKEN`—GitHub token for opening pull requests (pr mode)---

Usage
-----

[](#usage)

```
php artisan ai:code
```

Type a task at the prompt. The agent maintains full conversation history within a session, so you can follow up, ask questions, and give corrections naturally.

Type `exit` or `quit` to end the session.

### Shell mode flag

[](#shell-mode-flag)

Pass `--shell` to override the configured shell mode for a single session without touching your config or `.env`:

```
# Safe read-only exploration — no commands will run at all
php artisan ai:code --shell=off
php artisan ai:code --off          # shorthand

# Require your approval before every shell command (config default)
php artisan ai:code --shell=approve
php artisan ai:code --approve      # shorthand

# Only allow commands from shell_allowlist, no prompt
php artisan ai:code --shell=allowlist
php artisan ai:code --allowlist    # shorthand

# No restrictions, no prompts — CI or fully-trusted environments only
php artisan ai:code --shell=yolo
php artisan ai:code --yolo         # shorthand
```

The flag is session-scoped and does not persist to config.

### Example session

[](#example-session)

```
┌ What should I work on? ──────────────────────────────────────┐
│ Add a slug field to the Post model                           │
└──────────────────────────────────────────────────────────────┘

→ searching for Post model
→ reading app/Models/Post.php
→ creating database/migrations/2024_01_01_add_slug_to_posts.php
→ editing app/Models/Post.php
→ running tests

All done. Migration created, $fillable updated, and tests pass.

┌ What should I work on? ──────────────────────────────────────┐
│ Make the slug auto-generate from the title on creation       │
└──────────────────────────────────────────────────────────────┘

```

---

Tips for better results
-----------------------

[](#tips-for-better-results)

**Be specific about what you want.**Vague tasks produce vague results. The more context you give upfront, the less back-and-forth is needed.

Instead of…Try…"Add a feature""Add a `published_at` timestamp to `Post` with a scope for published posts and a migration""Fix the bug""The `UserController@store` is returning a 500 when `email` is null — find out why and fix it""Refactor this""The `OrderService` class is doing too much — extract the payment logic into a `PaymentService`"**Use `--off` for questions and exploration.**If you just want to understand the codebase without making changes, `--off` mode prevents the agent from running any commands, so you can ask freely.

```
php artisan ai:code --off
# "How is authentication handled in this app?"
# "What does the Job queue setup look like?"
```

**Point it at the right place.**If you know which file or module is relevant, say so. "Look at `app/Services/BillingService.php`" is faster than letting it search from scratch.

**Correct it mid-session.**If the agent does something wrong, just say so in the next prompt. It reads your correction in context and adjusts. You don't need to restart the session.

**Keep tasks focused.**One clear task per session works better than a long list. Once a task is done, review the diff, commit it, then start a new session for the next task.

**Review before you move on.**After each task the agent shows a `git diff --stat`. Look at it before typing your next task. If something looks wrong, say so or discard with `git checkout -- .`.

---

Session memory
--------------

[](#session-memory)

The `memory` config controls what happens to conversation history when you exit.

ModeBehaviour`none`History is lost when the session ends. Every `php artisan ai:code` starts fresh.`file`**(Default)** The session transcript is saved to `storage/ai-code/`. Re-running `ai:code` picks up where you left off.`database`Uses `laravel/ai`'s `RemembersConversations`. Requires publishing and running the `laravel/ai` migrations first.### File memory

[](#file-memory)

With the default `file` mode, transcripts are stored as JSON in `storage/ai-code/`. The directory is created automatically. You can delete files there to clear history, or add `storage/ai-code/` to `.gitignore` to keep session history out of your repository.

### Database memory

[](#database-memory)

To use `database` mode, publish and run the `laravel/ai` migrations:

```
php artisan vendor:publish --tag="laravel-ai-migrations"
php artisan migrate
```

Then set `AI_CODE_MEMORY=database` in `.env`.

---

Configuration
-------------

[](#configuration)

After publishing the config, edit `config/ai-code.php`. All values can be set via environment variables — see the [Environment variables](#environment-variables)table above.

```
return [
    // laravel/ai provider name — must match a key in config/ai.php
    'provider' => env('AI_CODE_PROVIDER', 'anthropic'),

    // Model to use
    'model' => env('AI_CODE_MODEL', 'claude-sonnet-4-6'),

    // Maximum agent steps per turn (tool calls + reasoning cycles)
    'max_steps' => env('AI_CODE_MAX_STEPS', 40),

    // Hard spend limit for the session in USD — aborts when exceeded
    'budget_usd' => env('AI_CODE_BUDGET', 1.00),

    // Shell execution policy: off | allowlist | approve | yolo
    'shell'           => env('AI_CODE_SHELL', 'approve'),
    'shell_allowlist' => ['composer', 'npm', 'php artisan'],

    // Artisan commands the agent may run without a confirmation prompt
    'artisan_allowlist' => ['make:*', 'route:list', 'migrate', 'db:seed', 'test'],

    // Glob patterns (relative to workspace) the agent can never read or write
    'protected_paths' => ['.env', '.env.*', 'storage/*', 'vendor/*', '.git/*'],

    // Root directory for the agent — null defaults to base_path()
    'workspace' => null,

    // Session memory: none | file (default) | database
    'memory' => env('AI_CODE_MEMORY', 'file'),
];
```

### Shell modes

[](#shell-modes)

ModeBehaviour`off``RunShell` refuses everything. Use `RunArtisan` / `RunTests` instead.`allowlist`Only commands whose first token matches `shell_allowlist` run unattended.`approve`**Default.** Every command shows a confirmation prompt before running.`yolo`Runs anything, no prompt. **Dangerous — CI or fully-trusted environments only.**### Artisan allowlist

[](#artisan-allowlist)

The `artisan_allowlist` supports glob patterns, so `make:*` covers `make:model`, `make:controller`, etc. Commands not matching any pattern are refused.

### Protected paths

[](#protected-paths)

The `protected_paths` globs prevent the agent from reading or writing sensitive files regardless of what it is asked to do. This is enforced in PHP, not via prompting. Add your own patterns here if your project has additional secrets.

---

Built-in tools
--------------

[](#built-in-tools)

These tools are available to the agent in every session.

**Filesystem**

ToolWhat it does`ReadFile`Reads a file's contents. Always runs through `PathGuard` first.`Glob`Lists files matching a pattern. Protected paths are excluded from results.`SearchCode`Grep-style search returning file + line + snippet. Capped at 50 results.`EditFile``str_replace` edit — `old_str` must appear exactly once or the edit is refused.`WriteFile`Creates a new file. Refuses if the path already exists.**Execution**

ToolWhat it does`RunArtisan`Runs `php artisan ` in a subprocess. Allowlist-gated.`RunTests`Runs Pest or `php artisan test` in a subprocess. Returns full output.`RunPint`Runs Laravel Pint to format files. Called before finishing a task.`RunShell`General shell — governed by the `shell` config mode.**Observability**

ToolWhat it does`ReadLog`Returns the last N lines of `storage/logs/laravel.log`. Accepts an optional filter string.`QueryDatabase`Runs a read-only `SELECT` query and returns results as JSON. Capped at 100 rows.`ListRoutes`Returns a formatted table of all registered routes with method, URI, name, and action.`GitDiff`Shows a git diff — supports staged, a specific commit, a branch range, or a path.`ReadTelescopeEntry`Reads Telescope exception entries. Pass a job UUID for a specific lookup, or omit to return recent exceptions. No-ops gracefully if Telescope is not installed.All file reads happen in-process. Everything that executes code runs as a subprocess, so a broken generated file cannot crash the agent session.

---

Self-healing queue workers
--------------------------

[](#self-healing-queue-workers)

When enabled, Tackle listens for failed queue jobs and failed scheduled commands, dispatches an AI agent to diagnose the exception, patch the code, verify the fix with your test suite, and either open a pull request or apply the fix directly — all without you lifting a finger.

### How it works

[](#how-it-works)

1. A job fails → Laravel fires the `JobFailed` event.
2. Tackle's `JobFailureListener` picks it up and dispatches a `HealJobFailure`job to the `healer` queue (a separate queue from your normal workers).
3. A dedicated queue worker picks up `HealJobFailure`. It:
    - Creates an **isolated git worktree** on a fresh branch (`tackle/heal-{id}`).
    - Spins up a `HealingAgent` pointed at that worktree.
    - Feeds the agent the exception class, message, stack trace, and (if [Telescope](https://laravel.com/docs/telescope) is installed) the full Telescope exception entry.
    - The agent reads the failing code, applies a minimal fix via `EditFile`, and runs your test suite to verify.
4. After the agent finishes:
    - **`pr` mode (default):** pushes the branch to GitHub and opens a pull request with the agent's reasoning as the description.
    - **`patch` mode:** merges the fix back into your main workspace branch and re-dispatches the original job.
5. The worktree is cleaned up regardless of outcome.

### Prerequisites

[](#prerequisites)

- Your project must be a **git repository** with a remote named `origin`.
- A **queue worker** must be running the `healer` queue (see below).
- For PR mode, a **GitHub personal access token** is required.
- For `patch` mode, the working tree must be clean when healing runs.

### Enabling the healer

[](#enabling-the-healer)

Publish and run the migration, then enable via `.env`:

```
php artisan vendor:publish --tag="laravel-tackle-migrations"
php artisan migrate
```

```
AI_CODE_HEALING_ENABLED=true
```

The event listeners register automatically once this is set to `true`.

### Starting the healer worker

[](#starting-the-healer-worker)

The healer runs on a dedicated queue to avoid competing with your normal workers:

```
php artisan queue:work --queue=healer
```

Run this alongside your existing workers. In production (Supervisor, Forge, etc.) add a separate process group for the `healer` queue.

### GitHub token setup

[](#github-token-setup)

For PR mode, Tackle needs a GitHub token with the `repo` scope.

**Resolution order:**

1. `GITHUB_TOKEN` in `.env` (or the `ai-code.healing.github_token` config key)
2. GitHub CLI (`~/.config/gh/hosts.yml`) — if you have `gh` installed and authenticated, Tackle reads your token automatically with no extra config.
3. If no token is found, the branch is pushed but the PR is not opened. A log entry records that you need to configure a token.

```
GITHUB_TOKEN=ghp_...
```

### Configuration

[](#configuration-1)

All healer options live under the `healing` key in `config/ai-code.php`:

OptionEnv varDefaultDescription`enabled``AI_CODE_HEALING_ENABLED``false`Enable or disable the healer`mode``AI_CODE_HEALING_MODE``pr``pr` = open a pull request; `patch` = apply directly`queue``AI_CODE_HEALING_QUEUE``healer`Queue name for the `HealJobFailure` job`threshold``AI_CODE_HEALING_THRESHOLD``1`Number of failures before healing triggers`base_branch``AI_CODE_HEALING_BASE_BRANCH``main`Branch PRs are opened against`branch_prefix``AI_CODE_HEALING_BRANCH_PREFIX``tackle/heal-`Prefix for fix branches`github_token``GITHUB_TOKEN``null`GitHub token for opening PRs`telescope``AI_CODE_HEALING_TELESCOPE``true`Use Telescope context if available### Failure threshold

[](#failure-threshold)

By default (`threshold=1`) the healer triggers on the first failure. If you want the healer to wait until a job has failed a certain number of times before intervening (e.g. to let transient failures resolve themselves), set:

```
AI_CODE_HEALING_THRESHOLD=3
```

### PR mode vs patch mode

[](#pr-mode-vs-patch-mode)

`pr` (default)`patch`Human review requiredYes — merge the PRNo — merged automaticallyTests must passNo (PR opened regardless)Yes (only merges on green)Job re-dispatchedNoYes, after mergeBest forProduction / sensitive codeCI environments / trusted agents### Laravel Telescope integration

[](#laravel-telescope-integration)

If [Laravel Telescope](https://laravel.com/docs/telescope) is installed in your application, Tackle uses it to give the agent richer context: the full exception entry including class, message, and stack frames. No extra configuration is needed — Tackle detects Telescope automatically and degrades gracefully if it is not present.

### Scheduled command healing

[](#scheduled-command-healing)

Tackle also listens to the `ScheduledTaskFailed` event, which Laravel fires when a task registered in `App\Console\Kernel::schedule()` (or a `Schedule` class) throws an exception.

The healing flow is identical to queue jobs — an isolated git worktree, an AI agent, a test run, then a PR or patch. The one difference: scheduled tasks are not re-dispatched after a patch (they run on their own schedule). The fix simply takes effect the next time the task runs.

No extra configuration is needed beyond `AI_CODE_HEALING_ENABLED=true`.

### Per-class opt-out

[](#per-class-opt-out)

Some jobs should never be auto-patched — payment processors, email senders, anything where an untested change would be worse than the failure. Use the `#[Healable(false)]` attribute to opt out:

```
use Tackle\Attributes\Healable;

#[Healable(false)]
class ChargeSubscription implements ShouldQueue
{
    public function handle(): void
    {
        // Tackle will skip this job entirely — even when AI_CODE_HEALING_ENABLED=true.
    }
}
```

The listener checks for the attribute via reflection before dispatching a heal job. Jobs without the attribute, or with `#[Healable(true)]`, are healed normally.

### Audit log

[](#audit-log)

Every healing attempt — successful or not — is written to the `tackle_healing_log` table. View recent entries with:

```
php artisan tackle:healing-log
```

The table output shows when, what failed, whether tests passed, the outcome, and a link to the PR or branch:

```
+-------------+----------------+--------------------+-----------+-------+------------+
| When        | Type           | Subject            | Tests     | Out.  | PR / Branch|
+-------------+----------------+--------------------+-----------+-------+------------+
| 2 mins ago  | job            | BrokenJob          | ✗         | PR    | github.com/|
| 1 hour ago  | scheduled_task | SendWeeklyReport   | ✓         | patched| tackle/... |
+-------------+----------------+--------------------+-----------+-------+------------+

```

**Filters:**

```
# Show only job failures
php artisan tackle:healing-log --type=job

# Show only scheduled task failures
php artisan tackle:healing-log --type=scheduled_task

# Show only successful patches
php artisan tackle:healing-log --outcome=patched

# Show only PR-mode results
php artisan tackle:healing-log --outcome=pr_opened

# Show more entries
php artisan tackle:healing-log --limit=50
```

The audit log requires the migration to have been run:

```
php artisan vendor:publish --tag="laravel-tackle-migrations"
php artisan migrate
```

If the migration has not been run, healing continues normally — the log write degrades gracefully.

### Healer limitations

[](#healer-limitations)

- The healer targets **code bugs** — logic errors the AI can diagnose and fix. It is not designed for infrastructure issues (database down, disk full, etc.).
- The fix branch is pushed to `origin` — your CI pipeline will run on it and can catch anything the local test run missed.
- In `patch` mode, if tests fail the healer falls back to PR mode automatically so nothing is merged without verification.
- The healer never modifies `.env`, `vendor/`, `storage/`, or `.git/` — the same path guards apply as in interactive mode.
- Healer jobs have `$tries = 1`. A failing healer does not create a healing loop.

---

Code review
-----------

[](#code-review)

`php artisan ai:review` feeds your git diff to a read-only AI agent that reads the surrounding codebase for context, then surfaces real issues grouped by file with severity levels.

```
# Review everything since your last commit (staged + unstaged)
php artisan ai:review

# Review only staged changes
php artisan ai:review --staged

# PR-style review — your branch vs. another branch
php artisan ai:review --against=main

# Review a specific commit
php artisan ai:review --commit=abc1234

# Tell the agent what to prioritise
php artisan ai:review --against=main --focus=security,performance
```

### Output format

[](#output-format)

Findings are grouped by file with three severity levels:

LevelMeaning🔴 CriticalBugs that will cause failures, security vulnerabilities, data loss risks🟡 WarningEdge cases, missing error handling, performance concerns, breaking changes🟢 SuggestionImprovements worth considering but not blockingThe review ends with a one-line verdict: **LGTM** / **LGTM with minor notes** / **Needs changes**.

### How it works

[](#how-it-works-1)

The `ReviewAgent` is a read-only agent — it has access to `ReadFile`, `Glob`, and `SearchCode` but no editing tools. Before commenting on any changed function or class it reads the full file for context, so findings are grounded in the actual codebase rather than the diff alone.

### Focus areas

[](#focus-areas)

Pass `--focus` with a comma-separated list to direct the agent's attention:

```
php artisan ai:review --focus=security
php artisan ai:review --focus=performance,tests
php artisan ai:review --staged --focus=bugs,security
```

Any plain-language description works — `security`, `performance`, `n+1 queries`, `missing tests`, `breaking changes`, etc.

---

Explain code
------------

[](#explain-code)

`php artisan ai:explain` reads a file or class and explains what it does in plain English — inputs, outputs, side effects, and any non-obvious behaviour. The agent reads the full file and any closely-related classes before responding.

```
# Explain a whole file
php artisan ai:explain app/Services/BillingService.php

# Focus on a specific method
php artisan ai:explain app/Services/BillingService.php --method=charge
```

---

Generate tests
--------------

[](#generate-tests)

`php artisan ai:test` reads a class, checks your existing test conventions, and writes a Pest test file covering the happy path, edge cases, and error conditions. It runs the tests after writing to confirm they pass.

```
# Generate tests for a class
php artisan ai:test app/Services/BillingService.php

# Focus on a single method
php artisan ai:test app/Services/BillingService.php --method=charge

# Force a feature or unit test
php artisan ai:test app/Http/Controllers/UserController.php --feature
php artisan ai:test app/Services/BillingService.php --unit
```

Test type is inferred from the path when no flag is given — controllers, jobs, commands, listeners, and middleware default to `Feature`; everything else defaults to `Unit`.

---

Health check
------------

[](#health-check)

`php artisan tackle:health` verifies that the package is correctly set up. Run it after installation or when something isn't working as expected.

```
php artisan tackle:health
```

It checks:

- `config/ai-code.php` and `config/ai.php` are published
- An API key is configured for the active provider
- The project is a git repository with at least one commit
- `.env.testing` exists (warns if missing)
- If healing is enabled: migration has been run, GitHub token is available

---

Replay a healing attempt
------------------------

[](#replay-a-healing-attempt)

`php artisan tackle:replay` re-dispatches a previous healing attempt — useful when you want to retry after adjusting config or fixing something manually.

```
# Replay the most recent healing attempt
php artisan tackle:replay

# Replay the last attempt for a specific job class
php artisan tackle:replay --class="App\Jobs\ProcessPayment"

# Replay a specific log entry by ID
php artisan tackle:replay --id=42
```

---

Limitations
-----------

[](#limitations)

Things Tackle cannot do in v1:

- **No internet access.** The agent cannot fetch URLs, read documentation, or call external APIs. It works only with files in your workspace.
- **No binary files.** `ReadFile` reads text. Images, compiled assets, and other binaries are not readable by the agent.
- **No auto-commit or push.** All edits are left unstaged. The agent will never run `git commit` or `git push`.
- **History resets on exit** (unless `memory=file` or `memory=database`). With the default `file` mode, history persists between sessions automatically.
- **Budget is estimated, not exact.** The spend limit is calculated from token counts using approximate per-model pricing. Actual charges from your provider may differ slightly.
- **Tests need a working environment.** `RunTests` runs your actual test suite. If tests require a database or other services, those must be running and configured before starting a session.

---

Customization
-------------

[](#customization)

### Generators

[](#generators)

Tackle ships generator commands so you don't have to look up method signatures:

```
# Scaffold a tool at app/Ai/Tools/MyTool.php
php artisan tackle:tool MyTool

# Scaffold an agent that extends DefaultCodingAgent (most common)
php artisan tackle:agent MyAgent

# Scaffold a bare CodingAgent implementation
php artisan tackle:agent MyAgent --full
```

To customise the generated stubs, publish them first:

```
php artisan vendor:publish --tag="laravel-tackle-stubs"
```

This copies the stubs to `stubs/tackle/` in your project root. Both commands check for published stubs before falling back to the package defaults.

---

### Adding your own tools

[](#adding-your-own-tools)

Create a class that extends `Tackle\Tools\AbstractTool`, then extend `DefaultCodingAgent` to merge it into the tool list, and rebind the contract.

**Step 1 — Generate the tool (or write it manually):**

```
php artisan tackle:tool ReadDatabase
```

**Step 2 — Implement the tool:**

```
// app/Ai/Tools/ReadDatabase.php
namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\DB;
use Laravel\Ai\Tools\Request;
use Tackle\Tools\AbstractTool;

class ReadDatabase extends AbstractTool
{
    public function description(): string
    {
        return 'Run a read-only SQL query and return results as JSON. SELECT only.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'query' => $schema->string()
                ->description('The SELECT query to run.')
                ->required(),
        ];
    }

    public function handle(Request $request): string
    {
        $sql = $request->string('query', '');

        if (! str_starts_with(strtolower(ltrim($sql)), 'select')) {
            return 'Only SELECT queries are allowed.';
        }

        return json_encode(DB::select($sql), JSON_PRETTY_PRINT);
    }
}
```

**Step 3 — Extend the agent (or generate it):**

```
php artisan tackle:agent MyCodingAgent
```

**Step 4 — Wire in your tool:**

```
// app/Ai/MyCodingAgent.php
namespace App\Ai;

use App\Ai\Tools\ReadDatabase;
use Tackle\Agents\DefaultCodingAgent;

class MyCodingAgent extends DefaultCodingAgent
{
    public function __construct(
        private ReadDatabase $readDatabase,
        ...$args,
    ) {
        parent::__construct(...$args);
    }

    public function tools(): iterable
    {
        return [...parent::tools(), $this->readDatabase];
    }
}
```

**Step 5 — Rebind in your service provider:**

```
// app/Providers/AppServiceProvider.php
use App\Ai\MyCodingAgent;
use Tackle\Contracts\CodingAgent;

public function register(): void
{
    $this->app->bind(CodingAgent::class, MyCodingAgent::class);
}
```

The Laravel container resolves all constructor dependencies automatically, so your tool class can type-hint anything it needs (DB connections, services, etc.).

#### Tool contract

[](#tool-contract)

Every tool receives a `Laravel\Ai\Tools\Request` object in `handle()`. It behaves like a read-only request bag:

```
$request->string('key', 'default');   // string value
$request->boolean('key', false);      // boolean value
$request->integer('key', 0);          // integer value
$request->get('key', 'default');      // raw value
$request->all();                      // all arguments as array
```

When a tool should refuse an action, **return a string explaining why** rather than throwing an exception. The agent reads the refusal message and reroutes itself accordingly.

---

### Swapping the agent entirely

[](#swapping-the-agent-entirely)

If you need deeper control — different instructions, a different conversation strategy, or a completely different set of tools — implement the `CodingAgent`contract directly and rebind it:

```
// app/Ai/MyAgent.php
namespace App\Ai;

use Laravel\Ai\Promptable;
use Tackle\Contracts\CodingAgent;

class MyAgent implements CodingAgent
{
    use Promptable;

    public function instructions(): string
    {
        return 'You are a specialist in this project. Only touch the billing module.';
    }

    public function messages(): iterable
    {
        return [];
    }

    public function tools(): iterable
    {
        return [
            // your tools here
        ];
    }
}
```

```
$this->app->bind(\Tackle\Contracts\CodingAgent::class, MyAgent::class);
```

The `CodingAgent` contract extends `Laravel\Ai\Contracts\Agent`, `HasTools`, and `Conversational`, so `laravel/ai`'s full streaming and tool-calling machinery works automatically as long as you use the `Promptable` trait.

---

### Changing the model or provider

[](#changing-the-model-or-provider)

The quickest way is via `.env`:

```
AI_CODE_PROVIDER=openai
AI_CODE_MODEL=gpt-4o
```

The provider name must match a key in `config/ai.php`. Any provider supported by `laravel/ai` (Anthropic, OpenAI, Gemini, Groq, Ollama, etc.) works as long as it supports tool calling.

Internally, Tackle injects provider and model values via two custom Laravel contextual attributes — `#[AiProvider]` and `#[AiModel]` — so any agent you write by extending `DefaultCodingAgent` inherits these config values automatically through the container.

---

Safety
------

[](#safety)

- **Protected paths** — the agent cannot read or write `.env`, `storage/`, `vendor/`, or `.git/` by default. Enforced in `PathGuard`, not via prompting. The model cannot talk its way around this.
- **Unstaged edits** — all file changes are left unstaged. Review with `git diff`; discard with `git checkout -- .`.
- **Budget cap** — the session aborts once estimated spend exceeds `budget_usd`.
- **Subprocess isolation** — `RunArtisan`, `RunTests`, `RunPint`, and `RunShell`all run as child processes. A broken generated file cannot crash the session.
- **Shell is gated** — the default `approve` mode requires your confirmation before any shell command runs. Use `--off` for read-only exploration.

---

Troubleshooting
---------------

[](#troubleshooting)

### `HTTP request returned status code 401`

[](#http-request-returned-status-code-401)

Your API key is missing or incorrect. Check that `ANTHROPIC_API_KEY` (or the key for your chosen provider) is set in `.env` and that `config/ai.php` has been published and contains the matching provider block.

### `Agent error: ...` and the session continues

[](#agent-error--and-the-session-continues)

The agent caught an exception during a turn. The error is shown but the session stays alive — type your next task to continue. If the same error repeats, check the message for clues (auth issues, missing binaries, filesystem permissions).

### `Session aborted: estimated cost exceeds the budget limit`

[](#session-aborted-estimated-cost-exceeds-the-budget-limit)

You've hit the `budget_usd` cap. Increase it in `.env`:

```
AI_CODE_BUDGET=5.00
```

Or pass a higher limit for a single session by editing the config temporarily. The default $1.00 limit is intentionally conservative.

### `ai:code requires an interactive TTY`

[](#aicode-requires-an-interactive-tty)

The command must be run in a real terminal — not piped, not in a CI job, not through a non-interactive shell. This is required because the approval prompts need user input.

### `Path '...' is outside the workspace root`

[](#path--is-outside-the-workspace-root)

The agent tried to access a file outside the configured workspace (defaults to `base_path()`). If you're working in a monorepo or non-standard layout, set `workspace` in `config/ai-code.php` to the correct root path.

### `Path '...' matches protected pattern`

[](#path--matches-protected-pattern)

The agent tried to read or write a protected file (`.env`, `vendor/`, etc.). This is intentional — protected paths are blocked in code, not via prompting. If you need to unblock a path (e.g. you're working on a package inside `vendor/`), remove or narrow the relevant pattern in `protected_paths`.

### `old_str not found` / `old_str appears N times`

[](#old_str-not-found--old_str-appears-n-times)

The agent is trying to edit a file but the string it wants to replace either doesn't exist or appears more than once. This usually means the agent needs to re-read the file to get the current content. Tell it: "read the file again before editing."

### `Pint is not installed`

[](#pint-is-not-installed)

Install Pint as a dev dependency in the host app:

```
composer require laravel/pint --dev
```

### Tests fail during a session

[](#tests-fail-during-a-session)

This is expected behaviour. When `RunTests` returns failures, the agent reads the output and attempts to fix the code. If it gets stuck, tell it what the failure means or paste the relevant stack trace as your next message.

### Healer branch is pushed but no PR is opened

[](#healer-branch-is-pushed-but-no-pr-is-opened)

Tackle could not find a GitHub token. Check the resolution order:

1. `GITHUB_TOKEN` in `.env`
2. GitHub CLI: run `gh auth status` — if it shows "not logged in", run `gh auth login`
3. `ai-code.healing.github_token` in `config/ai-code.php`

### `git worktree add failed`

[](#git-worktree-add-failed)

This means either:

- The project is not inside a git repository. Run `git init && git commit -am "initial"`.
- The branch name already exists. Delete it: `git branch -D tackle/heal-` and retry.

### Healer ran but didn't fix the bug

[](#healer-ran-but-didnt-fix-the-bug)

The agent's fix will be in the PR (or logged). Review the PR description for the agent's reasoning. If the diagnosis was wrong, close the PR and fix it manually — the agent's attempt gives you a starting point and a working branch to build on.

### Healer triggered a loop

[](#healer-triggered-a-loop)

`HealJobFailure` has `$tries = 1`, so a failing healer cannot create a loop with itself. If you see repeated healer jobs it means the original job keeps failing and `threshold` is set to 1. Raise the threshold or disable healing until the root cause is resolved:

```
AI_CODE_HEALING_ENABLED=false
```

---

Known Risks
-----------

[](#known-risks)

> **`laravel/ai` is new and fast-moving.** The version is pinned to `^0.1`. Breaking changes upstream are likely. Check the changelog before upgrading.

> **This tool modifies your codebase and runs commands.** Always run it inside a committed git working tree so you have a clear undo path (`git checkout -- .`).

---

Development
-----------

[](#development)

```
composer install
./vendor/bin/pest        # run tests
./vendor/bin/pint        # format code
```

---

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance100

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

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 ~0 days

Total

7

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1389886?v=4)[Jordan Dalton](/maintainers/jordandalton)[@JordanDalton](https://github.com/JordanDalton)

---

Tags

laravelaiartisancoding-assistant

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/jordandalton-laravel-tackle/health.svg)

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

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.1k2.2k](/packages/unopim-unopim)[markwalet/nova-modal-response

A Laravel Nova asset for Modal responses on an action.

17818.7k](/packages/markwalet-nova-modal-response)[mozex/laravel-scout-bulk-actions

Import, flush, and queue-import all your Laravel Scout searchable models at once. Auto-discovers models, runs in bulk, tracks progress.

1437.7k](/packages/mozex-laravel-scout-bulk-actions)

PHPackages © 2026

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