Skip to main content

Multiple MCP accounts and resource sets

Most personal agents do not need an MCP resource set. Start with the first useful result, and open the detailed model only when the workflow needs more than one account, shared access, or an external-customer mapping.

Start simple - one personal account

A new user begins in one implicit personal space. The first Gmail or other toolkit connection becomes that toolkit's obvious default connection. Give its numeric configured-tool ID directly to one agent through available_tools, run the agent, freeze an accepted run, and deploy it through the normal agent lifecycle. The user does not need to create or name an MCP resource set, group, slot, grant, principal, revision, or mapping.

The MCP path is intentionally short:

search_tools({"query":"read my Gmail inbox"})
# If Gmail is not configured, send the hosted setup link returned by FlyMyAI.
# Programmatic setup may call add_tool({"mcp_tool":"gmail"}) without an alias.
list_configured_tools({"mcp_tool":"gmail","alias":"default","page_size":100})
create_agent({
"name":"Inbox summary",
"user_prompt":"Summarize unread messages and return a short action list.",
"available_tools":[123],
"mcp_access_mode":"legacy"
})
run_agent({
"agent_id":"<same agent UUID>",
"variables":{},
"operation_key":"inbox-summary-run-<uuid>"
})

Continue by refining and freezing that same agent, then follow Create, Freeze, and Embed One Agent if it will be deployed. This default path uses the same exact connection IDs, authority checks, freeze snapshot, and runtime selection model described below. It can grow into advanced configuration without rebuilding the agent or reconnecting the first account.

Expand only when the workflow needs it

Introduce aliases and resource sets when one toolkit name is no longer enough to identify the account that should act.

For example, one agent can read a support Gmail inbox, compare it with a sales Gmail inbox, and archive selected messages through a third Gmail account. Each mailbox is one exact connection instance. A named resource set groups those instances into one reusable MCP resource set.

The identities stay separate:

ObjectWhat it identifiesStable reference
ToolkitWhich operations exist, such as Gmail actionsmcp_tool slug
Connection instanceWhich authenticated account or remote MCP server actspublic_id
MCP resource setA named collection of exact connectionsresource-set public_id and revision
Agent grantWhich agent may use the setmcp_resource_set_ids
Agent groupA flat group of agents that share set grantsagent-group public_id
Runtime bindingWhich exact connection fills a frozen slotconnection public_id

Aliases such as support and sales are labels for people. Authorization, freezing, dispatch, refresh, revocation, and customer isolation use persisted IDs. Never select an account by email, display name, toolkit slug, resource-set name, list position, or a database first row.

Advanced owner workflow

Set the exact REST environment before using the examples. Released production uses the value shown here; a release candidate must supply its own root and must not fall back to production:

export FLYMYAI_AGENTS_API_ROOT=https://backend.flymy.ai/api/v1/agents
: "${FLYMYAI_AGENTS_API_ROOT:?Set the exact Agents API root}"
case "$FLYMYAI_AGENTS_API_ROOT" in */api/v1/agents) ;; *) exit 1 ;; esac
BASE="${FLYMYAI_AGENTS_API_ROOT%/}"
auth=(-H "X-API-KEY: $FLYMYAI_API_KEY")
: "${MCP_RESOURCE_SET_CREATE_KEY:?Persist one key for the exact owner-set create body}"
: "${AGENT_GROUP_CREATE_KEY:?Persist one key for the exact group create body}"

The same flow is available through the FlyMyAI Agents MCP gateway. The MCP tool names are included after the REST example.

1. Create exact connection instances

Create each catalog connection with a unique alias:

support=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data '{"mcp_tool":"gmail","alias":"support"}' \
"$BASE/tools/")

sales=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data '{"mcp_tool":"gmail","alias":"sales"}' \
"$BASE/tools/")

archive=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data '{"mcp_tool":"gmail","alias":"archive"}' \
"$BASE/tools/")

SUPPORT_ID=$(jq -er '.public_id' <<<"$support")
SALES_ID=$(jq -er '.public_id' <<<"$sales")
ARCHIVE_ID=$(jq -er '.public_id' <<<"$archive")

Each response includes:

  • id - the numeric compatibility ID used by legacy direct tool attachment;
  • public_id - the stable UUID used by resource sets and cross-system code;
  • mcp_tool - the toolkit definition;
  • alias - the normalized human label;
  • is_configured, next_configuration_step, connection_status, connection_status_reason, connection_status_checked_at, connection_status_valid_until, connection_status_reason_code, and connect_url, without returning stored secrets to an MCP client.

