Skip to content

🤖 fix: inherit calling-chat Exec settings in Exec sub-agents - #4080

Merged
ThomasK33 merged 8 commits into
mainfrom
exec-model-1xpw
Sep 4, 2026
Merged

🤖 fix: inherit calling-chat Exec settings in Exec sub-agents#4080
ThomasK33 merged 8 commits into
mainfrom
exec-model-1xpw

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Make inherited Exec sub-agents use the calling chat's saved Exec model, thinking, and reasoning settings rather than letting global UI Exec defaults override them. Explicit invocation and sub-agent-profile overrides still win. Plan/Explore/custom agents and kind: "workspace" interactive turns retain their existing resolution rules.

Implementation

  • Add one field-wise parent-workspace-exec resolver tier, scoped to target exec + profile subagent, between explicit sub-agent defaults and global Exec defaults.
  • Read that tier from the immediate caller's raw persisted Exec bucket. Legacy aiSettings qualifies only when the caller explicitly identifies as Exec. Plan-to-Exec handoff uses the transitioning workspace's choice.
  • Keep created child settings pinned through queueing, reload, nested delegation, and reactivation.
  • Keep metadata-only Plan/Exec display fallbacks ephemeral; otherwise reading metadata manufactures a persisted Exec preference from a Plan-only workspace. Genuine legacy metadata buckets still migrate, and existing saved buckets are not rewritten.
  • Show symbolic calling-chat inheritance in Settings and defer capabilities until the inherited model is known. Mode-only Standard/Pro overrides remain explicit; the inherit action resets reasoning without changing callback contracts.

Validation

  • After Codex feedback: 713 affected tests and the full-app integration pass; make static-check remains green. Omitted workspace reasoning mode stays Standard, and explicit canonical Exec overrides survive save/reload even when equal to global defaults.
  • make static-check passed, including both TypeScript projects, lint, formatting, and import/link/shell/Docker checks.
  • On Bun 1.3.5: 456 TaskService tests, 195 resolver/config/Settings tests, 4 pinned-order fixture tests, and the full-app inheritance integration all passed.
  • Resolver/config tests, Settings UI tests, service lifecycle regressions, and a full-app picker → actual task execution → child provider-request regression.
  • Full-app Storybook desktop/phone interactions, plus recorded live desktop and 375px mobile checks.
  • Live acceptance: global anthropic:claude-fable-5-1, caller openai:gpt-6-astra, task model: null / thinking: null → successful child gpt-6-astra provider request (via Coder routing).
  • Changing the caller to Gemini Flash changes new children, while reactivating the original child keeps Astra. Explicit sub-agent defaults, per-call overrides, and reset were also verified against actual provider requests.
  • Local validation uses the repository/CI-pinned Bun 1.3.5. The older ambient Bun 1.2.15 produced 17 asynchronous cleanup errors; the same complete TaskService suite passes cleanly on the pinned version.

Validation prerequisite

The Unit CI job also exposed five flat-sidebar test mocks missing the required archivingWorkspaceIds set. The fixtures now supply empty sets (five added lines); all 49 sidebar tests pass, with no sidebar product change.

This branch also removes one obsolete getSessionDir mock member from the existing cross-project pinned-order test fixture. It is not part of Config anymore and prevented baseline typechecking. This is a test-only deletion; runtime pinning behavior is unchanged and its four tests pass.

Risks and scope

The provenance boundary is deliberate: a missing raw Exec bucket must not be mistaken for an explicit chat preference. Previously persisted synthesized buckets are indistinguishable from genuine saved choices and remain untouched. No config schema, new inheritance enum, or child migration is introduced. Inherited preferences are still normalized against the final model at launch.

Recorded verification

Inherited task and actual Astra child:

Inherited Exec task completed as GPT-6 Astra

Astra child with Coder routing

Mobile reasoning controls:

Calling-chat inheritance controls at 375px

Live inheritance:

02-live-spawn.webm

Explicit overrides and reset:

04-overrides-plan.webm

All evidence uses an isolated disposable sandbox, now stopped and removed. Plan-mode Exec spawning was correctly restricted; the handoff branch is covered by deterministic service tests instead of bypassing Plan permissions.


📋 Implementation Plan

The original approved plan follows verbatim. Its planning-time validation status is historical; implementation and verification are summarized above. Metadata write-back removal and the test-only typecheck repair are the documented adjustments.

