Connections & the Infra Page
Credentials do not live in your config. They live in your mesh's own encrypted store, and config refers to them by name.
agents:
support:
name: "support"
channels:
slack:
connection: "slack-workspace" # ← a name, never a secretThis is not a style preference. Keys, tokens, passwords, client ids,
addresses (URLs, endpoints, hosts) and header or query rows are entered only
in the vault, on Studio's Infrastructure page. A block picks its record with
connection: <name> (Zapier blocks use vault_connection: <name>). A value
written inline, including an environment reference such as
${SLACK_BOT_TOKEN} or ${SLACK_BOT_TOKEN:default}, is refused at load,
and the error names the field and the record to create.
This covers human seats' webhook_config, manager.escalation targets,
event brokers, channels (Slack, Teams, email and the rest), mcp
rows, external agents' connector_config, mesh.bedrock / vertex /
foundry / local, and knowledge stores and embeddings.
A missing record never stops boot. If a connection: names a record that
does not exist yet, the agent that needs it is switched off with the reason,
and it starts working as soon as the record exists, with no restart.
The name itself may come from the environment —
connection: "${SLACK_CONNECTION:slack-workspace}" is fine, because a
connection name is not a secret.
Model provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, and friends) and
LOCAL_MODEL_ENDPOINT still work from .env. A value in .env wins; the
vault (Infrastructure → Models) fills any gaps.
The Infra page
Every credential the mesh can consume appears as a card, grouped into sections: Messaging, Workspace, Models, Cloud access, Knowledge, Integrations, External agents, Brokers & queues, Notifiers, Rooms, Agent identities and Datastores. Each card knows which fields that provider actually needs, because the SDK publishes its own catalogue — the page never hard-codes a form.
Where to make each kind of record:
| Record | Where |
|---|---|
| Slack, Microsoft Teams, Telegram, Zoho Cliq (Discord and WhatsApp coming) | Infrastructure → Messaging → the platform |
| Infrastructure → Workspace → Email | |
| Model providers | Infrastructure → Models → the provider; a local server under Models → Local / self-hosted; cloud model services under Models as "AWS Bedrock", "Google Vertex AI" and "Azure AI Foundry" |
| The mesh's own cloud sign-in | Infrastructure → Cloud access → "AWS access", "Google Cloud access" or "Azure access" |
| Zapier, n8n, Composio, MCP (as tools or as external agents) | Infrastructure → Integrations → the platform (on 2.4.482, MCP shows under External agents) |
| An API tool's base URL (when its spec has none, or to call another server) and sign-in rows | an Add custom record with service type API tools |
| External agents (CrewAI, LangGraph, A2A Protocol, ...) | Infrastructure → External agents → the framework |
| Redis Streams, MQTT, Apache Kafka, AWS SQS, IMAP Mailbox | Infrastructure → Brokers & queues → the broker |
| MongoDB, pgvector, Pinecone | Infrastructure → Knowledge → the store |
| A webhook for a human seat or an escalation | an Add custom record with service type Outbound webhook |
| Device notifiers and rooms | Infrastructure → Notifiers and Infrastructure → Rooms (see [Devices](../human-in-loop/devices)) |
| An agent's own sign-in | Infrastructure → Agent identities: each agent gets its own sign-in through the organisation's single sign-on (coming, e.g. Microsoft Entra), so it calls services as itself rather than with a shared key |
| The datastore | Set only in .env; Infrastructure → Datastores shows whether it is reachable, its latency and server, the settings in force and which .env names set them, never the address or password |
The Tools page (APIs, MCP servers, Zapier, Composio, n8n) still exists: it sets up the server or tool and picks its record. Its APIs tab imports an API from its OpenAPI/Swagger file, so agents can find and call its operations as tools. The record itself is made on the Infrastructure page.
A card carries the provider's whole credential set, not one key. Slack, for instance, has three:
| field | what it does |
|---|---|
bot_token | the primary credential — posts, reads, joins |
signing_secret | verifies that an inbound webhook really came from Slack |
app_token | receives events over an outbound WebSocket (Socket Mode) |
Fields you have filled show a stored marker. A card missing a required
field says so on its face. Adding a secret later does not disturb the others,
and setting one to null removes just that one without disconnecting the
connection.
Two ways to connect
Connect — one click
For providers where we operate a registered app, the page offers a Connect button. It sends you to the provider's own consent screen; the token comes back to the mesh and lands in your vault. You never see or handle it.
Slack works this way today. Other providers deliberately do not offer one-click, and each says why on its own card — a token minted by a shared app is either read by nothing in the mesh or expires within the hour, so offering a button that leads nowhere would be worse than offering none.
Build my own app — your app, your workspace
Where one-click is not honest, the page can still provision an app for you: you supply a short-lived setup token from the provider, and the mesh creates the app, sets its scopes, events and redirect URLs, and stores the resulting credentials. Available for Slack, Composio, Zoho Cliq, OpenAI, Email (Mailgun), xAI and Pinecone.
The app belongs to you, not to us.
Custom connections
When a system has no card of its own, build one. A custom connection is an Add custom record on the Infrastructure page, a form you design. Add custom is at the top of every section except Cloud access.
- Pick its service type: what it is for (see the table below).
- Give it a name and, if you like, a base URL and a Service name (see below).
- Add as many fields as the system needs. Each field has:
| part | what it means |
|---|---|
| name | the header, body field or query parameter name |
| value | what is sent |
| secret | on: the value goes only to the vault and is never shown again |
| where it is sent | a header, or, only where the table below allows it, the body or the query string |
A record keeps sign-in only. Header rows work everywhere. Body and query rows are kept only where the table says so, and a plain (not secret) body or query row is refused on save, with a message naming the page it belongs on (for example a webhook's or an API's own query values).
| Service type | Where you pick it | Rows it keeps |
|---|---|---|
| External agent | an external agent's connection | header rows, plus a secret query row (2.4.497+) |
| Tools | an MCP, Zapier, Composio or n8n entry or tool block | header rows. For a local (command) MCP server, each row becomes an environment variable of that process (2.4.497+) |
| Model | a model you add on the Models page (your own server) | header rows |
| API tools | an API tools entry's *extra fields* connection | header rows, body rows for sign-in (OAuth client id and secret, username and password), and a secret query row |
| Knowledge store | a custom vector store, or a custom embedding endpoint | header rows |
| Outbound webhook | an escalation webhook target, or a human agent's outbound webhook (connection) | header rows |
| Your own code | await leafmesh.connection_rows("name") in a Python tool or custom agent | header rows, read by your code |
A header the call sets itself wins over a field of the same name.
One service, many records (2.4.509). A custom record can name the
Service it is for, in free text such as "Github", so several records of
one service sit together. A service holds one service type. A save that leaves
the service out keeps the one already there; an empty service clears it. The
organisation and each tenant can each hold their own record of a service, and
every listed record says whose it is (owner: the organisation or the tenant).
GET /api/infra/connections?owners=all lists the organisation's records and
every tenant's; only the organisation can ask for it.
agents:
partner_agent:
name: "partner_agent"
agent_type: "external"
framework: "a2a"
connector_config:
connection: "partner-api" # the custom connection's nameRe-saving the form keeps a stored secret you leave blank, replaces one you type, and deletes one you remove. A header name must be a valid HTTP header name, and a name may be used only once.
Older records. A plain query or body row saved before 2.4.486 is kept but no longer sent; the start logs name each one (never its value).
Where the secret actually sits
In your mesh's own persistence — Redis or Postgres, whichever you run — encrypted with a key your deployment holds. It is never sent anywhere, and the API that lists connections returns field names only, never values.
Two consequences worth knowing:
- Your environment token determines the namespace. Every key the mesh
writes is scoped by
LEAFMESH_ENV_TOKEN. Change it and the mesh looks in a new, empty namespace — connections included. They are not lost, but that mesh no longer sees them. From 2.4.507 the mesh does not start without it. - The vault key is yours (2.4.507). It is
LEAFMESH_VAULT_KEYin.env;create-leafmeshwrites one for a new project. The mesh never makes or derives a key, and with none the start stops. A new key means a new, empty vault. - Multi-pod deployments need a shared vault key. A per-pod key means each pod encrypts into its own world; the mesh warns loudly when it detects this.
That is the built-in vault. You can instead connect the secret store you already run — HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Secret Manager and others — and tenants' credentials then live there too, each in its own namespace. See Bring Your Own Vault.
Connecting a live mesh
A connection made while the mesh is running is adopted immediately — the running adapter picks it up without a restart. For Slack that includes opening Socket Mode from the app-level token, so inbound starts working at once rather than at the next restart.
If a credential arrives somewhere the mesh cannot act on it, it says so rather than leaving you with an adapter that looks connected and never answers.
Reference by name, everywhere
The same connection: reference works across the SDK — channels, connectors
(Zapier, Composio, n8n, MCP), knowledge providers, and event brokers:
agents:
researcher:
name: "researcher"
agent_type: "external"
framework: "n8n" # external agents use `framework:`
connector_config:
connection: "n8n-prod"
classifier:
name: "classifier"
agent_type: "programmatic"
integration: "zapier" # programmatic agents use `integration:`
connector_config:
connection: "zapier-main"Knowledge is per-agent and names a service rather than a provider type; the service's own credential comes from a connection:
agents:
support:
name: "support"
knowledge:
serviceName: "mongo_main"
enabled: true
groupName: "support_runbook"Several accounts of one service
2.4.486+. Make one record per account (for example ops-slack and
sales-slack, or salesforce-dev and salesforce-prod), and let each block
name its record with connection: <record name>:
| Where | Key |
|---|---|
| A channel | channels.slack.connection: ops-slack |
| An external agent | connector_config.connection: salesforce-dev (Zapier: vault_connection) |
An mcp tool block | connection: |
A zapier, composio or n8n tool block | vault_connection: (on zapier, connection: is the Zapier app) |
| A model | model_connection: on the agent (see [Agent Configuration → model_connection](../api-reference/agent-config#model_connection)) |
| A human seat's webhook | webhook_config.connection: |
In Studio every list shows each record by name, for example "Salesforce Agentforce · salesforce-dev".
Testing a connection
Every connection is tested when you save it, and you can test it again at
any time with POST /api/infra/connections/{name}/test. The provider itself
is asked whether the credential works. The answer is active (accepted, with
the account it belongs to), failing (rejected, with the reason) or
readable (stored, but no check exists or the provider could not be
reached).
Related
-
Bring Your Own Vault — connect your own secret store
-
Devices — notifier and room connections
-
Human Agents & Channels — channel seats and how inbound is routed
-
Declarative Connectors — Zapier, Composio, n8n