Skip to content

Helper model setting for spawned agents - #36

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

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

Conversation

@olety

@olety olety commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Stacked on #35 (the 2026-10-24 upstream sync): branched from its commit 22ed07464. It retargets to rust-oneiron, rebased, once #35 merges.

What changed

An agent that spawns a helper (rlm.spawn, rlm.create_session) without naming a model ran the helper on its own model. Now a helper model can be set, per settings file and per session, and the helper's real model and thinking level are visible.

  • Settings file: subagentDefaultModel (parsed before, read by nothing) and the new subagentDefaultThinking (off ... max, or parent, so a project file can override a global level). A spawn reads the file when it runs.
  • Session override: --subagent-model / --subagent-thinking, else PRIME_SUBAGENT_MODEL / PRIME_SUBAGENT_THINKING, for -p, --mode json, --autonomous, --daemon-hosted, and the session an interactive launch opens. In the TUI, /subagent-model [<provider/model>|parent|default] [<thinking>|parent] sets it for the current session; with no argument it shows the current value and where each field comes from.
  • TUI settings menu (Models tab): "Helper model" (a list: parent plus the signed-in providers' models) and "Helper thinking" (parent, off ... max). Both persist to the settings file; parent removes the key.
  • Visibility: the spawn and create-session handles carry thinking (the level the child runs at, clamped to its model) beside model; the agents view already lists each child's model:level; /subagent-model warns when the model is not in the catalog.
  • Docs: docs/oneiron/HELPER-MODEL.md, a row in RUST-BASE.md, the pa-core/pa-cli/pa-daemon READMEs, the CLI help rows, the rlm.spawn docstring.

Resolution order

Each field (model, thinking level) resolves on its own; the first layer that names a value wins:

  1. the model and thinking the spawn call names;
  2. the session override;
  3. the settings file;
  4. the parent's model and thinking (the old behavior).

parent at layer 2 or 3 stops the walk at layer 4.

  • The spawn call's own values keep their strict checks (unknown model or unsupported level fails the spawn).
  • A default model the catalog does not know never fails a spawn: the process warns once and the helper runs on the parent's model. The CLI checks it at startup (stderr); the TUI checks it when the session opens (a warning row).
  • A default thinking level, and the inherited one, clamps to what the helper's model supports.
  • The daemon allowedModels allowlist applies to the helper model as to any model.
  • With nothing configured, every path behaves as before.

Spawn paths covered: the daemon host's rlm.spawn and rlm.create_session (crates/pa-daemon/src/rlm_children/host.rs) and the direct print host's rlm.spawn (crates/pa-cli/src/print_rlm.rs; its rlm.create_session stays daemonless-refused). Factory states and goal runs spawn through these hosts. Side questions, compaction, branch summaries and refine are one-off model calls on the session or auxiliary model, not agents, and are unchanged.

The override covers the agent tree: an RLM child's create carries its parent's override (subagentModel/subagentThinking, kept in the durable create command). The daemon worker writes the override to the session file it owns as a subagent_model_state row: on every set_subagent_model (a clear included; the live value and the row change together under the worker's core lock) and once at an RLM child's birth (a failed birth write fails the create). A resume, respawn or idle wake reads the latest row ahead of the create keys.

Daemon wire

Fork-only, behind the new server capability subagent_model (no schema bump; a client checks it before it sends either command): get_subagent_model_status and set_subagent_model { model?, thinking? }, both session-plane, the getter read-only. --daemon-hosted refuses a daemon without the capability instead of dropping the override.

Test evidence

Linux (nelly, Rust 1.98.1) through the fleet, each run on a packed snapshot of the named commit:

  • Full gate on 56e7382dc (fmt, cargo clippy --workspace --all-targets -- -D warnings, scripts/oneiron/gate.sh test), job 1024-060058-714a: 6141 passed, 2 failed, 20 ignored. The two failures were list assertions the feature had to extend (the accept list's fork tail, the hello's capability list); fixed in 1ceeca484.
  • Full gate on 1ceeca484, job 1024-064051-94cb: 239 test binaries, 6135 passed, 0 failed, 20 ignored.
  • Full gate on 48e05f3ca (this head), job 1024-070837-3b2a: 240 test binaries, 6145 passed, 0 failed, 20 ignored; fmt and clippy clean.
  • New tests: session_engine::helper_model (order per field, parent, validation, create-key round trip, settings layer incl. project parent over global, persisted row, warn-once), rlm_child_model (order through the catalog, unknown-default fallback vs strict pinned values, inherited-level clamp, allowlist), agent_engine::tests::rlm_children (status/set, file row beats create keys, clear), rlm_surface (set_subagent_model writes its row to the session file, a clear too, refused engines write nothing; an inherited override is written once as the birth row), rlm_children_e2e::an_unpinned_spawn_runs_on_the_helper_model (real supervisor: settings default, session override, pinned model), settings menu (the_helper_model_rows_persist_a_catalog_model_and_a_thinking_level), CLI (subagent_flags_carry_the_helper_override, subagent_flags_beat_their_env_vars_field_by_field).
  • Review: two GPT-6.1 Sol passes. Round 1 found 3 bugs (the row went to the engine's in-memory manager; handles showed the raw inherited level; a project parent could not override a global level), fixed in 1ceeca484. Round 2 confirmed them and found 2 more (concurrent setters; an unsaved birth row), fixed in 48e05f3ca.

Review notes

  • Ownership: settings and the pure order in pa-core (session_engine::helper_model), catalog resolution in pa-daemon (rlm_child_model::resolve_helper, shared by the print host as resolve_child_model_unchecked was), wire vocabulary in pa-types, UI in pa-tui over the ClientSettings seam. Dependency direction unchanged.
  • Classification: new pub surface (pa_core::session_engine::helper_model, pa_daemon::rlm_child_model::{HelperRequest, ResolvedHelper, resolve_helper, resolve_helper_unchecked, helper_model_known, unknown_helper_model_notice}, two DaemonCommand variants and SUBAGENT_MODEL_CAPABILITY, four defaulted ClientSettings methods, the menu's HELPER_PARENT). No new dependency.
  • TS parity: these surfaces have no TS counterpart except subagentDefaultModel, where TS failed the spawn on an unavailable default; the fork warns and falls back to the parent's model (owner ruling, 2026-10-24). No adoption telemetry event: fork-only.
  • Not activated: no install, no daemon restart, no change to any live settings file.

@coderabbitai

coderabbitai Bot commented Oct 23, 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: e6fca8d5-4f15-49e3-aa56-b900bc7222ae

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.

@olety
olety marked this pull request as ready for review October 23, 2026 22:38
@cursor

cursor Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes default model/thinking for unpinned RLM spawns across CLI, daemon, and TUI, with new persisted session state and wire commands; spawn behavior is user-visible but guarded by capability checks and extensive tests.

Overview
Spawned agents (rlm.spawn / rlm.create_session) that omit model or thinking no longer always inherit the parent. A layered helper model picks each field from the spawn call, then session override, then settings (subagentDefaultModel / new subagentDefaultThinking), then the parent; parent at override or settings stops at the parent's value.

CLI and session control: --subagent-model / --subagent-thinking and PRIME_SUBAGENT_* env vars apply to print, json, hosted, and interactive launch; /subagent-model and Models-tab Helper model / Helper thinking rows set the same override or global defaults. Unknown default models warn once and fall back to the parent (spawn-pinned models stay strict); thinking clamps to the resolved helper model. Spawn handles now include thinking.

Daemon: Capability subagent_model adds get_subagent_model_status / set_subagent_model; overrides ride child creates as subagentModel/subagentThinking, persist as subagent_model_state, and hosted clients refuse daemons without the capability. Core logic lives in pa_core::session_engine::helper_model with resolution in rlm_child_model::resolve_helper.

Reviewed by Cursor Bugbot for commit 48e05f3. 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 1 potential issue.

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 48e05f3. Configure here.

let thinking = match (plan.thinking.value, plan.thinking.source) {
(Some(level), HelperSource::Spawn) => {
assert_thinking_supported(agent_dir, Some(&level), &selector)?;
Some(level)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spawn thinking parent not rejected

Medium Severity

A spawn-pinned thinking of parent (or any string that is not a real level) passes the supposed strict spawn check and is written onto the child as its thinking level. assert_thinking_supported returns success for an unparseable level, unlike a spawn-pinned model, which fails catalog resolution. The new parent sentinel makes this a reachable helper-model value, so a daemon child can start with thinking parent instead of inheriting or being refused.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 48e05f3. Configure here.

olety added 3 commits October 24, 2026 08:45
A spawned agent (rlm.spawn, rlm.create_session) that names no model or
thinking level ran on its parent's. Each field now resolves in order:
the spawn call, the session override, the settings file, the parent.

- Settings: subagentDefaultModel (already parsed, never read) and the new
  subagentDefaultThinking. A spawn reads them when it runs.
- Session override: --subagent-model/--subagent-thinking, else
  PRIME_SUBAGENT_MODEL/PRIME_SUBAGENT_THINKING, for print/json and
  --daemon-hosted runs and the interactive launch; /subagent-model in the
  TUI. The value parent keeps the parent's value. RLM children carry the
  override (create keys subagentModel/subagentThinking) and a daemon
  session persists it as a subagent_model_state row.
- An unknown default model warns once and falls back to the parent's
  model; a default thinking level clamps to the helper's model. The spawn
  call's own values keep their strict checks; the allowlist still applies.
- Paths: the daemon host's spawn and create_session, the direct print
  host's spawn. Factory states and goal runs go through them.
- Daemon wire: get_subagent_model_status/set_subagent_model behind the
  subagent_model capability.
- /settings Models tab: Helper model and Helper thinking.
- Visibility: spawn and create-session handles carry thinking beside
  model; /subagent-model names each value's layer.
Review fixes for the helper-model setting:

- The override row went into the engine's in-memory session manager,
  which never reaches disk. The worker now writes it to the session file
  it owns: every set_subagent_model (a clear included) and an RLM child's
  inherited override once, as its birth row. A resume, respawn or idle
  wake reads the latest row ahead of the create keys.
- Spawn and create-session handles reported the parent's raw thinking
  level. The inherited level now clamps to the child's catalog model, so
  the handle shows the level the child runs at.
- subagentDefaultThinking is validated text, so a project file's parent
  can override a global level.
- The fork's two daemon commands sit at the tail of the accept list, and
  the hello's capability list names subagent_model.
Second review round:

- set_subagent_model applied the live override, awaited, then appended
  the row, so two concurrent setters could leave the file's latest row
  different from the live value. The live apply and the durable append
  now happen together under the worker's core lock; the status is read
  after.
- An RLM child whose inherited birth row failed to write was still
  created, and lost the override on its next file-only open. The create
  now fails instead. A session without a file is unaffected.
@olety
olety changed the base branch from upstream-sync-20261024 to rust-oneiron October 23, 2026 23:45
@olety
olety force-pushed the oneiron/helper-model-setting branch from 48e05f3 to 0c6c3f2 Compare October 23, 2026 23:45
@olety
olety merged commit fc8c2b4 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