Exec sub-agents must inherit the calling chat’s Exec selection

1. Outcome, scope, and recommendation

Fix the behavior, not just the label. When Sub-agents → Exec → Model is inherited, an Exec sub-agent should use the Exec model selected in its calling chat. A global UI Exec default is a fallback, not an override of that chat’s choice.

Concrete acceptance case:

  • Global UI Exec default: anthropic:claude-fable-5-1.
  • Calling chat’s Exec selection: openai:gpt-6-astra.
  • Exec sub-agent model: inherited; task invocation has no model override.
  • Result: the child’s actual model request uses GPT-6-astra, not Fable.

Recommended approach: insert one narrowly scoped, per-field calling-workspace Exec candidate into the existing shared resolver; thread it through the existing TaskService resolution seam; update settings to describe dynamic inheritance. Reuse the existing persisted workspace settings and child launch snapshots. No new stored inheritance enum, migration, configuration switch, runtime snapshot transport, or duplicated resolver.

Estimated net product-code change: +80–150 LoC across resolver/types/adapter/service and settings/reasoning controls, excluding tests, stories, and this plan. Backend portion approximately +30–55; UI portion approximately +50–95. Reuse existing helpers to stay near the lower end. This is an estimate, not a line-count target.

This is a planning-only deliverable. Implementation requires Exec mode. Do not change the personal running instance, rewrite existing child history, commit/push, or create a PR as part of this planning request.

2. Verified diagnosis

Baseline: branch exec-model-1xpw, HEAD 2a1b5b267c0133f3d89d023a55d1c2b725d70386, clean working tree during investigation. Line references below are navigation anchors for that revision.

Boundary Verified behavior and relevant code
Settings src/browser/features/Settings/Sections/TasksSection.tsx:1059–1108 leaves agentAiDefaults.exec.subagent.modelString absent for inheritance, but displays agentAiDefaults.exec.modelString as the inherited concrete model.
Chat picker src/browser/features/ChatInput/index.tsx:664–718 saves a workspace/per-agent preference and calls workspace.updateAgentAISettings; WorkspaceModeAISync.tsx:48–100 restores per-agent selections on a mode switch. A saved Exec bucket survives switching to Plan.
Send options src/browser/hooks/useSendMessageOptions.ts:31–93 builds active model/thinking/reasoning/agent options. The active Plan model is distinct from the chat’s saved Exec selection.
Tool runtime src/node/services/turnRequestBuilder.ts:1685 and src/node/services/tools/task.ts:96–110 expose the active turn model as parentRuntimeAiSettings. It is not necessarily the Exec model.
Task resolution src/node/services/taskService.ts:1892–1957 builds fallback layers from target-agent bucket, active-agent bucket, and legacy aiSettings, then calls the node resolver with profile: "subagent".
Actual bug src/common/utils/ai/resolveAgentAiSettings.ts:72–155 orders config-subagent → global config → definitions/ancestors → parentRuntime → generic workspace fallbacks. An unset Exec sub-agent model therefore selects global Fable before reaching the calling chat’s GPT-6-astra.
Raw versus display metadata src/node/config/index.ts:3198–3205 expands a missing aiSettingsByAgent into both Plan and Exec buckets from legacy aiSettings. This can make a Plan-only workspace appear to have an Exec preference. Raw loadConfigOrDefault() workspace entries do not perform that expansion (normalizePersistedWorkspace, lines 654–704). The new candidate must use raw entries.
Isolation Production profile: "subagent" resolution is centralized in TaskService.resolveTaskAISettings. kind: "workspace" uses workspaceTurnManager.ts:1230 with profile: "interactive"; it is a separate contract and must remain unchanged.
Lifetime Single/grouped task creation already persists resolved aiSettings and task model/thinking fields; queued starts and sendTreeMessage reuse child-owned settings (taskService.ts:2998–3130, 3925–3965, 5178–5197, 10158–10175).

Reproduction: a read-only call to the real resolver returned Fable with source { tier: "config", agentId: "exec" }. The assertion-based reproduction below exited 1 with AssertionError, actual anthropic:claude-fable-5-1, expected openai:gpt-6-astra. This confirms the resolver mismatch, not a live provider/UI end-to-end reproduction.

Minimal reproduction command (same executed resolver inputs/assertion)
bun -e '
import assert from "node:assert";
import { resolveAgentAiSettings } from "./src/common/utils/ai/resolveAgentAiSettings";
const resolved = resolveAgentAiSettings({
  targetAgentId: "exec",
  profile: "subagent",
  agentAiDefaults: { exec: { modelString: "anthropic:claude-fable-5-1" } },
  parentRuntime: { model: "openai:gpt-5-pro" },
  fallbacks: [
    { model: "openai:gpt-6-astra", thinkingLevel: "high" },
    { model: "openai:gpt-5-pro", thinkingLevel: "medium" },
    { model: "openai:gpt-5-pro", thinkingLevel: "medium" },
  ],
});
assert.strictEqual(resolved.selected.model, "openai:gpt-6-astra");
'

Existing baseline tests executed: TasksSection.ui.test.tsx 21 passed, TasksSection.test.ts 5 passed. No full typecheck, backend fix validation, or live dogfood has been performed during planning.

3. Behavioral contract

3.1 Meaning of inheritance

  • Exec chat → Exec child: use that calling chat’s saved Exec model/thinking/reasoning unless a higher-priority child override supplies the field.
  • Plan chat → Exec child: use the same chat’s Exec bucket, not the active Plan model. Merely switching modes must not erase the Exec preference.
  • No authentic saved Exec selection: the new tier is absent. Retain the existing lower-tier resolution order, with global UI Exec defaults before runtime/generic fallbacks. This fix does not remove every pre-existing Plan-runtime fallback when all higher layers are absent.
  • Legacy/nested Exec caller: when there is no raw persisted Exec bucket, raw legacy aiSettings may supply the new candidate only when persisted agentId (or legacy agentType if agentId is absent) explicitly identifies Exec. Do not default unknown mode to Exec. Do not trust synthesized display metadata or infer provenance from whether Plan/Exec values happen to match.
  • Nested delegation: inherit from the immediate calling workspace, not the root chat. An explicitly configured/overridden child becomes the parent of its own future children.
  • Other agents: no change for Plan, Explore, Desktop, custom agent IDs, or custom agents merely based on Exec. No change to interactive/default workspace-turn resolution.

3.2 Exact precedence, independently for each field

For normalized target exec and profile subagent only:

  1. Explicit invocation overrides.
  2. Target workspace settings, when supplied by an existing resolution path.
  3. Explicit global Exec sub-agent profile fields (agentAiDefaults.exec.subagent).
  4. Calling workspace’s Exec settings — new candidate.
  5. Global UI Exec fields (agentAiDefaults.exec).
  6. Existing definition, ancestry, runtime, generic fallback, and system-default candidates, in their current order.

Missing/invalid-empty fields continue down the existing field-selection rules; do not treat a partial object as an all-or-nothing override. Model-only or thinking-only overrides must not erase other inherited fields. Keep alias/provider normalization, thinking policies, selected-versus-effective reasoning, and pro capability checks in their existing final resolution stage.

3.3 Timing and continuation

  • Resolve inheritance from the raw persisted parent workspace entry in the existing config snapshot used during task creation, not display metadata, a new turn-start snapshot, or the later dequeue time.
  • A picker change persisted before that snapshot affects subsequent new children. An already-created child keeps its selected model through queueing, reload, and task_send_message reactivation.
  • A concurrent picker update need not retroactively change a task whose config snapshot was already read. Tests synchronize on update completion; no sleeps or invented timing guarantees.
  • createMany uses one existing config snapshot for the whole batch and its saved launch plans; do not reload config per sibling.
  • Plan → Exec handoff already re-enters resolveTaskAISettings (taskService.ts:11599). Treat that as a new Exec settings resolution from the transitioning workspace, not as ordinary same-agent reactivation. Preserve current handoff behavior otherwise.

4. Implementation sequence and intermediate gates

Phase A — Lock the reported regression down before product edits

  1. Extend src/common/utils/ai/resolveAgentAiSettings.test.ts with conflicting global and calling-chat models. First reproduce the present fallback behavior against the desired outcome; after introducing the named context input, adapt the fixture to use that input without weakening the expected result.
  2. Add a service-level regression in src/node/services/taskService.test.ts near existing Exec sub-agent defaults coverage (around line 6436): real persisted parent metadata has separate Plan/Exec models, global Exec is a third model, and the Exec task has no model override.
  3. Assert the child launch options and persisted model selection, not prose, prompt strings, or the child model’s self-identification. Use existing fixtures and real createTestHistoryService() whenever history is involved.
  4. Cover single-create and the shared resolver path before broadening lifecycle coverage. Keep tests provider-free; reuse existing deterministic model/router seams where necessary rather than introducing a mock-heavy service harness.

