Custom Tools
Create a class that extends Tackle\Tools\AbstractTool. In Tackle 1.56.8 and later, register it with the tackle.tools container tag. You can also extend DefaultCodingAgent and rebind its contract as shown below.
Step 1 — Generate the tool (or write it manually)
php artisan tackle:tool ReadDatabaseStep 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 MyCodingAgentStep 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
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 arrayWhen 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.
Generators and stubs
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 --fullTo customise the generated stubs, publish them first:
php artisan vendor:publish --tag="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.
Register tools from a package
Since Tackle 1.56.8, a package or application service provider can register tools without replacing the default coding agent:
public function register(): void
{
$this->app->tag([
\App\Ai\Tools\ReadDatabase::class,
], 'tackle.tools');
}The default coding agent resolves tagged tools through Laravel's container, applies the tackle.tools configuration allowlist, and wraps them with its normal shield and tool-event handling. The container tag and configuration key have the same name: the tag contributes tools; the configuration key restricts the available tool names.
Custom agents that replace tools() must include package tools explicitly or call parent::tools(). Lean agents retain their narrower tool list. MCP tools are separately configured in tackle.mcp.tools.
Tackle Grokbot uses this extension point to register its discovery, sending, and delivery-history tools.