Follow the returned setup or connect URL for each exact row. Do not ask a user to paste an API key, OAuth token, password, or refresh token into chat.

Calling POST /tools/ without alias addresses the legacy alias default. That request remains idempotent and preserves old single-connection behavior. Use an explicit non-default alias whenever another instance is intended. Aliases and resource-set slots use only ASCII letters, digits, underscores, and hyphens. The API rejects spaces, punctuation, and arbitrary authority fields instead of normalizing them into a different identity.

One owner may retain at most 25 connection rows for one toolkit, including inactive rows. Replaying an existing alias remains idempotent at the limit. Delete an unused row to free capacity; merely setting is_active:false keeps its exact identity and therefore does not free a slot.

Through MCP, the equivalent calls are:

add_tool({"mcp_tool":"gmail","alias":"support"})
add_tool({"mcp_tool":"gmail","alias":"sales"})
add_tool({"mcp_tool":"gmail","alias":"archive"})
list_configured_tools({"mcp_tool":"gmail","page_size":100})

For an unconfigured row, the current gateway returns guidance containing the hosted https://app.flymy.ai/mcp-configs#<slug>?connection_id=<public UUID> link and the exact alias, public UUID, and numeric compatibility ID. A slug-only link is used only before an exact row exists. It does not return a field named setup_url. After the user finishes, match that exact alias and public_id in list_configured_tools. Require is_configured:true; connected is ready, setup_required or reconnect_required needs the hosted link, verification_pending needs a bounded live read, and temporarily_unavailable must be surfaced rather than bypassed.

Revoke one exact connection with delete_tool and that row's numeric id (REST DELETE /api/v1/agents/tools/{id}/) after matching its public_id and alias. Revocation is per connection: local authority closes atomically with a durable provider revocation for that row only, sibling aliases keep working, and a later dispatch on the revoked row fails before any provider call.

Retain every returned public_id. Listing by toolkit slug alone is not enough when several rows share the same toolkit.

list_configured_tools returns exactly one bounded cursor page shaped as {next_cursor, previous_cursor, results}. Filter by exact mcp_tool and, when one named row is wanted, exact alias. Pass a non-null next_cursor back unchanged as cursor; never assume the first page contains every connection instance. The endpoint does not expose a fuzzy query filter.

2. Create a named MCP resource set

An owner resource set can contain the owner's catalog connections and custom MCP servers:

resource_set=$(curl -fsS -X POST "${auth[@]}" \
-H "Idempotency-Key: $MCP_RESOURCE_SET_CREATE_KEY" \
-H 'Content-Type: application/json' \
--data '{
"name":"Inbox operations",
"description":"Support, sales, and archive mailboxes",
"management_mode":"flymyai"
}' \
"$BASE/mcp-resource-sets/")

RESOURCE_SET_ID=$(jq -er '.public_id' <<<"$resource_set")
RESOURCE_SET_REVISION=$(jq -er '.revision' <<<"$resource_set")

For MCP, pass the same durable identity as the required operation_key on create_mcp_resource_set: a caller-owned string of 1-255 printable ASCII characters with no leading or trailing spaces. No client, SDK, or server generates a fallback key. Persist the exact body and key before the first dispatch. If the response is lost, replay the identical body with that same key. A changed body with the old key returns 409; a new key could create a second resource set and is not a recovery strategy.

Omitting principal_id creates an owner-authority set. Do not put a customer integration_connection in an owner set. Customer mappings use a set rooted at one exact ExternalPrincipal, described below.

management_mode describes what the saved set represents:

  • flymyai with no principal_id is an owner resource set granted to owner agents;
  • customer with an exact principal_id is a customer-managed named mapping whose opaque set ID is stored and supplied by the customer's backend.

A FlyMyAI-managed embedded binding is not a principal resource set. It uses saved ConnectionBinding rows, and the deployment run omits resource_set_id and connections. Do not create a principal set with management_mode: "flymyai" and do not send the deprecated external_principal_id field.

3. Replace membership atomically

Membership is an atomic full replacement protected by optimistic concurrency. Send the current revision as expected_revision:

