# How Switchyard RuntimeModels Differentiates Parent and Sub-Agent Model Groups

> Learn how Switchyard's RuntimeModels separates parent and sub-agent model groups using distinct HashMaps for isolated management and access. Understand the differentiation for robust agent architecture.

- Repository: [NVIDIA-NeMo/Switchyard](https://github.com/NVIDIA-NeMo/Switchyard)
- Tags: internals
- Published: 2026-09-12

---

**Switchyard's `RuntimeModels` struct maintains two isolated HashMap collections—`by_category` for parent agents and an optional `subagent` map for delegated agents—enabling complete separation of model groups through distinct accessor methods.**

The `RuntimeModels` container serves as the central registry that routing algorithms use to look up available model IDs during request processing. According to the NVIDIA-NeMo/Switchyard source code, this Rust-based orchestration system implements strict capability isolation by storing parent and sub-agent model groups in separate struct fields with scope-aware lookup methods.

## Core Data Structure in algorithm.rs

The `RuntimeModels` definition resides in [`crates/libsy/src/core/algorithm.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/core/algorithm.rs) at lines 46-52, where the struct explicitly partitions model visibility into two distinct storage fields:

```rust
pub struct RuntimeModels {
    /// When using subagents this is the parent agent category.
    by_category: HashMap<Category, Vec<ModelId>>,
    subagent: Option<HashMap<Category, Vec<ModelId>>>,
}

```

This architecture enforces **complete isolation** between execution contexts:

- **`by_category`** – A `HashMap<Category, Vec<ModelId>>` containing model groups exclusively available to the main (parent) agent
- **`subagent`** – An `Option<HashMap<Category, Vec<ModelId>>>` holding an optional, secondary set of groups visible only to delegated sub-agents

## Parent Model Access with models_for()

Parent agents retrieve their available models through the `models_for()` method implemented at lines 68-71 of [`crates/libsy/src/core/algorithm.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/core/algorithm.rs):

```rust
pub fn models_for(&self, category: &Category) -> &[ModelId] {
    self.by_category.get(category).map_or(&[], Vec::as_slice)
}

```

This method returns a slice reference (`&[ModelId]`) directly from the `by_category` HashMap. When the requested category does not exist in the parent collection, it returns an empty slice rather than exposing sub-agent models as a fallback.

## Sub-Agent Model Access with subagent_models_for()

Sub-agents access their dedicated model groups via `subagent_models_for()`, defined at lines 73-78 in the same file:

```rust
pub fn subagent_models_for(&self, category: &Category) -> &[ModelId] {
    self.subagent
        .as_ref()
        .and_then(|models| models.get(category))
        .map_or(&[], Vec::as_slice)
}

```

This accessor traverses the optional `subagent` field using `as_ref()` and `and_then()` to safely handle cases where no sub-agent map exists. Like its parent counterpart, it returns an empty slice for missing categories, ensuring sub-agents never accidentally inherit parent model visibility.

## Driver Scope Selection Logic

The routing **Driver** determines which collection to query based on its execution scope. The source defines a `Scope` enum with `Parent` and `Subagent` variants:

- **Parent scope**: The main driver calls `models.models_for(category)` to read exclusively from the `by_category` field
- **Subagent scope**: When spawning sub-agents via `Driver::for_subagent`, the cloned driver switches to `Scope::Subagent` and invokes `models.subagent_models_for(category)`

This scope-based dispatch ensures that parent agents cannot access sub-agent model groups, and sub-agents cannot fall back to parent models unless developers explicitly populate both HashMaps with identical data during `RuntimeModels` construction.

## Practical Implementation Example

The following example demonstrates constructing a `RuntimeModels` instance with both parent and sub-agent collections, then accessing each group through its respective method:

```rust
use switchyard_libsy::RuntimeModels;
use switchyard_protocol::{Category, ModelId};
use std::collections::HashMap;

// Build RuntimeModels with both parent and sub-agent groups
let mut parent = HashMap::new();
parent.insert(Category::Efficient, vec![ModelId::from("fast-model")]);

let mut sub = HashMap::new();
sub.insert(Category::Efficient, vec![ModelId::from("sub-fast-model")]);

let runtime = RuntimeModels::new(parent).with_subagent(sub);

// Parent driver reads from by_category
let parent_models = runtime.models_for(&Category::Efficient);
// => returns ["fast-model"]

// Sub-agent driver reads from subagent option
let sub_models = runtime.subagent_models_for(&Category::Efficient);
// => returns ["sub-fast-model"]

```

In [`crates/libsy/src/algorithms/subagent.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/algorithms/subagent.rs), you can find production examples of this pattern where sub-agents receive restricted model selections distinct from their parent agents' capabilities.

## Summary

- **Dual-storage architecture**: `RuntimeModels` uses separate `by_category` and `subagent` HashMap fields to physically isolate parent and sub-agent model groups
- **Dedicated accessors**: The `models_for()` and `subagent_models_for()` methods provide clean, type-safe APIs that prevent cross-contamination between agent scopes
- **Scope-driven selection**: The `Driver` enum (`Parent` vs `Subagent`) determines which accessor to call, ensuring runtime enforcement of model visibility boundaries
- **Safe defaults**: Both accessors return empty slices for missing categories rather than falling back to alternative collections, maintaining strict isolation by default

## Frequently Asked Questions

### What happens if subagent_models_for is called but no sub-agent map exists?

The method returns an empty slice (`&[]`). Because the `subagent` field is an `Option<HashMap>`, the implementation uses `as_ref()` and `and_then()` to safely handle the `None` case, mapping it to an empty slice without panicking or falling back to parent models.

### Can parent agents access sub-agent model groups?

No. The `models_for()` method only queries the `by_category` HashMap and has no visibility into the `subagent` optional field. This architectural separation in [`crates/libsy/src/core/algorithm.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/core/algorithm.rs) ensures parent agents remain unaware of sub-agent capabilities unless both collections are explicitly populated with overlapping model IDs during construction.

### How does the Driver switch between parent and sub-agent scopes?

The Driver implements a cloning mechanism via `for_subagent()` that creates a new driver instance with `Scope::Subagent`. When the routing algorithm invokes model lookups, this scope determines whether to call `models_for()` (parent scope) or `subagent_models_for()` (sub-agent scope), effectively switching the visible model group without modifying the underlying `RuntimeModels` struct.

### Where is RuntimeModels typically instantiated in the Switchyard codebase?

Production instances are commonly built in [`crates/libsy/src/algorithms/util/target_selector.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/algorithms/util/target_selector.rs), where the target selection logic constructs `RuntimeModels` by mapping categories to model IDs based on configuration. The sub-agent variant sees heavy use in [`crates/libsy/src/algorithms/subagent.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/algorithms/subagent.rs) for scenarios requiring delegated agent isolation.