Gate A: the targeted regression fails specifically because Fable/global wins over GPT-6-astra/chat Exec. Capture the test command and failure. A compiler error or unrelated environment failure is not a valid red result.

Phase B — Add the narrowly scoped resolver input and wire it once

  1. src/common/types/agentAiSettings.ts
    • Add parentWorkspaceExecSettings?: AgentAiSettingsLayerValues to ResolveAgentAiSettingsInput.
    • Add provenance tier "parent-workspace-exec" to AiSettingTier; identify agentId: "exec" for this source.
    • Document that this input is calling-workspace context, not an explicit tool override and not the active parent runtime model. Audit exhaustive consumers of AiSettingTier and update only those that need the new member.
  2. src/common/utils/ai/resolveAgentAiSettings.ts
    • Insert the new candidate between the target’s sub-agent profile and its ordinary configured defaults.
    • Require both profile === "subagent" and normalized target ID exec. Do not activate merely because an ancestor being visited is named Exec or the implicit reasoning-only fallback reaches Exec.
    • Insert independently of whether any agentAiDefaults.exec entry exists; the existing early return for missing config must not suppress the candidate.
    • Reuse independent field selection and the final model-aware thinking/reasoning normalization. Add a short rationale comment explaining why a chat-specific Exec choice outranks global defaults but not explicit child overrides.
  3. src/node/services/agentDefinitions/resolveNodeAgentAiSettings.ts
    • Add the same optional context input to the node adapter and forward it unchanged. Preserve alias/model/provider handling.
  4. src/node/services/taskService.tsresolveTaskAISettings
    • Add a required parentWorkspaceId to this private method’s parameters. Update its three callsites: createMany (~2999), create (~3863), and plan handoff (~11599, use args.workspaceId). Each already has the ID.
    • Retrieve the raw parent with the existing findWorkspaceEntry(params.cfg, params.parentWorkspaceId)?.workspace (taskUtils.ts:166–178). params.cfg is already loaded raw config: this is an in-memory lookup with no extra I/O, IPC, or metadata expansion. The entire grouped launch keeps the same config snapshot.
    • Use raw.aiSettingsByAgent?.exec when present, regardless of the parent’s active mode or whether Plan and Exec values are identical. Do not require a model/thinking field to be present before passing a partial layer; reasoning-only values must remain valid.
    • If the raw Exec bucket is absent, normalize persisted agentId with an empty fallback, consulting legacy agentType only if agentId is absent. Only explicit exec allows raw legacy aiSettings. Do not call a resume helper that defaults missing identity to Exec. Missing raw entry gives no new candidate and does not alter existing missing-parent handling.
    • Never derive the new tier from parentMeta, getAllWorkspaceMetadata(), or getWorkspaceMetadata(): those may fabricate Exec values from Plan. Never use equality of bucket values as a provenance heuristic.
    • Use existing conversion/normalization helpers. Do not mutate metadata, move generic fallbacks, manufacture explicit.model, add another config load, or change global defaults. Preserve the existing parentMeta fallback path at its current lower priority.
    • Single create, grouped create, workflow-driven taskService.create, and plan handoff inherit the fix through this seam. Keep kind: "workspace" and other interactive consumers untouched.
  5. Preserve existing child persistence and lifecycle. Do not add a new persisted source token, rematerialize global defaults, migrate existing children, or re-resolve inherited models on dequeue/resume. Use tests to prove the existing child aiSettings/task model snapshot suffices.

Defensive programming: use typed optional layer values and explicit scope guards; preserve existing validation of persisted/user-controlled settings. Assertions belong to impossible internal invariants, not missing settings or malformed user config. No startup-fatal checks or silent fallback to a different provider to hide a model error.

Gate B: the original resolver/service regression is green; precedence, Plan isolation, no-config, custom-agent, and lifecycle tests below pass. Inspect the diff for any changed non-Exec resolution order before proceeding.

Phase C — Make settings accurately represent dynamic inheritance

