Packstub.

Configuration

php artisan packstub-agents:install publishes config/packstub-agents.php. Most values can also be set fluently on AgentsPlugin in the panel provider; the plugin mirrors those into the config so every runtime (queue, console, MCP requests) sees one truth.

config/packstub-agents.php

Key Default Env What it does
name Assistant AGENT_NAME how the assistant introduces itself; AgentsPlugin::name() overrides it
panel null the panel the assistant lives in; set by the plugin when it registers
provider anthropic AGENT_PROVIDER anthropic or openai (any laravel/ai text provider); the platform default, a workspace may bring its own
enabled null AGENT_ENABLED null = enabled when a key exists for the provider; false hides the chat
models see below AGENT_MODEL, AGENT_MODEL_FAST, AGENT_MODEL_DEEP the picker entries per provider: label, model, effort
max_steps 12 tool round-trips one turn may take before the agent has to answer
max_tokens 4096 answer length
max_conversation_messages 40 how many earlier messages a long chat replays
limits.* see Budgets and limits AGENT_TURNS_PER_MINUTE the platform ceiling
limits_connection null AGENT_LIMITS_CONNECTION the connection of the agent_limits table (the central one in a database-per-tenant app)
mcp.enabled true AGENT_MCP_ENABLED the MCP endpoint and the Agent access page
mcp.path mcp the endpoint path; mcp/{tenant} in a panel with tenancy
mcp.server null an AgentServer subclass; null = the package's server with the tools registered on the plugin
mcp.middleware ['throttle:60,1', 'auth:sanctum', AuthenticateAgent::class] the endpoint's middleware
run_migrations true run the package migrations from the vendor directory; false to publish and split them

The provider keys themselves live in laravel/ai's config/ai.php (ANTHROPIC_API_KEY, OPENAI_API_KEY).

models

'models' => [
    'anthropic' => [
        'auto' => ['label' => 'Auto', 'model' => env('AGENT_MODEL', 'claude-opus-5'), 'effort' => 'medium'],
        'fast' => ['label' => 'Fast', 'model' => env('AGENT_MODEL_FAST', 'claude-haiku-4-5'), 'effort' => null],
        'deep' => ['label' => 'Deep', 'model' => env('AGENT_MODEL_DEEP', 'claude-opus-5'), 'effort' => 'xhigh'],
    ],
    'openai' => [
        'auto' => ['label' => 'Auto', 'model' => env('AGENT_MODEL'), 'effort' => 'medium'],
        'fast' => ['label' => 'Fast', 'model' => env('AGENT_MODEL_FAST'), 'effort' => 'low'],
        'deep' => ['label' => 'Deep', 'model' => env('AGENT_MODEL_DEEP'), 'effort' => 'high'],
    ],
],

Rename, remove or add entries; the picker shows whatever is there. A null model resolves to the provider's smartest model (or cheapest for the fast key).

AgentsPlugin

use Packstub\Agents\AgentsPlugin;

AgentsPlugin::make()
    ->name('Ask Acme')
    ->agent(Assistant::class)
    ->server(AcmeServer::class)
    ->tools([SearchOrders::class, ShowTable::class])
    ->resources([OrderResource::class, CustomerResource::class])
    ->authorizeUsing(fn (string $ability): bool => auth()->user()->can($ability))
    ->roleLabelUsing(fn (): ?string => auth()->user()->role?->getLabel())
    ->credentialsUsing(fn (): ?WorkspaceCredentials => ...)
    ->chat(true)
    ->agentAccess(enabled: true, ability: 'setup.view', group: 'Setup')
    ->limits(enabled: true, authorize: fn (): bool => auth()->user()->is_admin)
    ->hideAskButtonOn(['*.pages.dashboard']);
Method
name(string) how the assistant is called in the panel
agent(class) your Agent subclass (default: the package's DefaultAgent)
server(class) the AgentServer subclass with the tool list, name and instructions
tools(array) the tool list when there is no server class
resources(array) explicit AgentResource classes for show-table and page context (default: every panel resource implementing the contract)
authorizeUsing(fn (string $ability): bool) how a tool's ability is checked for the current person (default: the Gate when it has that ability, otherwise allowed)
roleLabelUsing(fn (): ?string) the person's role label for the prompt and refusals
credentialsUsing(fn (): ?WorkspaceCredentials) where a workspace's own provider, key and model come from
chat(bool) the Chat and Chats pages, the topbar button and the sidebar block
agentAccess(bool $enabled, ?string $ability, Closure|string|null $group) the token page, its gate and navigation group
limits(bool $enabled, ?Closure $authorize) the operator's AI limits resource and who may edit it (default: any signed-in user of the panel)
hideAskButtonOn(array $routePatterns) route name patterns without the topbar button (the chat itself is always excluded)

Two panels may register the plugin: the tenant panel with the chat and the token page, the operator panel with chat(false)->agentAccess(false)->limits().

The Agents facade

Packstub\Agents\Facades\Agents reads back what the app told the package: name(), panel(), tenant(), toolClasses(), resourceClasses(), allows($ability), roleLabel(), credentials(), canManageLimits(). Tools and views use it; your own code may too.

Translations and views

Strings are __() calls keyed by the English text, with JSON files for German, Spanish, Romanian and Russian in resources/lang. Add your own language by publishing a JSON file with the same keys into your app's lang/ directory. Views are published with vendor:publish --tag=packstub-agents-views and live under the packstub-agents:: namespace.