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) - 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 - 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_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).
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).