AI Tools — Developer Guide
How EloSync registers permission-aware tools for EloSyncBusinessAgent and how to add a new one.
Registry
App\AI\Tools\AIToolRegistry maps tool names to AiToolDefinition classes. Defaults are registered in registerDefaults():
- Workspace:
search_workspace(cross-module;module()isnull; each provider enforces its own entitlement +*.view) - Product help:
search_product_help(platform;module()isnull; curated Settings → Action recipes filtered by entitled modules; soft-flags missing permissions) - Leads:
search_leads,get_lead,get_stale_leads,get_recent_lead_activity,update_lead_status,assign_lead,add_lead_note - Tasks:
search_tasks,get_my_tasks,get_overdue_tasks,get_tasks_due_today,get_task - Projects:
search_projects,get_project,get_overdue_projects,update_project_status,assign_project,add_project_note - Opportunities:
search_opportunities,get_pipeline_summary,get_opportunity_stages,get_opportunity - Invoices:
get_overdue_invoices,get_invoice_balance_summary,get_invoice - Estimates:
get_estimate,update_estimate_status(visible withestimates.updateorsendoraccept),assign_estimate,add_estimate_note - Quotations:
get_quotation,update_quotation_status(visible withquotations.updateorsendoraccept),assign_quotation,add_quotation_note - Contracts:
get_contract,update_contract_status(visible withcontracts.updateorsendoraccept),assign_contract,add_contract_note - Contacts:
get_contact,update_contact_status(lifecycleon_boarded/off_boarded),assign_contact,add_contact_note - Companies:
get_company,assign_company,add_company_note(no lifecycle status on companies) - Vendors:
get_vendor,assign_vendor,add_vendor_note(status exposed on get; Active/Inactive writes stay on the Vendors UI/API) - Products / Inventory:
get_product(stock levels wheninventory.view),add_product_note,get_low_stock,get_inventory_stats - Payments:
get_payment,update_payment_status(visible withpayments.updateorpostorvoid; confirm usespost()/void()),assign_payment,add_payment_note - Credit notes:
get_credit_note,update_credit_note_status(visible withcredit-notes.updateorissueorapplyorvoidorrefund; confirm usesissue()/apply()/void()/refund()),assign_credit_note,add_credit_note_note - Purchase orders:
get_purchase_order,update_purchase_order_status(visible withpurchase-orders.updateorsendorreceiveorcancel),assign_purchase_order,add_purchase_order_note - Expenses:
get_expense_pending_approval,get_expense - Leave Management:
get_leave_request,get_pending_leave_requests,approve_leave_request,reject_leave_request(writes requireleave-management.approve; no assign/note tools) - Writes:
create_task,update_task_status(visible withtasks.updateortasks.complete),assign_task,add_task_note,update_lead_status,assign_lead,add_lead_note,log_activity,update_help_desk_ticket_status,assign_help_desk_ticket,add_help_desk_ticket_note,update_opportunity_stage,assign_opportunity,add_opportunity_note,update_invoice_status(visible withinvoices.updateorinvoices.sendorinvoices.void),assign_invoice,add_invoice_note,update_estimate_status(visible withestimates.updateorsendoraccept),assign_estimate,add_estimate_note,update_quotation_status(visible withquotations.updateorsendoraccept),assign_quotation,add_quotation_note,update_contract_status(visible withcontracts.updateorsendoraccept),assign_contract,add_contract_note,update_contact_status,assign_contact,add_contact_note,assign_company,add_company_note,assign_vendor,add_vendor_note,add_product_note,update_payment_status,assign_payment,add_payment_note,update_credit_note_status,assign_credit_note,add_credit_note_note,update_purchase_order_status,assign_purchase_order,add_purchase_order_note,update_project_status,assign_project,add_project_note,update_expense_status(visible withexpenses.updateorsubmitorapproveorrejectorpayorcancel),assign_expense,add_expense_note,approve_leave_request,reject_leave_request(confirmation required) - Reads:
get_help_desk_open_tickets,get_help_desk_ticket(module + permission gated)
List/detail tool payloads that include an assignee expose numeric assigned_to (user id) and assignee_name (display name).
search_workspace
App\AI\Tools\Search\AiWorkspaceSearchService fans out to entitled providers under app/AI/Tools/Search/Providers/ (Wave A+B+C): leads, tasks, projects, opportunities, contacts, companies, invoices, help-desk, estimates, payments, credit-notes, vendors, purchase-orders, expenses, employees, products, documents, knowledge-base, activities, meetings.
Arguments: query (required), optional modules (slug filter), limit_per_module (default 5, max 10), limit_total (default 25, max 50). Hits include module, id, uuid, title, subtitle, path. Provider exceptions are isolated (modules_failed); other modules still return hits.
List/detail tool payloads include both numeric id (for SPA deep links) and uuid (for tool lookups).
search_product_help
Always-on (with catalog mode) read-only tool for EloSync product navigation. Backed by App\AI\ProductHelp\ProductHelpCatalog + ProductHelpSearchService under app/AI/ProductHelp/.
Arguments: query (required), optional limit (default 5, max 10). Hits include id, title, path (breadcrumb array), path_label, action, deep_link, user_can_perform, missing_permissions, and optional notes. Recipes that require an uninstalled module are omitted; recipes the user cannot perform still return with user_can_perform: false.
When catalog mode is enabled (config/ai-tools.php), always-on tools are search_workspace, search_product_help, and get_ai_credit_status (plus meta search_ai_tools / execute_ai_tool).
availableFor($user, $tenant, $entitlements) filters tools when:
- Risk is not
Destructive. - Declared module slug is entitled (
module:{slug}) — skipped whenmodule()isnull. - Permission gate:
- Default: user has every permission listed on
permissions()— skipped when the list is empty. - Tools implementing
AiToolAnyOfPermissions: user has at least one ofanyOfPermissions()(e.g.update_task_status→tasks.updateortasks.complete).
- Default: user has every permission listed on
Tool definition contract
Implement App\AI\Tools\Contracts\AiToolDefinition:
| Method | Purpose |
|---|---|
name() | Stable snake_case identifier exposed to the model |
description() | Natural-language capability summary |
module() | Required marketplace slug (leads, tasks, …) or null |
permissions() | Spatie permission names (all required) |
risk() | ReadOnly, LowRiskWrite, or Destructive (destructive tools are never registered) |
requiresConfirmation() | When true, handler returns a pending action instead of mutating data |
schema() | JSON-schema-like argument map for the adapter |
handle(AiToolContext $ctx, array $args) | Execute and return serializable array |
Adapter
LaravelToolAdapter implements Laravel\Ai\Contracts\Tool:
- Builds JSON Schema properties from
schema(). - Re-checks permissions before
handle(). - JSON-encodes the handler result for the agent runtime.
Adding a tool (checklist)
- Create
app/AI/Tools/Definitions/YourTool.phpimplementingAiToolDefinition. - Declare module + permissions matching the domain API you mirror.
- Register the class in
AIToolRegistry::registerDefaults(). - Write actions that mutate data:
- Set
requiresConfirmation(): trueand returnpending_confirmationviaPendingAiActionService, or - Keep read-only and return DTO arrays only.
- Set
- Confirm path — add a
matcharm inPendingAiActionService::confirm()when introducing a new write tool (create_task, Task status/assign/note,update_lead_status, Lead assign/note,log_activity, Help Desk status/assign/note, Opportunity stage/assign/note, Invoice status/assign/note, Estimate status/assign/note, Quotation status/assign/note, Payment status/assign/note, Credit Note status/assign/note, Purchase Order status/assign/note, Project status/assign/note, Expense status/assign/note, Leave approve/reject). - Tests — extend
tests/Feature/Tenant/Ai/AiAuthorizationTest.php(permissions) and write confirmation tests when applicable. - Docs — update Tenant AI API tool list and user guide if user-visible.
Example skeleton
final class GetExampleTool implements AiToolDefinition
{
public function name(): string
{
return 'get_example';
}
public function module(): ?string
{
return 'leads';
}
public function permissions(): array
{
return ['leads.view'];
}
public function risk(): AiToolRiskEnum
{
return AiToolRiskEnum::ReadOnly;
}
public function requiresConfirmation(): bool
{
return false;
}
public function handle(AiToolContext $ctx, array $args): array
{
Gate::authorize('leads.view');
// … query tenant-scoped models …
return ['example' => []];
}
}Testing
- Feature tests live under
tests/Feature/Tenant/Ai/. - Use
installAiModule($tenant)andconfigurePlatformAi()helpers fromtests/Helpers.php. - For agent integration tests, prefer
EloSyncBusinessAgent::fake([...])(laravel/ai) to avoid live provider calls.
Platform freeze notes
- Do not bypass
AIGatewaywith parallel chat stacks. - Do not expose tools without module + permission gates.
- Keep workspace timezone conventions when returning scheduling fields (see tenant settings).