members=$(jq -nc \
--arg support "$SUPPORT_ID" \
--arg sales "$SALES_ID" \
--arg archive "$ARCHIVE_ID" \
--argjson revision "$RESOURCE_SET_REVISION" \
'{
expected_revision:$revision,
members:[
{
resource_type:"user_mcp_tool",
resource_id:$support,
slot:"support_read",
allowed_actions:["GMAIL_SEARCH_EMAILS"],
position:0
},
{
resource_type:"user_mcp_tool",
resource_id:$support,
slot:"support_send",
allowed_actions:["GMAIL_SEND_EMAIL"],
position:1
},
{
resource_type:"user_mcp_tool",
resource_id:$sales,
slot:"sales_mailbox",
allowed_actions:[],
position:2
},
{
resource_type:"user_mcp_tool",
resource_id:$archive,
slot:"archive_mailbox",
allowed_actions:[],
position:3
}
]
}')

resource_set=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data "$members" \
"$BASE/mcp-resource-sets/$RESOURCE_SET_ID/replace-members/")

RESOURCE_SET_REVISION=$(jq -er '.revision' <<<"$resource_set")

The member input contract is:

FieldMeaning
resource_typeuser_mcp_tool, custom_mcp_server, or integration_connection
resource_idStable public UUID of the exact connection or server
slotLogical role in the workflow
allowed_actionsOptional least-authority action ceiling; omission or an empty list means no additional set-level ceiling, not deny all
positionStable display and resolution order, defaulting to request order

Member identity is the exact (resource_type, resource_id, slot) tuple. One exact resource may appear in several distinct logical slots, as the support connection does above. Each slot keeps its own allowed_actions ceiling, so a read slot does not inherit the send slot's authority. Repeating the same tuple is invalid.

A 409 response with code: "stale_revision" means another writer changed the set. Reload the current representation, review the new membership, and submit a new full replacement with its revision. Do not overwrite it blindly.

Metadata edits use the same CAS boundary. MCP update_mcp_resource_set requires both the stable resource_set_id and current expected_revision, even when only name, description, or status changes. A missing revision is a validation error; HTTP 409 requires reload and intentional review.

A resource set supports at most 100 members and one runtime slot supports at most 25 connections. Members pooled inside one slot must use one toolkit and one action ceiling. Different slots remain independent even when they point to the same exact resource.

One scoped agent execution admits at most 100 MCP resource sets, 500 effective connector bindings, 100 logical slots, 5,000 allowed-action entries, and 1 MiB of UTF-8 allowed-action text. An oversized authority graph fails closed before provider dispatch.

4. Grant the resource set to one agent

Pass stable resource-set UUIDs when creating or updating the agent:

agent=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data "$(jq -nc --arg set "$RESOURCE_SET_ID" '{
name:"Cross-mailbox brief",
user_prompt:(
"Compare the support and sales mailboxes, then move approved messages " +
"through the archive mailbox. Return a structured summary."
),
mcp_resource_set_ids:[$set],
mcp_access_mode:"scoped",
output_schema:{
type:"object",
properties:{summary:{type:"string"}},
required:["summary"],
additionalProperties:false
}
}')" \
"$BASE/tasks/")

AGENT_ID=$(jq -er '.uuid' <<<"$agent")

Through MCP:

create_agent({
"name":"Cross-mailbox brief",
"user_prompt":"Compare support and sales, then archive approved messages.",
"mcp_resource_set_ids":["<resource-set public UUID>"],
"mcp_access_mode":"scoped"
})

When converting an existing personal agent from its direct available_tools attachment to logical resource slots, discover the live update_agent request schema with MCP tools/list first. The current schema requires agent_id and accepts the same optional attachment fields as create_agent. Include the existing default connection in the new set when it must remain available, then replace the old direct attachment explicitly:

update_agent({
"agent_id":"<same agent UUID>",
"available_tools":[],
"mcp_resource_set_ids":["<resource-set public UUID>"],
"mcp_access_mode":"scoped"
})
get_agent({"agent_id":"<same agent UUID>"})

An omitted attachment field is unchanged; an explicit empty array clears that relation. Verify that available_tools is empty and mcp_resource_set_ids contains exactly the intended set. Direct attachments and set grants otherwise form a union, which can preserve unintended owner authority. Clear available_custom_mcp_servers only when those legacy server attachments are also being replaced. Never copy an owner connection ID into a customer mapping.

