Skip to content

Helper model list for spawned agents - #37

Merged
olety merged 3 commits into
rust-oneironfrom
oneiron/helper-model-list
Oct 24, 2026
Merged

olety merged 3 commits into
rust-oneironfrom
oneiron/helper-model-list

Conversation

@olety

@olety olety commented Oct 24, 2026

Copy link
Copy Markdown
Member

Why

Agents in the kernel challenge spawned helpers on cpa-a/claude-opus-5, a paid reseller route, on their own. A model named in a spawn call wins over every default (#36), and rlm.find_models showed agents every model in the catalog. The owner asked for a helper model list that is separate from the full list: a person can still pick any model, but agents pick their helpers only from the list.

What changed

  • Settings key subagentModels: an ordered list of provider/model-id references that spawned agents may run on. It is read from the global settings file only, like allowedModels, so a project file (which an agent can write) cannot widen it. Absent or empty means no limit, which is the old behavior. An array of strings is the documented form. A single comma-separated string also works, and a non-string entry drops alone instead of disabling the whole list.
  • Spawn check: an rlm.spawn or rlm.create_session call that names a model off the list is refused. The error names the allowed models so the agent can retry with one:
    Requested subagent model "cpa-a/claude-opus-5" is not on the helper model list; use one of: cpa-r/deepseek-flash-1017, ... (or omit model= for the default).
    An unresolvable name gets the same refusal, so catalog close matches off the list are never shown; a listed model the catalog lacks fails with the allowed models named. A settings default or a parent model off the list falls to the list's first entry, with a warning once on stderr. The fallback must resolve to that exact entry: a short form or nested-provider match that lands on another route fails the spawn instead. A refused spawn starts no child.
  • A person's choice is allowed: the session override (--subagent-model, PRIME_SUBAGENT_MODEL, /subagent-model) may name a model off the list, and an agent may then name that model too. A short form the person typed joins the list as its catalog selector. A person can widen the list for one session with --subagent-models a,b, PRIME_SUBAGENT_MODELS=a,b, or /subagent-models a,b in the TUI (/subagent-models default clears it). The session's entries come after the settings file's, so the fallback stays the settings file's first entry. With no settings list there is nothing to widen. --model and /model are unchanged.
  • Roster: rlm.find_models lists only the allowed models when a list is set. It does this through a new defaulted trait method, RlmSubagentHost::helper_model_list.
  • One snapshot per admission: a daemon spawn now resolves its model and creates the child from the same parent identity snapshot, so a concurrent set_subagent_models cannot leave the child with a different override than the one its model was checked against.
  • Both hosts: the daemon (resolve_helper in crates/pa-daemon/src/rlm_child_model.rs, through rlm_children/host.rs) and the in-process print host (crates/pa-cli/src/print_rlm.rs, its scripted lane included) apply the same rule. Factory states spawn through rlm.spawn(model=...), so the host check covers them.
  • Inheritance and persistence: the session's additions ride RLM child creates as subagentModels (kept in the durable create command). They persist in the subagent_model_state row as data.models. A row without the field reads as no additions.
  • Status: get_subagent_model_status adds models (the active list, null when there is no limit), modelsSource (settings, or settings+session), and modelAllowed (false when the spawns' default is off the list). /subagent-model and /subagent-models print the list and its source, and warn when the default is off it. The TUI also runs this check when a session opens with a list configured.
  • Docs: docs/oneiron/HELPER-MODEL.md (new "Helper model list" section and settings row), the RUST-BASE.md row, and the pa-core/pa-cli/pa-daemon READMEs. Also the CLI help row and the slash-command entry.

Daemon wire

This is fork-only, with no schema bump. New server capability subagent_models. New session-plane, mutating command set_subagent_models { activeSessionId, models? }, which replaces only the session's list additions. set_subagent_model now replaces only the model and thinking level and keeps the additions. Each write still persists the whole override under the worker's core lock. A daemon without the capability ignores the list, so --daemon-hosted refuses one when a settings list or --subagent-models is set, and the TUI warns when the session opens.

Test evidence

Mac (Oletybook), Rust 1.98.1 (the CI pin), head 73c5155a4:

  • cargo fmt --all --check: clean. cargo clippy --workspace --all-targets --locked -- -D warnings: clean.
  • scripts/oneiron/gate.sh policy: passed.
  • The helper-list tests below pass, on both hosts.
  • scripts/oneiron/gate.sh crates pa-types pa-core pa-tui pa-daemon pa-cli (sandboxed HOME and TMPDIR, its own kernel venv): 5390 passed, 72 failed, 17 ignored. kitty_verdict_time_e2e hung both times on a child stuck in the kernel's exit path (state E), and I killed it.
  • Every failing suite fails the same way on the base commit fc8c2b429, on this Mac. I ran the same suites through the same gate sandbox:
    • First set, 12 suites: 34 failures on base and on head, with identical test names.
    • Second set, the other 16 suites plus every crate's library tests: 38 failures on base, 40 on head. The two extra are compaction_unblocked_tests, which race on a shared faux registration when run in parallel. They also fail on base in other runs, and all four pass serialized on both trees.
    • The causes are Linux-only tests (/proc, GNU ld --build-id, Linux trash) and the mock-daemon headless suites, which fail on macOS at base too.
  • Not run: the Linux gate (nelly, as in Helper model setting for spawned agents #36). Please run it before merge.
  • New and extended tests:
    • session_engine::helper_model: list order and widening, parent, exact case-insensitive matching, the refusal text, parsing, the global-only settings read (array and comma string), and the create-key and persisted-row round trips.
    • rlm_host::find_models_lists_only_the_helper_model_list.
    • rlm_child_model::the_helper_model_list_refuses_off_list_spawns_and_keeps_defaults_on_it.
    • agent_engine::tests::rlm_children (status models/modelsSource/modelAllowed).
    • rlm_surface::the_list_and_the_model_override_replace_only_their_own_fields.
    • CLI flag and env tests.
    • End to end, daemon: rlm_children_e2e::a_spawn_off_the_helper_model_list_is_refused. It runs against a real supervisor: an off-list spawn is refused and starts no child, a listed model starts, an unpinned spawn falls to the first entry, create_session is refused, and a session addition widens both the roster and the spawn.
    • End to end, in-process: print_inline_subagents_e2e::print_inline_spawns_off_the_helper_model_list_are_refused. It runs the real binary and a real kernel: the off-list spawn fails in the agent's tool result, the roster shows only the listed model, and the listed spawn starts.
  • Review: one GPT-6.1 Sol pass (xhigh, read-only). It found four issues: the first-entry fallback could resolve to an off-list route through a suffix match; a running daemon without the capability silently ignored a settings list; a person's short-form override was refused when an agent named it; and an unavailable listed model could show off-list close matches. It also flagged an older torn identity snapshot that the new field inherited. All five are fixed in 73c5155a4, with tests.

Review notes

  • Ownership: the settings key and the pure list rules live in pa-core (session_engine::helper_model::HelperModelList). Catalog resolution and enforcement live in pa-daemon (resolve_helper_unchecked, which the print host shares). The wire vocabulary is in pa-types. The UI is in pa-tui over the ClientSettings seam (pa-tui does not depend on pa-core; it splits the typed list itself and the daemon normalizes it). Dependency direction is unchanged.
  • Classification: new pub surface: HelperModelList, normalize_model_list, parse_model_list, HelperDefaults::models/with_models, HelperSource::List, first_helper_notice (replaces first_unknown_helper_model), SUBAGENT_MODELS_ENV/SUBAGENT_MODELS_CONFIG_KEY, SettingsManager::get_subagent_models, the defaulted RlmSubagentHost::helper_model_list, the defaulted SessionEngine::subagent_override, DaemonCommand::SetSubagentModels, SUBAGENT_MODELS_CAPABILITY, and the defaulted ClientSettings::subagent_models. No new dependency.
  • TS parity: these surfaces have no TS counterpart (fork-only), so there is no frame or wire diff to show.
  • Not activated: no install, no daemon restart, no change to any live settings file. An installed daemon without this change ignores subagentModels.

olety added 2 commits October 24, 2026 18:34
Agents named paid routes in their spawn calls: a model named there wins
over every default, and the find_models roster showed every catalog model.
The settings key subagentModels now limits what spawned agents may run on,
while every model stays in the catalog for a person to pick.

- Settings: subagentModels, an ordered list of provider/model-id refs, read
  from the global settings file only (a project file cannot widen it).
  Absent or empty: no limit.
- Spawn check: an rlm.spawn or rlm.create_session call naming a model off
  the list is refused with the allowed models named. A settings default or
  the parent's model off the list falls to the first entry, with a warning
  once. Both hosts check it: the daemon (resolve_helper) and the in-process
  print host, its scripted lane included.
- A person's choice is allowed: the session override (--subagent-model,
  PRIME_SUBAGENT_MODEL, /subagent-model) may sit off the list. A person can
  widen the list for one session: --subagent-models, PRIME_SUBAGENT_MODELS,
  TUI /subagent-models, daemon command set_subagent_models (capability
  subagent_models). The additions ride RLM child creates (subagentModels)
  and the subagent_model_state row.
- Roster: rlm.find_models lists only the allowed models, through the new
  RlmSubagentHost::helper_model_list.
- Status: get_subagent_model_status adds models, modelsSource and
  modelAllowed; the TUI shows the list and warns when the default is off it.
- Docs: docs/oneiron/HELPER-MODEL.md, RUST-BASE.md, crate READMEs.
…t ignore it

Review fixes for the helper model list:

- The first-entry fallback resolved its reference like a spawn reference,
  so a short form or a nested-provider match could run a route off the
  list. The resolved fallback must now be on the list, else the spawn fails
  with a message naming the entry.
- A listed model the catalog lacks failed with the catalog's close
  matches, which could name models off the list. It now fails with the
  allowed models named.
- A person's short-form helper model joined the list as typed, so an agent
  naming its full selector was refused. The hosts now add its catalog
  selector (rlm_child_model::helper_model_list, shared by the spawn check,
  the roster and the status).
- A running daemon without subagent_models ignores the settings list.
  --daemon-hosted refuses one when a list is set; the TUI warns at open.
- A spawn resolved its model from one identity snapshot and created the
  child from another, so a concurrent set_subagent_models could tear them.
  The admission now uses one snapshot for both.
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: e266eb0d-6462-479c-b4b9-5da85287d0ab

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes which models subagents may use and can refuse spawns or shift defaults to the list's first entry; hosted sessions require a daemon that advertises subagent_models.

Overview
Adds a helper model list (subagentModels in global settings) so spawned agents can only run on explicitly allowed provider/model-id entries, while people still pick any model via --model / /model.

When the list is set, rlm.spawn and rlm.create_session refuse models not on it (with allowed models named in the error). Defaults or parent models off the list fall to the first list entry with a one-time warning. Session choices widen the list: --subagent-models, PRIME_SUBAGENT_MODELS, /subagent-models, and --subagent-model overrides stay allowed. rlm.find_models only shows listed models via RlmSubagentHost::helper_model_list.

Daemon wire adds capability subagent_models, command set_subagent_models, and status fields models, modelsSource, and modelAllowed. --daemon-hosted and the TUI refuse or warn when an older daemon cannot enforce the list. Enforcement is shared by the daemon and in-process print hosts; list state persists on RLM creates and in subagent_model_state rows.

Reviewed by Cursor Bugbot for commit 73c5155. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 73c5155. Configure here.

return;
None
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TUI drops helper override on old daemon

Medium Severity

When a settings subagentModels list is present and the running daemon lacks subagent_models, apply_launch_subagent_model warns and returns before sending set_subagent_model. Launch --subagent-model/--subagent-thinking never land, even though that older daemon still advertises subagent_model and can apply them. Hosted print checks the two capabilities separately and still applies the model override.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 73c5155. Configure here.

)
.unwrap_or_else(|_| reference.to_string()),
)
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status misreports unknown default fallback

Low Severity

modelAllowed treats an unresolvable subagentDefaultModel as off the helper list. Spawn resolution first falls back to the parent, and only then clamps to the list's first entry if that parent is also off the list. The TUI therefore warns that spawns use the first list entry while also warning that they use the parent's model.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 73c5155. Configure here.

@olety
olety merged commit ba21351 into rust-oneiron Oct 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant