Skip to content

Database Connections

SqlAgent uses the database.connections map in config/sql-agent.php to determine which databases the agent can query. Each entry maps a logical name to a Laravel database connection. The agent autonomously decides which database to query for each question and can combine results across databases.

  1. The agent receives schema context for all configured databases.
  2. The run_sql and introspect_schema tools have a connection parameter.
  3. The LLM chooses which database to query on each tool call.
  4. For cross-database questions, the LLM runs separate queries and combines results in its response.

No special “query planner” is needed — the LLM’s existing tool-calling loop handles multi-step reasoning naturally.

Add entries to the connections map under the database key in config/sql-agent.php:

'database' => [
'storage_connection' => env('SQL_AGENT_STORAGE_CONNECTION', config('database.default')),
'connections' => [
'crm' => [
'connection' => 'mysql_crm',
'label' => 'CRM Database',
'description' => 'Customers, contacts, deals, and activities.',
],
'analytics' => [
'connection' => 'pgsql_analytics',
'label' => 'Analytics Database',
'description' => 'Page views, events, funnels, and attribution data.',
],
],
],

Each connection accepts these options:

OptionDescriptionRequired
connectionThe Laravel database connection name (from config/database.php)Yes
labelHuman-readable label shown to the LLM and in the UINo (defaults to the key)
descriptionWhat data this database holds — helps the LLM choose the right databaseNo
allowed_tablesWhitelist of tables the agent may access on this connection (empty = all)No
denied_tablesBlacklist of tables the agent may never access on this connectionNo
hidden_columnsColumns to hide per table on this connectionNo

By default, SqlAgent ships with a single default connection that uses your application’s default database. If your application only has one database, the default config works out of the box — just update the label and description to match your data:

'connections' => [
'default' => [
'connection' => env('SQL_AGENT_CONNECTION', config('database.default')),
'label' => 'Database',
'description' => 'All application data including users, orders, and products.',
],
],

Each connection can define its own table and column restrictions:

'connections' => [
'hr' => [
'connection' => 'pgsql_hr',
'label' => 'HR Database',
'description' => 'Employees, departments, and leave records.',
'allowed_tables' => ['employees', 'departments', 'leave_requests'],
'denied_tables' => ['salary_details', 'performance_reviews'],
'hidden_columns' => [
'employees' => ['ssn', 'bank_account'],
],
],
],

How it works:

  • allowed_tables acts as a whitelist. When non-empty, only listed tables are visible to the agent on this connection. Leave empty to allow all tables.
  • denied_tables acts as a blacklist. Listed tables are always denied, even if they appear in allowed_tables. This takes precedence.
  • hidden_columns removes specific columns from schema introspection and semantic model output. The agent will not know these columns exist.

Restrictions are enforced at every layer:

  • Schema introspection (listing tables, inspecting columns)
  • Semantic model loading (table metadata from database)
  • SQL execution (queries referencing denied tables are rejected)

The chat header shows a badge indicating the number of connected databases (e.g., “2 databases connected”). The LLM handles connection routing — no user selection is needed.

Tool call indicators in the streaming UI show which database is being queried, for example “Running SQL query on crm” or “Inspecting schema on analytics”.

With a CRM and analytics database configured, you might ask:

“Which of our top 10 customers by revenue had the most page views last month?”

The agent will:

  1. Query the CRM database for the top 10 customers by revenue.
  2. Query the analytics database for page views grouped by customer.
  3. Combine the results and present an insightful answer.

Each step is visible in the streaming UI as a separate tool call with its connection label.

Table metadata is scoped per connection using the connection field in each JSON knowledge file. When you run sql-agent:load-knowledge, the loader reads the connection field from each table JSON file and stores it in the database alongside the metadata.

Add a connection field to your table JSON files matching the logical connection name (the key in your connections config, not the Laravel connection name):

{
"connection": "crm",
"table": "customers",
"description": "All registered customers.",
"columns": {
"id": "Primary key",
"name": "Customer full name",
"email": "Contact email address"
}
}

Files without a connection field default to "default" and are included for all connections.

  1. Tag each table JSON file with the appropriate connection value.
  2. Run php artisan sql-agent:load-knowledge to import all files.
  3. The agent loads only the metadata matching each connection when building context.
  • No cross-database JOINs. The agent runs separate queries and combines results programmatically.
  • Learnings and query patterns are global. They are not scoped per connection.
  • All schemas are loaded upfront. For databases with many tables, consider using allowed_tables to limit what the agent sees.