The mutable owner runtime receives the union of legacy direct attachments, direct resource-set grants, and active agent-group grants. Exact connection members are deduplicated only when the complete (resource_type, resource_id, slot) tuple repeats across grants. Distinct slot memberships for the same connection remain separate with their own action ceilings. A foreign-owner set or connection fails closed.

5. Share the resource set with a flat agent group

Use an agent group when several agents need the same resource sets:

curl -fsS -X POST "${auth[@]}" \
-H "Idempotency-Key: $AGENT_GROUP_CREATE_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq -nc --arg agent "$AGENT_ID" --arg set "$RESOURCE_SET_ID" '{
name:"Inbox agents",
description:"Agents allowed to use the inbox operations resource set",
is_active:true,
agent_ids:[$agent],
resource_set_ids:[$set]
}')" \
"$BASE/agent-groups/"

The MCP equivalents are create_agent_group, update_agent_group, get_agent_group, and list_agent_groups. Supplying agent_ids or resource_set_ids on an update replaces that full assignment list atomically. Omitting a list leaves it unchanged. create_agent_group requires its own caller-owned operation_key; an uncertain response is retried only with the identical body and original key.

list_configured_tools, list_mcp_resource_sets, list_mcp_resource_set_members, and list_agent_groups return one compact cursor envelope shaped exactly as {next_cursor, previous_cursor, results}. Request page_size from 1-100, treat cursor as an opaque, nonblank printable string of at most 1024 Unicode code points, and pass next_cursor unchanged to continue. Resource-set and agent-group list query is at most 256 printable characters; the member list accepts only its set ID, page size, and cursor; configured-tool list uses only exact mcp_tool and alias filters. Do not translate cursors to offsets or stop after the first page when the continuation is non-null.

Agent groups are flat in this release. They cannot contain groups. A child or subagent does not inherit a parent's connector authority merely because the parent invoked it. Add that agent to the group or grant the set directly.

Exact selection at runtime

FlyMyAI exposes one action schema per toolkit action, not one duplicated schema per connected account. If exactly one granted connection can execute an action, legacy implicit selection is preserved. If several can execute it, the action schema requires _flymyai_connection.

Pass an allowed connection public_id:

{
"_flymyai_connection": "22222222-2222-4222-8222-222222222222",
"query": "after:2026/08/01"
}

For a direct MCP execute_tool call outside an agent run, pass the exact configured-tool public UUID as connection_id. Omission is supported only while one eligible connection is unambiguous; it must never select an account by row order. The direct call still requires its caller-owned stable operation_key and exact action schema.

Read the exact enum and descriptions from the runtime schema. Never substitute the alias or choose the first row. Missing, foreign, revoked, expired, stale, or no-longer-granted selectors fail before provider dispatch.

Connected-but-unattached discovery can auto-activate a toolkit only when one eligible connection exists. An ambiguous toolkit must be attached or grouped explicitly.

Freeze and publish without losing account identity

Freeze only an accepted completed execution of the same logical agent. New frozen versions capture effective resource-set membership, exact owner connection IDs, logical slots, action ceilings, and source set revisions.

Changing a member, slot, action ceiling, group membership, or set revision can change the next version fingerprint. An older immutable version does not change. Do not rely on editing an alias after freeze to retarget an old release.

Adding an account without rebuilding means keeping the same agent UUID: update that agent, accept a new run, and freeze a new immutable version. Stage that version on the same stable deployment ID. Neither the old version nor an incompatible saved customer mapping is mutated in place.

Continue with Create, Freeze, and Embed One Agent for the complete run, freeze, version, preflight, publish, and rollback flow.

Product-customer mapping modes

external_user_id resolves one product customer's ExternalPrincipal. It does not identify a Gmail account, choose a resource set, create a FlyMyAI user, or switch billing. FlyMyAI still bills the deployment owner.

Choose the mapping mode explicitly:

ModeWhat your backend sends on deployment runWhere the mapping lives
FlyMyAI-managed bindingNeither resource_set_id nor connectionsSaved ConnectionBinding rows
Customer-managed named mappingresource_set_id and its saved resource_set_revisionStable opaque mapping ID in your backend, exact members in FlyMyAI
One-off explicit mappingconnections keyed by slotCanonical request stored by your backend

resource_set_id and connections are mutually exclusive. Supplying both is a validation error. resource_set_revision is valid only with resource_set_id. The backend still accepts an omitted named-mapping revision for compatibility, but the public safe workflow requires both saved values so a stale mapping fails closed.

