What it does
The OpenAI-compatible endpoints /api/agents/v1/chat/completions and /api/agents/v1/responses do not load an agent's Subagents (spawn-as-tool) configuration. When an agent with enabled subagents is invoked over /v1, no agent in the graph can spawn subagents — not the primary, not handoff targets — because the feature is never instantiated by those controllers. The same agent invoked through the browser/JWT path (/api/agents/*) spawns subagents normally.
This is the subagent analogue of #12726 / PR #12740, which fixed handoffs/chains (graph edges) to load on /v1 via the shared discoverConnectedAgents helper. Handoffs work on /v1 today; subagent-spawn does not, and there's no equivalent port.
Root cause (code map)
The subagent loader lives only on the JWT path:
api/server/services/Endpoints/agents/initialize.js — loadSubagentsFor (closure) + resolveSubagentTrees (BFS) populate config.subagentAgentConfigs, gated by subagentsCapabilityEnabled (endpoints.agents.capabilities includes subagents) and per-agent agent.subagents.enabled. Depth guard MAX_SUBAGENT_GRAPH_NODES; cycle guard via a per-config max-depth map.
It is absent from the /v1 controllers:
api/server/controllers/agents/openai.js and responses.js call discoverConnectedAgents(...) (handoff BFS) and then build runAgents = [primaryConfig, ...handoffAgentConfigs.values()] for createRun. They never invoke the subagent loader, so every config.subagentAgentConfigs stays undefined.
The runtime already supports it: packages/api/src/agents/run.ts::buildSubagentConfigs transforms agent.subagentAgentConfigs into SDK SubagentConfig[] — it simply receives nothing on the /v1 path. So this looks like a controller-wiring gap, not a runtime change.
Proposed fix (mirrors #12740)
- Extract
loadSubagentsFor / resolveSubagentTrees from initialize.js into a shared, DI-friendly helper in packages/api/src/agents/ (e.g. alongside discoverConnectedAgents), and refactor the JWT path to call it (no behavior change).
- Call the helper in both
/v1 controllers after discoverConnectedAgents and before createRun, gated by subagentsCapabilityEnabled.
- Replicate the capability-disabled strip (
config.subagents/subagentAgentConfigs = undefined).
Security note (important review point)
The JWT loader does no load-time ACL check (it relies on the SDK's spawn-time permission callback). The /v1 route gates on REMOTE_AGENT + VIEW, and discoverConnectedAgents deliberately re-checks each handoff target against that resource type. The subagent port should apply the same REMOTE_AGENT + VIEW check so a headless API key cannot spawn agents its user can't see.
Use case
Invoking an orchestrator agent headlessly (server-to-server, via sk- API key) and expecting the same multi-agent behavior as the browser. Today handoffs survive the API boundary but subagent-spawn silently no-ops, so an agent's advertised capabilities differ by entry path.
Environment
- LibreChat
v0.8.7-rc1
- Confirmed against current
main (openai.js / responses.js / initialize.js).
What it does
The OpenAI-compatible endpoints
/api/agents/v1/chat/completionsand/api/agents/v1/responsesdo not load an agent's Subagents (spawn-as-tool) configuration. When an agent with enabled subagents is invoked over/v1, no agent in the graph can spawn subagents — not the primary, not handoff targets — because the feature is never instantiated by those controllers. The same agent invoked through the browser/JWT path (/api/agents/*) spawns subagents normally.This is the subagent analogue of #12726 / PR #12740, which fixed handoffs/chains (graph edges) to load on
/v1via the shareddiscoverConnectedAgentshelper. Handoffs work on/v1today; subagent-spawn does not, and there's no equivalent port.Root cause (code map)
The subagent loader lives only on the JWT path:
api/server/services/Endpoints/agents/initialize.js—loadSubagentsFor(closure) +resolveSubagentTrees(BFS) populateconfig.subagentAgentConfigs, gated bysubagentsCapabilityEnabled(endpoints.agents.capabilitiesincludessubagents) and per-agentagent.subagents.enabled. Depth guardMAX_SUBAGENT_GRAPH_NODES; cycle guard via a per-config max-depth map.It is absent from the
/v1controllers:api/server/controllers/agents/openai.jsandresponses.jscalldiscoverConnectedAgents(...)(handoff BFS) and then buildrunAgents = [primaryConfig, ...handoffAgentConfigs.values()]forcreateRun. They never invoke the subagent loader, so everyconfig.subagentAgentConfigsstaysundefined.The runtime already supports it:
packages/api/src/agents/run.ts::buildSubagentConfigstransformsagent.subagentAgentConfigsinto SDKSubagentConfig[]— it simply receives nothing on the/v1path. So this looks like a controller-wiring gap, not a runtime change.Proposed fix (mirrors #12740)
loadSubagentsFor/resolveSubagentTreesfrominitialize.jsinto a shared, DI-friendly helper inpackages/api/src/agents/(e.g. alongsidediscoverConnectedAgents), and refactor the JWT path to call it (no behavior change)./v1controllers afterdiscoverConnectedAgentsand beforecreateRun, gated bysubagentsCapabilityEnabled.config.subagents/subagentAgentConfigs = undefined).Security note (important review point)
The JWT loader does no load-time ACL check (it relies on the SDK's spawn-time permission callback). The
/v1route gates onREMOTE_AGENT+VIEW, anddiscoverConnectedAgentsdeliberately re-checks each handoff target against that resource type. The subagent port should apply the sameREMOTE_AGENT+VIEWcheck so a headless API key cannot spawn agents its user can't see.Use case
Invoking an orchestrator agent headlessly (server-to-server, via
sk-API key) and expecting the same multi-agent behavior as the browser. Today handoffs survive the API boundary but subagent-spawn silently no-ops, so an agent's advertised capabilities differ by entry path.Environment
v0.8.7-rc1main(openai.js/responses.js/initialize.js).