Primary file: src/browser/features/Settings/Sections/TasksSection.tsx, especially renderExecSubagentDefaults and its AiDefaultsControls call.

  1. Use an unambiguous inherited/reset label such as “Use calling chat’s Exec” for the Exec sub-agent controls. Keep the global UI Exec card unchanged.
  2. Replace Inherits from UI Exec: <global model> and the concretely clamped inherited thinking hint with symbolic descriptions. Explain:
    • Unset fields use the calling chat’s Exec settings at task creation.
    • Global UI Exec defaults are used when no chat Exec selection exists.
    • Enabled/advisor settings remain shared with UI Exec.
  3. Do not consult the currently active workspace’s Plan model to predict a universal settings value. Global Settings applies to many chats; it cannot display a single dynamically inherited model truthfully.
  4. Deferred model capability handling: when the sub-agent model is inherited, do not use global Fable (or a fake/empty model string) to clamp explicit thinking choices or hide supported reasoning preferences.
    • Extend AiDefaultsControlsThinkingSelectorControl (src/browser/components/ThinkingSelector/ThinkingSelector.tsx) with optional modelCapabilitiesDeferred?: boolean, defaulting false. Enable it only for the Exec sub-agent card with inherited model.
    • In deferred mode reuse THINKING_LEVELS from src/common/types/thinking and existing generic option labels, preserving explicit selections without model-specific clamping. Permit the saved Pro preference, but do not invent provider-specific Fast capabilities. Explain that actual model capabilities are enforced at launch. Branch capability/label computation explicitly; do not pass a fake or empty model as a sentinel.
    • Add optional reasoningModeInherited?: boolean, set on the Exec card from entry?.reasoningMode === undefined, independently of model/effort inheritance. Pass only the explicit mode or a non-authoritative "standard" control placeholder—not uiExecEntry.reasoningMode. The inherited flag must suppress any claim that Standard/Pro is the effective inherited mode.
    • Represent unset mode symbolically in the existing Pro row (accessible mixed toggle state and an inherited description), distinct from explicit Standard or Pro. Keep the current callback type: clicking inherited → explicit Pro; Pro → explicit Standard; Standard → Pro. The existing inherit/reset action restores unset reasoning. Do not introduce a three-way click cycle or widen every caller’s callback to accept undefined.
    • Choosing only a thinking level must leave mode unset if it was unset. A mode-only override with inherited effort must remain visibly explicit; the trigger must not misleadingly claim that all reasoning fields are inherited. Cover both Standard-only and Pro-only overrides.
    • When an explicit concrete sub-agent model is selected, retain current model capability filtering and final resolver normalization. Every other caller retains its existing binary controls, labels, and capability behavior.
  5. Keep reset semantics: resetting model removes only its override; choosing inherited reasoning clears the relevant thinking/reasoning overrides as today. Rendering or opening settings must not materialize inherited defaults into subagent.
  6. Do not alter persisted schemas or config-only merge helpers to implement runtime inheritance. In particular, deriveLegacySubagentAiDefaultsProjection (src/common/types/agentAiDefaults.ts:155–174) is called only by config save for downgrade compatibility (src/node/config/index.ts:2072), not by runtime routing. Leave that projection intact; global config cannot serialize a per-chat dynamic value.

Gate C: settings UI tests pass, including reset/persistence and dynamic capability behavior. A full-app story and mobile screenshot demonstrate that the longer wording wraps without overflowing and keyboard access remains intact. Avoid copy-literal-only tests; prove selection, omission, reset, and capability behavior.

5. Regression matrix

Use deliberately different supported model IDs for competing layers; never let identical fixture values mask the winning source. Check effective model plus provenance in resolver tests and persisted/launched settings at the service boundary.

