Skip to content
MeridFlow AiFlow v8.x • self-hosted

Orchestrator links

An Orchestrator link is an edge in the sub-Orchestrator graph: it grants one Orchestrator, the "from" side in the URL path, the ability to reach another Orchestrator, the "to" side in the request body, as one of its own tools. This is what Orchestrators: multi-model, multi-agent delegation calls a sub-Orchestrator. It's a distinct mechanism from Agent-to-Orchestrator delegation, which is how a native, realtime Agent, not another Orchestrator, reaches an Orchestrator.

Gated behind a deployment setting, admin session token required

See the two notes at the top of Orchestrators: every endpoint here only exists when your deployment has Orchestrators turned on, and every endpoint here authenticates with an admin session token.

Field Type Notes
id integer Response only.
from_orchestrator_id integer Response only. The Orchestrator in the URL path, the one this link grants a new capability to.
to_orchestrator_id integer The target Orchestrator's id. Required on create.
to_orchestrator_slug string Response only. The target Orchestrator's slug, denormalized onto the link so a list view doesn't need a separate lookup per row.
to_orchestrator_name string Response only. The target Orchestrator's name, denormalized the same way.
invocation_style string enum One of agent_tool, sub_agent. Defaults to agent_tool on create. See Invocation styles below.
enabled boolean Defaults to true on create.

Invocation styles

  • agent_tool: the parent Orchestrator calls the linked Orchestrator explicitly, as an ordinary tool call, and gets one answer back. Control returns to the parent immediately afterward, the same shape as an Agent's own delegate_to_agent call.
  • sub_agent: control, not just an answer, can transfer to the linked Orchestrator for the rest of the turn. The model decides for itself when a hand-off is warranted, rather than going through an explicit call-and-return.
GET /api/v1/orchestrators/{orchestrator_id}/links

Requires: Viewer and above.

Returns every outgoing link from this Orchestrator, that is, every sub-Orchestrator it can reach, ordered by ascending id.

curl https://api.your-domain.com/api/v1/orchestrators/1/links \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/orchestrators/1/links",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
links = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/orchestrators/1/links", {
  headers: { Authorization: `Bearer ${adminToken}` },
});
const links = await response.json();
[
  {
    "id": 1,
    "from_orchestrator_id": 1,
    "to_orchestrator_id": 2,
    "to_orchestrator_slug": "shipping-carrier-lookup",
    "to_orchestrator_name": "Shipping Carrier Lookup",
    "invocation_style": "agent_tool",
    "enabled": true
  }
]

A nonexistent orchestrator_id gets a 404.

POST /api/v1/orchestrators/{orchestrator_id}/links

Requires: Editor and above.

Field Type Required Default
to_orchestrator_id integer Yes none
invocation_style string enum (agent_tool, sub_agent) No agent_tool
enabled boolean No true
curl -X POST https://api.your-domain.com/api/v1/orchestrators/1/links \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"to_orchestrator_id": 2, "invocation_style": "agent_tool"}'
import httpx

response = httpx.post(
    "https://api.your-domain.com/api/v1/orchestrators/1/links",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={"to_orchestrator_id": 2, "invocation_style": "agent_tool"},
)
response.raise_for_status()
link = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/orchestrators/1/links", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${adminToken}`,
  },
  body: JSON.stringify({ to_orchestrator_id: 2, invocation_style: "agent_tool" }),
});
const link = await response.json();

Returns 201 Created with the full link object shown in Listing an Orchestrator's links above.

Error responses on create

Status When detail
404 orchestrator_id in the path doesn't exist. Orchestrator {orchestrator_id} not found
404 to_orchestrator_id in the body doesn't exist. Orchestrator {to_orchestrator_id} not found
409 A link from this Orchestrator to that same to_orchestrator_id already exists. A link to '{slug}' already exists
422 Creating this link would introduce a cycle in the sub-Orchestrator graph. Linking to '{slug}' would create a cycle in the sub-Orchestrator graph.

Cycle rejection

A link is rejected with a 422 if adding it would let the graph, starting from the target Orchestrator and following existing links, ever reach back to the source Orchestrator. A few properties of this check are worth knowing:

  • A self-link always counts as a cycle. Setting to_orchestrator_id to the same id as the orchestrator_id in the path is rejected the same way, with the same 422 and the same message shape (slug is simply that Orchestrator's own slug).
  • Disabled links still count. The check considers every existing link regardless of its enabled value, since a disabled link could always be re-enabled later. A cycle that's merely dormant today is still rejected now, not deferred until it would actually be traversed.
  • It runs again when an Orchestrator is actually invoked, not only at link-creation time, as defense in depth in case a cycle were ever introduced outside the normal write path. In ordinary use, going through this API is enough on its own: a cycle can't get created through these endpoints in the first place.

Example rejection response, linking Orchestrator 2 back to Orchestrator 1 after 1 already links to 2:

{
  "detail": "Linking to 'order-lookup' would create a cycle in the sub-Orchestrator graph."
}
PATCH /api/v1/orchestrators/{orchestrator_id}/links/{link_id}

Requires: Editor and above.

Both fields are optional; only the fields present in the request body are changed. to_orchestrator_id can't be changed after creation and isn't an accepted field on this endpoint; delete the link and create a new one to point somewhere else.

Field Type Required
invocation_style string enum (agent_tool, sub_agent) No
enabled boolean No
curl -X PATCH https://api.your-domain.com/api/v1/orchestrators/1/links/1 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"invocation_style": "sub_agent"}'
import httpx

response = httpx.patch(
    "https://api.your-domain.com/api/v1/orchestrators/1/links/1",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={"invocation_style": "sub_agent"},
)
response.raise_for_status()
link = response.json()
const response = await fetch(
  "https://api.your-domain.com/api/v1/orchestrators/1/links/1",
  {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${adminToken}`,
    },
    body: JSON.stringify({ invocation_style: "sub_agent" }),
  },
);
const link = await response.json();

Returns 200 OK with the full, updated link object. A nonexistent link_id (or one that doesn't belong to orchestrator_id) gets a 404.

DELETE /api/v1/orchestrators/{orchestrator_id}/links/{link_id}

Requires: Editor and above.

curl -X DELETE https://api.your-domain.com/api/v1/orchestrators/1/links/1 \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.delete(
    "https://api.your-domain.com/api/v1/orchestrators/1/links/1",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
const response = await fetch(
  "https://api.your-domain.com/api/v1/orchestrators/1/links/1",
  {
    method: "DELETE",
    headers: { Authorization: `Bearer ${adminToken}` },
  },
);

Returns 204 No Content. A nonexistent link_id gets a 404.