The names describe who stores the run selector. A FlyMyAI-managed run uses saved bindings and no principal resource-set ID. A customer-managed named run uses a principal resource set created with management_mode: "customer" and principal_id; the customer's backend stores and supplies that set ID.

Bootstrap the customer principal through connect-session

GET /deployments/{deployment_id}/access/ is read-only. It can show the deployment requirements and existing customer state, but it never creates an ExternalPrincipal. The first mutating bootstrap is POST /deployments/{deployment_id}/connect-session/ with the exact external_user_id and one connection_required slot from the published deployment:

: "${SLOT:?Use a connection_required slot from deployment access}"
session=$(curl -fsS -X POST "${auth[@]}" \
-H 'Content-Type: application/json' \
--data "$(jq -nc \
--arg user "$EXTERNAL_USER_ID" \
--arg slot "$SLOT" \
'{external_user_id:$user,slot:$slot}')" \
"$BASE/deployments/$DEPLOYMENT_ID/connect-session/")

jq '{redirect_url,expires_at}' <<<"$session"

That POST resolves or creates the principal and creates a short-lived hosted authorization session. Treat it as a single-dispatch write: persist the canonical deployment, customer, and slot before calling, and reconcile an ambiguous response before creating another session. Redirect the authenticated customer to redirect_url. After authorization, poll the read-only access route for active, unexpired connections:

curl -fsS -G "${auth[@]}" \
--data-urlencode "external_user_id=$EXTERNAL_USER_ID" \
"$BASE/deployments/$DEPLOYMENT_ID/access/"

Only after that mutating bootstrap should the owner query the principal list. Require exactly one result:

principals=$(curl -fsS -G "${auth[@]}" \
--data-urlencode "deployment=$DEPLOYMENT_ID" \
--data-urlencode "external_user_id=$EXTERNAL_USER_ID" \
"$BASE/external-principals/")