Case Required assertion
Reported bug Global Fable + chat Exec Astra + inherited child → Astra from parent-workspace-exec.
Plan caller Active Plan model differs from both; saved Exec bucket wins, not parent runtime.
No global Exec entry Saved chat Exec still wins; missing config does not skip the new candidate.
No chat Exec bucket Existing global/default resolution is retained; no promoted Plan bucket.
Legacy/nested caller Raw Exec caller with only aiSettings supplies the new tier; raw Plan/unknown caller does not, even if display metadata synthesized an Exec bucket. Explicit non-Exec agentId must beat a stale legacy agentType: exec.
Authentic equal buckets A raw Plan caller with genuinely persisted Exec and Plan buckets that have identical values still inherits the Exec bucket; no value-equality provenance heuristic.
Raw entry missing No new candidate; existing caller validation/fallback remains unchanged, with no metadata-based guess.
Explicit invocation Task model still outranks configured sub-agent and chat settings.
Explicit sub-agent defaults Sub-agent profile model still outranks the chat’s Exec model.
Partial overrides Sub-agent thinking only + chat model; sub-agent model only + chat thinking; partial chat bucket falls through per field.
Model policy Incompatible thinking is normalized against the final model; selected pro preference is retained while effective mode is capability-gated.
Scope guard Interactive Exec, non-Exec target, and custom Exec-based agent with frontmatter/config are unchanged even if the new input is supplied.
Workspace tier Existing target-workspace settings, when supplied, still outrank configured/chat defaults.
Grouped and queued All child launch plans persist the creation-time choice; changing parent/global settings before dequeue does not retarget them.
Nested/reawakened Grandchild uses immediate Exec parent’s saved settings; reawakening an existing child preserves its settings despite parent changes.
Mode handoff Plan → Exec continuation resolves from the transitioning workspace’s Exec preference, while ordinary same-agent resume stays pinned.
Settings controls Inherited state stays sparse, reset deletes overrides, dynamic model does not hide/clamp preferences using global Fable. Unset mode renders mixed rather than global Pro; selecting effort preserves unset mode; explicit Pro/Standard and mode-only overrides survive round trips and reset correctly.
Initial send persistence A first Exec send with no separate picker-change event persists its selected model before tools execute (workspaceService.ts:10914, 9493–9507, 9602–9608), and the child inherits that model.
UI-to-backend boundary Selecting Exec model A in the chat UI then spawning an inherited Exec child uses A; selecting B and creating a new child uses B. Existing children remain A.

Place pure precedence/policy cases in resolveAgentAiSettings.test.ts; adapter forwarding in resolveNodeAgentAiSettings.test.ts; lifecycle/launch cases in taskService.test.ts using existing fixtures. Extend tools/task.test.ts only if a missing tool-forwarding assertion is necessary—do not duplicate service coverage.

For the UI-to-backend boundary, use a full-app tests/ui test with the existing mock AI router if practical, otherwise an IPC integration test plus recorded live browser verification. UI tests must drive actual picker actions, not mutate local storage or call the tested backend action directly. IPC setup may seed global defaults and disposable metadata. Do not use provider keys for pure routing tests.

6. Validation commands and release gates

Run the narrow red test first, then the complete affected files:

bun test src/common/utils/ai/resolveAgentAiSettings.test.ts
bun test src/node/services/agentDefinitions/resolveNodeAgentAiSettings.test.ts
bun test src/node/services/taskService.test.ts
bun test src/browser/features/Settings/Sections/TasksSection.test.ts \
  src/browser/features/Settings/Sections/TasksSection.ui.test.tsx

Also run colocated ThinkingSelector tests and affected config compatibility tests if those contracts are touched. Run the selected integration file with TEST_INTEGRATION=1 bun x jest <test-file> --runInBand using the repository harness. After the final edit, including formatter changes:

make typecheck
make static-check

Use existing Makefile Storybook/Pixel targets for the added/updated full-app story; verify the target names when implementing. Add a full-app src/browser/stories/App.taskSettings.stories.tsx rather than extending isolated component story coverage alone. Pin desktop and phone Pixel variants, mirror local viewport globals, and ensure breakpoint-dependent play tests run at a forced narrow container width or guard on rendered width.

Capture each gate’s exit status directly. Do not infer success from a pipeline’s final filter. Report unrelated baseline/toolchain failures separately and do not make unrelated repository fixes to get a green report.

Final gate: all targeted/static checks and the actual UI-to-child dogfood below pass. No claim of a fixed bug based solely on settings text or a green pure resolver test.

7. Dedicated dogfooding plan and review evidence

