@@ -239,29 +239,31 @@ func LoadWithConfig(ctx context.Context, agentSource config.Source, runConfig *c
239239 agentsByName [agentConfig .Name ] = ag
240240 }
241241
242- // Connect sub-agents and handoff agents
242+ // Connect sub-agents and handoff agents.
243+ // externalAgents caches agents loaded from external references (OCI/URL),
244+ // keyed by the original reference string, to avoid loading the same
245+ // external agent twice. This is kept separate from agentsByName to
246+ // prevent external agents from shadowing locally-defined agents.
247+ externalAgents := make (map [string ]* agent.Agent )
243248 for _ , agentConfig := range cfg .Agents {
244- name := agentConfig .Name
245-
246- subAgents := make ([]* agent.Agent , 0 , len (agentConfig .SubAgents ))
247- for _ , subName := range agentConfig .SubAgents {
248- if subAgent , exists := agentsByName [subName ]; exists {
249- subAgents = append (subAgents , subAgent )
250- }
249+ a , exists := agentsByName [agentConfig .Name ]
250+ if ! exists {
251+ continue
251252 }
252253
253- if a , exists := agentsByName [name ]; exists && len (subAgents ) > 0 {
254+ subAgents , err := resolveAgentRefs (ctx , agentConfig .SubAgents , agentsByName , externalAgents , & agents , runConfig , & loadOpts )
255+ if err != nil {
256+ return nil , fmt .Errorf ("agent '%s': resolving sub-agents: %w" , agentConfig .Name , err )
257+ }
258+ if len (subAgents ) > 0 {
254259 agent .WithSubAgents (subAgents ... )(a )
255260 }
256261
257- handoffs := make ([]* agent.Agent , 0 , len (agentConfig .Handoffs ))
258- for _ , handoffName := range agentConfig .Handoffs {
259- if handoffAgent , exists := agentsByName [handoffName ]; exists {
260- handoffs = append (handoffs , handoffAgent )
261- }
262+ handoffs , err := resolveAgentRefs (ctx , agentConfig .Handoffs , agentsByName , externalAgents , & agents , runConfig , & loadOpts )
263+ if err != nil {
264+ return nil , fmt .Errorf ("agent '%s': resolving handoffs: %w" , agentConfig .Name , err )
262265 }
263-
264- if a , exists := agentsByName [name ]; exists && len (handoffs ) > 0 {
266+ if len (handoffs ) > 0 {
265267 agent .WithHandoffs (handoffs ... )(a )
266268 }
267269 }
@@ -477,6 +479,95 @@ func getToolsForAgent(ctx context.Context, a *latest.AgentConfig, parentDir stri
477479 return toolSets , warnings
478480}
479481
482+ // resolveAgentRefs resolves a list of agent references to agent instances.
483+ // References that match a locally-defined agent name are looked up directly.
484+ // References that are external (OCI or URL) are loaded on-demand and cached
485+ // in externalAgents so the same reference isn't loaded twice.
486+ func resolveAgentRefs (
487+ ctx context.Context ,
488+ refs []string ,
489+ agentsByName map [string ]* agent.Agent ,
490+ externalAgents map [string ]* agent.Agent ,
491+ agents * []* agent.Agent ,
492+ runConfig * config.RuntimeConfig ,
493+ loadOpts * loadOptions ,
494+ ) ([]* agent.Agent , error ) {
495+ resolved := make ([]* agent.Agent , 0 , len (refs ))
496+ for _ , ref := range refs {
497+ // First, try local agents by name.
498+ if a , ok := agentsByName [ref ]; ok {
499+ resolved = append (resolved , a )
500+ continue
501+ }
502+
503+ // Then, check whether this ref was already loaded as an external agent.
504+ if a , ok := externalAgents [ref ]; ok {
505+ resolved = append (resolved , a )
506+ continue
507+ }
508+
509+ if ! config .IsExternalReference (ref ) {
510+ continue
511+ }
512+
513+ a , err := loadExternalAgent (ctx , ref , runConfig , loadOpts )
514+ if err != nil {
515+ return nil , fmt .Errorf ("loading %q: %w" , ref , err )
516+ }
517+ * agents = append (* agents , a )
518+ externalAgents [ref ] = a
519+ resolved = append (resolved , a )
520+ }
521+ return resolved , nil
522+ }
523+
524+ // maxExternalDepth is the maximum nesting depth for loading external agents.
525+ // This prevents infinite recursion when external agents reference each other.
526+ const maxExternalDepth = 10
527+
528+ // loadExternalAgent loads an agent from an external reference (OCI or URL).
529+ // It resolves the reference, loads its config, and returns the default agent.
530+ func loadExternalAgent (ctx context.Context , ref string , runConfig * config.RuntimeConfig , loadOpts * loadOptions ) (* agent.Agent , error ) {
531+ depth := externalDepthFromContext (ctx )
532+ if depth >= maxExternalDepth {
533+ return nil , fmt .Errorf ("maximum external agent nesting depth (%d) exceeded — check for circular references" , maxExternalDepth )
534+ }
535+
536+ source , err := config .Resolve (ref , runConfig .EnvProvider ())
537+ if err != nil {
538+ return nil , err
539+ }
540+
541+ var opts []Opt
542+ if loadOpts .toolsetRegistry != nil {
543+ opts = append (opts , WithToolsetRegistry (loadOpts .toolsetRegistry ))
544+ }
545+
546+ result , err := Load (contextWithExternalDepth (ctx , depth + 1 ), source , runConfig , opts ... )
547+ if err != nil {
548+ return nil , err
549+ }
550+
551+ return result .DefaultAgent ()
552+ }
553+
554+ // contextKey is an unexported type for context keys defined in this package.
555+ type contextKey int
556+
557+ // externalDepthKey is the context key for tracking external agent loading depth.
558+ var externalDepthKey contextKey
559+
560+ func externalDepthFromContext (ctx context.Context ) int {
561+ if v , ok := ctx .Value (externalDepthKey ).(int ); ok {
562+ return v
563+ }
564+ return 0
565+ }
566+
567+ func contextWithExternalDepth (ctx context.Context , depth int ) context.Context {
568+ return context .WithValue (ctx , externalDepthKey , depth )
569+ }
570+
480571// createRAGToolsForAgent creates RAG tools for an agent, one for each referenced RAG source
481572func createRAGToolsForAgent (agentConfig * latest.AgentConfig , allManagers map [string ]* rag.Manager ) []tools.ToolSet {
482573 if len (agentConfig .RAG ) == 0 {
0 commit comments