PRINCIPAL_ID=$(jq -er '
(.results // .) as $rows |
if ($rows|length)==1 then $rows[0].public_id else error("expected one principal") end
' <<<"$principals")

Create a customer-managed named mapping

Create a principal-authority set:

: "${CUSTOMER_RESOURCE_SET_CREATE_KEY:?Persist one key for this exact customer mapping create}"
customer_set=$(curl -fsS -X POST "${auth[@]}" \
-H "Idempotency-Key: $CUSTOMER_RESOURCE_SET_CREATE_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq -nc --arg p "$PRINCIPAL_ID" '{
name:"Primary inbox mapping",
description:"Mapping stored by the customer backend",
management_mode:"customer",
principal_id:$p
}')" \
"$BASE/mcp-resource-sets/")

Replace its members with exact integration_connection public UUIDs belonging to that same principal. Cross-principal IDs are rejected. Store the returned resource-set public_id and revision next to your own customer and deployment record. The ID is opaque and does not expose credentials.

Run the stable deployment with that saved mapping:

curl -fsS -X POST "${auth[@]}" \
-H "Idempotency-Key: $PERSISTED_DEPLOYMENT_RUN_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq -nc \
--arg user "$EXTERNAL_USER_ID" \
--arg set "$CUSTOMER_RESOURCE_SET_ID" \
--argjson revision "$CUSTOMER_RESOURCE_SET_REVISION" \
'{
external_user_id:$user,
variables:{},
resource_set_id:$set,
resource_set_revision:$revision
}')" \
"$BASE/deployments/$DEPLOYMENT_ID/run/"

The runtime revalidates set authority and every exact connection at the immutable snapshot boundary. A stale revision, revoked connection, wrong principal, or slot mismatch fails closed.

For FlyMyAI-managed bindings, omit both mapping fields. For a one-off mapping, send connections instead:

{
"external_user_id": "customer_42",
"variables": {},
"connections": {
"support_mailbox": "<integration-connection public UUID>",
"archive_mailbox": ["<integration-connection public UUID>"]
}
}

Each connections value is either one integration-connection public UUID or an array of UUIDs for a pooled slot. Use only slot names from the frozen access requirements, honor each slot's cardinality, and include every required slot when the request is intended to be a complete explicit mapping. An omitted slot is not an explicit empty value: current runtime behavior continues to its saved binding for that slot. Every supplied UUID is revalidated against the resolved external principal; an owner connection, a different customer's connection, an unknown slot, or invalid cardinality fails before provider dispatch. The request contains IDs only, never provider credentials.

Derive external_user_id from the authenticated product session and keep the builder key on your server. Persist the mapping ID, revision, canonical run body, idempotency key, execution ID, and settled price in your own ledger.

See Per-customer MCP Access for hosted connection sessions, readiness polling, isolation proof, billing, and cleanup.

Public API shapes

A resource-set list row is a compact summary containing:

public_id, name, description, status, management_mode, authority_type,
principal_id, revision, member_count, created_at, updated_at

It does not nest members. A resource-set detail/create/update/replace response contains:

public_id, name, description, status, management_mode, authority_type,
principal_id, revision, member_count, members, created_at, updated_at

Every member contains:

public_id, resource_type, resource_id, toolkit_slug, alias, display_name,
slot, allowed_actions, position, created_at, updated_at

An agent-group response contains:

public_id, name, description, is_active, agent_ids, resource_set_ids,
created_at, updated_at

All new cross-system references are public UUIDs. Numeric IDs remain only on legacy compatibility routes and fields.

MCP and SDK paths

Use the implemented lifecycle boundary:

StageImplemented surface
Author and testMCP create_agent, run_agent, and get_run
Freeze and owner-testMCP freeze_agent, get_compilation, and run_frozen
Exact owner connectionsMCP add_tool, update_tool, and list_configured_tools
Resource sets and groupsThe MCP tools listed below
Publish a deploymentAgents REST POST /deployments/, /preflight/, and /publish/
Bootstrap and inspect a customerAgents REST POST /deployments/{deployment_id}/connect-session/ and read-only GET /deployments/{deployment_id}/access/
Run for a customerAgents REST POST /deployments/{deployment_id}/run/

The Agents MCP gateway's resource-set and group surface is:

  • exact account creation through add_tool(mcp_tool, alias) and bounded discovery through list_configured_tools(mcp_tool, alias, page_size, cursor);
  • list_mcp_resource_sets, get_mcp_resource_set, create_mcp_resource_set, update_mcp_resource_set, and delete_mcp_resource_set;
  • paginated list_mcp_resource_set_members for large membership reads;
  • revision-checked replace_mcp_resource_set_members;
  • list_agent_groups, get_agent_group, create_agent_group, update_agent_group, and delete_agent_group;
  • mcp_resource_set_ids on create_agent and update_agent.

Deployment publication, hosted customer connection setup, principal reads, and customer runs remain on the Agents REST API. The MCP gateway can author, test, freeze, and owner-test the same agent before that REST handoff. Always use the tool list returned by MCP tools/list as the source of truth and do not turn a REST operation into an invented MCP call.

SDK versions that expose resource scopes mirror the same REST objects through typed mcp_resource_sets and agent_groups resources. Feature-detect those namespaces in the installed package. If they are absent, use the canonical REST calls above instead of inventing SDK methods. The currently released SDK's deployment limitations are documented in the lifecycle guide.

Rollout and compatibility

  • Additional aliases and resource-scope mutations are feature-gated during the staged rollout. A workspace that is not enabled can continue using the default connection and can receive a clear mutation-disabled error.
  • Existing single-connection agents keep their explicit default compatibility projection. They are not silently reassigned to another row.
  • Disabling a resource set stops its grants without deleting connection credentials. Deleting a set removes its membership and grants, not the underlying connections.
  • Sets and agent groups are not nested in this release. Cross-owner sharing and organization RBAC are not implied by matching names.
  • A rollback disables new creation but must keep reading and executing existing exact rows and sets. Never collapse several aliases back into one row.
  • Deploy backend expansion and backward-compatible readers before enabling multi-alias writers or the new clients. Before disabling those readers or rolling database state back across the alias constraint, run the server's read-only alias downgrade preflight and require a clean result. If it reports non-default aliases or scoped state, keep the compatible backend in service; do not drop or rewrite customer mappings to force a rollback.

Before promotion, run the clean-assistant lifecycle gate early. It must create one logical agent, use exact resource scopes, freeze one accepted run, publish one deployment, and stop without receiving customer IDs in its prompt. The trusted harness must then provision two different external_user_id bindings and prove they remain on the same agent, version, deployment, and billed owner while receiving distinct principals, executions, and connector state.