Isolated environment

  1. Read dev-server-sandbox, tests, and agent-browser skills; load CLI usage with agent-browser skills get core before browser commands.
  2. Start a bounded background sandbox:
    KEEP_SANDBOX=1 make dev-server-sandbox \
      DEV_SERVER_SANDBOX_ARGS="--clean-projects"
    This gives a separate temporary XUM_ROOT and free backend/Vite ports. Use only a disposable scratch project and sandbox config. Provider seeding may include credentials: never show or upload provider config, cookies, or secrets.
  3. Follow the printed URL with an isolated agent-browser session. Read readiness deterministically from server output. Do not kill or modify the user’s normal Xum instance.
  4. Before product edits, capture the baseline reproduction if live providers are available; otherwise capture the deterministic failing regression and explicitly label live baseline evidence unavailable.

User-visible scenarios

  1. In sandbox Settings, set global UI Exec to Fable and Exec sub-agent model/reasoning to inherited. Capture the settings card.
  2. In a new chat, select GPT-6-astra in Exec mode. Request a tiny Exec sub-agent with model/thinking omitted, e.g. a read-only repository-name check. Inspect the task arguments to ensure the model did not pass an explicit override that would invalidate the test.
  3. Open the child and verify its picker/status and persisted launch model. Enable sandbox API Debug Logs and correlate the actual request with the child ID; verify the effective provider/model (allowing documented alias normalization). Never rely on the model saying “I am Astra.”
  4. Switch the parent to Plan with a different model, keeping its saved Exec choice. Launch Exec through an allowed task route; if Plan restrictions prevent it, use the existing approved plan-to-Exec handoff. Do not bypass plan-mode safety merely to run the demo. Verify Exec selection, not Plan/global selection, governs the Exec resolution.
  5. Change parent Exec to a second available model and spawn a new inherited child. It follows the new selection; reawaken the earlier child and verify it remains pinned to its earlier model.
  6. Set an explicit Exec sub-agent model in Settings and repeat; it overrides the chat. Reset it and repeat; chat inheritance resumes. Also verify a per-call explicit model override wins.
  7. Exercise one queued/grouped launch and one nested Exec delegation where feasible. Corroborate their model choices with launch metadata/request logs; do not replace deterministic lifecycle tests with flaky multi-agent prompts.
  8. Check settings and child-task UI at desktop and approximately 375px width (390px pinned Storybook phone variant). Exercise keyboard selection/reset and confirm no clipped controls, falsely concrete inherited hints, or right-edge overflow.

Artifacts and completion

  • Record a video spanning settings → chat picker → omitted-override task call → child model evidence; capture screenshots of those key states, including mobile.
  • Use the installed browser CLI’s recording/screenshot commands; attach screenshots and the video with attach_file. Save a concise redacted manifest mapping scenario, parent/child IDs, configured/expected/actual model, and request evidence.
  • If provider access prevents an actual model request, report the exact blocker; provider-free routing tests and screenshots are not proof of a live provider request. Use alternate configured models for supplemental tests without claiming they reproduce the exact named-model case.
  • If later authorized to publish on GitHub, upload evidence with gh ... --attach; do not open a PR or post evidence now.
  • Stop the sandbox/browser and any validation monitors started for this work. Keep only redacted reviewer artifacts; handle retained sandbox data as credential-bearing local state.

8. Non-goals and rejected shortcuts

  • Copy-only fix: would explain the old behavior but fails the user’s requested behavior.
  • Promote parentRuntime or all fallbacks: risks inheriting Plan instead of Exec and overriding custom-agent defaults.
  • Pass chat model as explicit override: incorrectly bypasses the configured Exec sub-agent override and obscures provenance.
  • Global resolver reordering / descendant-wide changes: unnecessary blast radius; custom/ancestor semantics are out of scope.
  • New inheritance setting, migration, or turn snapshot: existing sparse settings and task snapshots are sufficient. Existing children stay unchanged; users who want global fixed Fable can explicitly set that sub-agent model.

9. Advisor review record

Final status: APPROVED with high confidence by advisor openai:gpt-5.5-pro / xhigh.

Review sequence:

  1. Architecture review endorsed the targeted resolver candidate, conditional on strict scope, per-field precedence, lifecycle pinning, and honest dynamic UI.
  2. Detailed review identified two blockers: synthesized metadata cannot establish a genuine Exec preference, and unset reasoning mode must not display global Pro as its concrete value.
  3. Verified raw config access/all three caller IDs and amended the plan to use findWorkspaceEntry(cfg, parentWorkspaceId), strict legacy identity, symbolic mode inheritance, and deferred capabilities without changing existing callback contracts. Added corresponding regression cases to the plan.
  4. Final review explicitly returned “APPROVED with high confidence” and “No remaining design blockers.” (An empty intermediate advisor response was retried; it was not counted as approval.)

