Concepts
MarkdownAI assistant model
The in-app assistant is App\Ai\Agents\AppAssistant: a conversational, tool-using agent (laravel/ai) whose tool list is filtered by the user’s effective permissions. Chat history, attachments, imports, and sync sources sit around that agent.
Related
Product guides: AI chat, AI imports & sync. Config: AI configuration. HTTP: AI API & webhooks.
AppAssistant
| Item | Detail |
|---|---|
| Class | App\Ai\Agents\AppAssistant |
| Path | app/Ai/Agents/AppAssistant.php |
| Contracts | Agent, Conversational, HasSkills, HasTools |
| Provider attribute | #[Provider('local')] |
| Traits | Promptable, RemembersConversations |
| Steps | maxSteps() = count(FieldTypeEnum::cases()) + 16 |
| Timeout | 300 seconds |
| Entry | AiChatController → new AppAssistant($user) |
| Route gate | permission:can-use-ai on routes/ai.php |
From externa-core 1.4.0, AppAssistant::tools() returns AiToolRegistryService::chatToolsFor($user): the shared registry that also backs the MCP server (#202). A tool is listed only when the user holds the required permission(s) via EffectivePermissionResolverService and it passes the config gates (AI_TOOLS_*, write gate). Each tool then re-checks action-level permissions through ChecksAiPermissions::requirePermission().
Write tools are off by default (1.4.0)
With AI_WRITE_TOOLS=false (default), chat keeps the read actions of the Manage* tools (list, get, search, …) and refuses their write and destructive actions. Pure write tools (ImportCollectionCsv, ImportRemoteJson) and RollbackLastAiTurn are not offered. Enable them per tool class and approve each write in the chat — AI tool approvals & audit.
Language policy
- The assistant replies in the same language as the user’s latest message. If it cannot confidently use that language, it uses English. It must never assume Italian.
- Tool errors, fallback turn summaries, and AI HTTP messages are English machine strings; the model translates them for the user.
- Prompt chips / AI UI chrome in
resources/jsship English literals (admin UI i18n elsewhere is unchanged).
Field-type knowledge
| Piece | Detail |
|---|---|
| Cookbook | App\Ai\Support\FieldTypeCookbook — curated purpose / required keys / settings_json examples / nested rules (aligned with Field types + BlocksFieldSchema) |
| Tool | DescribeFieldTypes — input types = list, all, or complex |
| Gate | Registered with collection tools; needs show/create/edit collections |
| Prompt rule | Before create_field / update_field on complex types, MUST call DescribeFieldTypes; treat ManageCollections get as source of truth for existing schema |
| Validation | create_field / update_field run CollectionFieldSettingsPipeline (same normalize + rules as HTTP Store/Update field) |
| MCP | describe_collection on the MCP server exposes the same schema to external agents; the cookbook stays chat-only |
Permission-gated tools
Registration (tool available in the agent)
Permission needed for the tool to be listed (before the write gate):
| Tool class | Registered when the user has |
|---|---|
ManageCollections | Any of: show / create / edit / delete / restore / force-delete collections |
ManageCollectionItems | Same collection permission set |
DescribeFieldTypes | Same collection permission set (action also accepts show or create or edit) |
QueryCollectionItems | can-show-collections |
ExportCollection | can-show-collections |
SearchSimilarCollectionItems | can-show-collections |
ImportCollectionCsv | can-create-collections |
ImportRemoteJson | can-create-collections |
ExtractPdfText | can-create-collections |
GetImportJobStatus | can-create-collections |
ManageAiSyncSources | can-create-collections |
RollbackLastAiTurn | can-delete-collections |
ManageFiles | Any of: show / create / edit / delete / restore / force-delete files |
ManageUsers | Any of: show / create / edit / delete / restore / force-delete users |
ManageRoles | Any of: show / create / edit / delete roles |
ManageGroups | Any of: show / create / edit / delete / restore / force-delete groups |
QueryActivityLogs | can-show-activity-logs |
Paths: app/Ai/Tools/{ToolName}.php.
Action-level gates (high level)
ManageCollections
| Actions | Permission |
|---|---|
list, get, list_field_packs | can-show-collections |
create, duplicate | can-create-collections |
update, create_field, update_field, delete_field, apply_field_pack | can-edit-collections |
delete | can-delete-collections |
restore | can-restore-collections |
force_delete | can-force-delete-collections |
Collection packs: for SEO entity / Articles / Pages / Products / Categories, the assistant MUST call apply_collection_pack (pack, e.g. seo or articles). Dependencies auto-create. UI: Collections Create from pack…. Presets: “Apply SEO collection”, “Scaffold Articles”, “Scaffold Products”.
Field packs: for denormalized SEO (seo_inline) or publishing/contact/social on an existing collection, MUST call apply_field_pack (collection_id + pack). Use seo_inline only when the user wants inline seo_* fields. UI: Fields page Add field pack…. Registry: Field types — Field packs.
ManageCollectionItems
| Actions | Permission |
|---|---|
list, get, list_relation_options | can-show-collections |
create | can-create-collections |
update, bulk_update, publish | can-edit-collections |
delete, bulk_delete | can-delete-collections |
restore | can-restore-collections |
force_delete | can-force-delete-collections |
Single-permission tools
| Tool | Permission |
|---|---|
QueryCollectionItems | can-show-collections |
ExportCollection | can-show-collections |
SearchSimilarCollectionItems | can-show-collections |
ImportCollectionCsv | can-create-collections |
ImportRemoteJson | can-create-collections |
ExtractPdfText | can-create-collections |
GetImportJobStatus | can-create-collections |
RollbackLastAiTurn | can-delete-collections |
QueryActivityLogs | can-show-activity-logs |
ManageAiSyncSources
| Actions | Permission |
|---|---|
list, create | can-create-collections |
update, enable, disable, delete | can-edit-collections |
ManageFiles
| Actions | Permission |
|---|---|
list, search | can-show-files |
create / create_folder, save_attachment | can-create-files |
rename, move, move_many, tag | can-edit-files |
delete | can-delete-files |
restore | can-restore-files |
force_delete | can-force-delete-files |
ManageUsers / ManageGroups — show / create / edit / delete / restore / force-delete mapped to the matching can-*-users or can-*-groups permissions.
ManageRoles — list/get → can-show-roles; create / duplicate → can-create-roles (+ show for duplicate); update / delete → edit/delete roles; list_permissions → can-show-permissions or create/edit roles. Duplicate clones Spatie permissions and collection/file matrices into a new named role (no user/group memberships). Cannot duplicate super-admin or locked system roles (same as the UI).
Same gates as HTTP
Collections HTTP routes use Spatie permission:can-*-collections (and FormRequest checks on bulk). The assistant re-checks the same permission names per tool action, plus CollectionPermissionEnforcer for item ACL / item_filter. See Effective permissions · Collections API.
Conversations
Configured under config/ai.php → ai.conversations:
| Key | Value |
|---|---|
| Tables | agent_conversations, agent_conversation_messages |
generate_title | false (title derived from the first prompt without an extra LM call) |
Controllers: AiConversationController, AiPageController. Routes cover list/create/update/destroy, pin, truncate, bulk-destroy, and deep-link GET /ai/{conversation}.
Since 1.4.0 each assistant message keeps what the stream showed, so a reload loses nothing (core #572):
Column on agent_conversation_messages | Content |
|---|---|
steps | The steps in order: reasoning, text, tool calls (arguments, result, error, denial) and provider tools |
usage | Input, output, reasoning and cache tokens |
meta | Provider, model, the cited sources (citations) and the error, if any |
status | How the turn ended |
AiConversationMessagePresenterService turns a stored message into the payload the Assistant page and the conversation API send, with each tool call's final state. What the user sees: AI chat — What an answer shows.
Attachments
| Piece | Detail |
|---|---|
| Model | App\Models\AiChatAttachment |
| Disk | local (AiChatAttachment::DISK) |
| Max size | 5 MiB |
| TTL | 24 hours |
| Extensions | csv, txt, xlsx, pdf |
| Upload route | POST /ai/attachments (throttle 30/min) |
| Chat usage | Up to 5 attachment_ids on POST /ai/chat |
Items, files, and saved prompts can also be attached as conversation context (1.4.0) — AI chat — Context attachments. | Cleanup | ai:cleanup-attachments (daily schedule) |
Daily prompt limit
| Config | Env | Default |
|---|---|---|
ai.daily_prompt_limit | AI_DAILY_PROMPT_LIMIT | 0 (disabled) |
When the limit is greater than 0, AiChatController counts today’s Spatie activity rows (log_name = ai, event = ai_prompt, causer = user). At or above the limit the chat endpoint returns 429 with message Daily AI prompt limit reached.
Embeddings and similarity search
| Config | Env | Default |
|---|---|---|
ai.embeddings.enabled | AI_EMBEDDINGS_ENABLED | false |
SearchSimilarCollectionItems (can-show-collections) respects the flag:
| Mode | When | Response mode |
|---|---|---|
| Embeddings | Flag true, query embedding succeeds, and stored CollectionItemEmbedding rows exist for the collection | "embeddings" |
| Semantic-lite | Flag false, provider failure, or no stored vectors yet | "semantic-lite" |
Embeddings path: generate a query vector via Laravel AI Embeddings, score stored vectors (cosine over JSON float arrays; pgvector later), return scored items. Item saves queue GenerateCollectionItemEmbeddingJob. If the collection has zero stored embeddings, the tool dispatches generation for up to 50 recent items and falls back this request.
Semantic-lite path: load up to 500 recent items (permission-filtered), case-insensitive str_contains over lowercased JSON of assembled field values.
Tool description text also switches when the flag is on. Config details: AI configuration — Embeddings.
Agent Skills
From externa-core 1.4.0 (#181), AppAssistant implements laravel/ai HasSkills. Playbooks live under resources/skills/*/SKILL.md and the model loads one on demand (LoadSkill) instead of carrying every rule in the system prompt.
| Skill | Used when |
|---|---|
collection-operations | Creating, updating, querying, packing, exporting, or deleting collections, fields, or items |
collection-import | CSV/XLSX attachments, remote JSON/API URLs, PDF extraction, queued import status, scheduled sync sources |
approvals | A write or destructive action needs confirmation, or an approval was rejected or abandoned |
Skills only add instructions. They do not grant tools or permissions; the registry and write gate still decide what can run. The playbook texts are marked as drafts for maintainer review in core.
MCP server
ai.mcp.enabled (AI_MCP_ENABLED, default false) turns on a real MCP endpoint at /mcp (externa-core 1.4.0) that reuses this registry for external AI clients, with owned API keys and mcp:read / mcp:write scopes. See MCP server.
Surrounding jobs and schedule
| Piece | Role |
|---|---|
ImportCollectionJob | CSV / Excel / remote JSON imports; status in cache |
ai:run-sync-sources | Every minute — due AiSyncSource rows enqueue imports |
ai:cleanup-attachments | Daily — purge expired chat attachments |
| Webhook | POST /ai/webhooks/collection-import (HMAC-signed, CSRF-excepted) |