Connections & the Infra Page

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 secret

This 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:

RecordWhere
Slack, Microsoft Teams, Telegram, Zoho Cliq (Discord and WhatsApp coming)Infrastructure → Messaging → the platform
EmailInfrastructure → Workspace → Email
Model providersInfrastructure → 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-inInfrastructure → 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 rowsan 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 MailboxInfrastructure → Brokers & queues → the broker
MongoDB, pgvector, PineconeInfrastructure → Knowledge → the store
A webhook for a human seat or an escalationan Add custom record with service type Outbound webhook
Device notifiers and roomsInfrastructure → Notifiers and Infrastructure → Rooms (see [Devices](../human-in-loop/devices))
An agent's own sign-inInfrastructure → 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 datastoreSet 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:

fieldwhat it does
bot_tokenthe primary credential — posts, reads, joins
signing_secretverifies that an inbound webhook really came from Slack
app_tokenreceives 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.

  1. Pick its service type: what it is for (see the table below).
  2. Give it a name and, if you like, a base URL and a Service name (see below).
  3. Add as many fields as the system needs. Each field has:
partwhat it means
namethe header, body field or query parameter name
valuewhat is sent
secreton: the value goes only to the vault and is never shown again
where it is senta 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 typeWhere you pick itRows it keeps
External agentan external agent's connectionheader rows, plus a secret query row (2.4.497+)
Toolsan MCP, Zapier, Composio or n8n entry or tool blockheader rows. For a local (command) MCP server, each row becomes an environment variable of that process (2.4.497+)
Modela model you add on the Models page (your own server)header rows
API toolsan API tools entry's *extra fields* connectionheader rows, body rows for sign-in (OAuth client id and secret, username and password), and a secret query row
Knowledge storea custom vector store, or a custom embedding endpointheader rows
Outbound webhookan escalation webhook target, or a human agent's outbound webhook (connection)header rows
Your own codeawait leafmesh.connection_rows("name") in a Python tool or custom agentheader 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 name

Re-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_KEY in .env; create-leafmesh writes 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>:

WhereKey
A channelchannels.slack.connection: ops-slack
An external agentconnector_config.connection: salesforce-dev (Zapier: vault_connection)
An mcp tool blockconnection:
A zapier, composio or n8n tool blockvault_connection: (on zapier, connection: is the Zapier app)
A modelmodel_connection: on the agent (see [Agent Configuration → model_connection](../api-reference/agent-config#model_connection))
A human seat's webhookwebhook_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

Last updated: 11/10/2026