All completed investigation reports have been incorporated. Earlier exploratory suggestions to trust expanded parent metadata, use an empty model as a capability sentinel, or widen the reasoning callback for a three-state click cycle are superseded by the reviewed design above.

Approval is for this implementation plan only. The bug has been reproduced at the resolver boundary; implementation, post-fix tests/static checks, and live recorded dogfooding remain to be performed. Pay particular attention during implementation to exhaustive AiSettingTier consumers and raw agentId/agentType normalization without an Exec default.


Generated with mux • Model: coder:openai/gpt-6-astra • Thinking: high

Keep explicit child overrides ahead of the chat's saved Exec choice and global defaults behind it. Preserve raw preference provenance without persisting display-only legacy buckets; cover queueing, nested delegation, reactivation, and handoff.

---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=26.07 -->
Keep delegated Exec defaults symbolic until launch, preserve explicit Standard/Pro choices independently of effort, and cover the full-app desktop/phone settings flow.
Drive real model picker and send interactions, execute real task tools with a provider-only SDK fake, and check child provider requests plus persisted settings across two Exec selections. The regression intentionally fails on the unfixed baseline: both children use the global fallback.
Explain creation-time resolution and the UI Exec fallback when the calling chat has no saved selection.

---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=46.66 -->
Remove a stale fixture member that blocks the inherited-Exec branch's pre-push typecheck. Session lookup no longer belongs to Config; pinned-order behavior is unchanged and its tests pass.

---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=58.46 -->
@chatgpt-codex-connector

This comment has been minimized.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review


Generated with mux • Model: coder:openai/gpt-6-astra • Thinking: high

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05aa0921d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
Comment thread src/common/utils/ai/resolveAgentAiSettings.ts
The five flat-list useWorkspaceActions overrides dropped the required archivingWorkspaceIds set from the shared fixture, causing row rendering to throw before their assertions. Match the empty-set default provided by WorkspaceContext without changing product behavior.

---
_Generated with `xum` • Model: `coder:openai/gpt-6-astra` • Thinking: `high` • Cost: `$24.82`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=24.82 -->
Preserve Standard when older workspace Exec settings omit reasoningMode. Keep explicitly authored Exec subagent fields even when they equal global defaults, while retaining legacy-only mirror cleanup and other agents' sparse normalization. Add configuration round-trip and child-launch regressions for both Codex findings.

---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_

<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=95.18 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Both previous findings are addressed and resolved. Please review the latest head.


Generated with mux • Model: coder:openai/gpt-6-astra • Thinking: high

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 87254a25db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 4, 2026
Merge current main and keep its single archivingWorkspaceIds fixture entry. Both branches independently fixed the same five mocks, and Git's textual merge produced duplicate properties that failed merge-group TypeScript checks.

Validation: make static-check-full; 713 targeted tests; full-app Exec inheritance integration.

---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_

_Generated with `xum` • Model: `coder:openai/gpt-6-astra` • Thinking: `high` • Cost: `$130.15`_
<!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=high costs=130.15 -->
@ThomasK33
ThomasK33 removed this pull request from the merge queue due to a manual request Sep 4, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

🤖 Re-review current head 3c80fa9cdc972b4f5b339b9c9307795f164c4acb after merging current main and reconciling five overlapping sidebar fixture fixes. The merge-group TypeScript failure reproduced locally and is fixed; full static checks, 713 targeted tests, and the full-app Exec inheritance integration pass.


Generated with mux • Model: coder:openai/gpt-6-astra • Thinking: high

Generated with xum • Model: coder:openai/gpt-6-astra • Thinking: high • Cost: $130.15

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

🤖 Please review current head 3c80fa9cdc972b4f5b339b9c9307795f164c4acb after integration with main.


Generated with mux • Model: coder:openai/gpt-6-astra • Thinking: high

Generated with xum • Model: coder:openai/gpt-6-astra • Thinking: high • Cost: $130.15

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 3c80fa9cdc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

This comment has been minimized.

@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 9607966 Sep 4, 2026
35 of 38 checks passed
@ThomasK33
ThomasK33 deleted the exec-model-1xpw branch September 4, 2026 15:28
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