🤖 feat: expose sub-agent model and thinking level in task schedule and report - #3789
Conversation
…d report Task tool results (spawned rows, completed reports) and the sub-agent report envelope now carry the child's resolved modelString/thinkingLevel, and the TaskToolCall card plus SubagentReportMessageContent render them. --- _Generated with `mux` • Model: `anthropic:claude-fable-5` • Thinking: `xhigh`_ <!-- mux-attribution: model=anthropic:claude-fable-5 thinking=xhigh -->
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b5aafc991
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9d56e5961
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ffb83dfb3
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c42b2fa70
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a1e21c0d9
ℹ️ 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
The module reads every field out of persisted tool args/results typed as `unknown`, and #3789/#3793 grew that from four to seven copies of the same "is a string, is not blank, use the trimmed form" check. Collapse those seven into a single coerceNonBlankString helper. The two task_await result reads are deliberately left alone: they validate a field is non-blank but then store the raw, untrimmed value, so routing them through the helper would change what gets persisted.
Summary
Task tool results and the sub-agent report envelope now carry the child's resolved model and thinking level, and the UI renders them: the
TaskToolCallcard shows<model> · thinking: <level>per task row, andSubagentReportMessageContentshows the same meta line on in-progress and completed report messages.Background
When a parent spawns sub-agents, the resolved model/thinking settings were invisible unless you opened the child workspace. Model and thinking overrides (
task({ model, thinking })) made this worse: there was no way to confirm from the parent transcript which settings a child actually ran with.Implementation
taskServicerecords the child's resolvedmodelString/thinkingLevelon spawn and threads them into spawned-task rows and completed-report entries of task tool results (toolDefinitions.tsschemas extended with optional fields).The sub-agent report envelope (
subagentReportEnvelope.ts) carries optionalmodel/thinkingLevel; parsing tolerates absent or malformed values so a bad producer cannot invalidate an otherwise well-formed report.TaskToolCallprefers live child-workspace metadata whenever the workspace exists (a plan child's auto-handoff to exec rewrites settings after launch, so result snapshots can go stale) and falls back to result-carried settings after cleanup; rendering reusesModelDisplayplus the existing thinking badge styling.Report-time settings (from review): completed task results read the child's settings at report time (threaded through
waitForAgentReportand the report cache/artifact), not the launch snapshot, so a plan child that auto-handoffs to exec is labeled with the exec phase's model and thinking level.Downgrade hardening (from review):
getTaskToolPartialState()now extracts referenced task IDs from persisted partial task outputs with a tolerant reader instead of the strict result schema, so future optional result fields cannot break interrupted-stream recovery bookkeeping when downgrading to this release.task_await propagation (from review): completed
task_awaitresults carry the report-timemodelString/thinkingLevel, andtaskReportLinkingfeeds them back to the original task card (precedence: live metadata, then linked report, then spawn snapshot), covering actively awaited background tasks whose synthetic report is suppressed.Narrow-card overflow (from review): the task card's AI settings row constrains long custom model IDs (
min-w-0at both flex levels plusbreak-words) so unbroken tokens wrap instead of overflowing the right edge; guarded by a 320px story with an overflow play contract.Validation
UAT in an isolated dev-server sandbox with real sub-agent spawns (foreground, background, and workspace-turn paths): verified the badge on running and completed task rows, both report surfaces, envelope contents on disk, and mobile (375px) wrapping with no right-edge overflow.
Recovery hardening is covered by a red-green regression test: a persisted task output carrying unknown future fields must not cause a duplicate fallback report; the test fails under the previous strict-parse behavior.
Risks
Low. All new schema fields are optional, so old persisted tool results and envelopes without the fields render exactly as before. Downgrading to the previous release keeps a narrow known window: a partial persisted mid-stream by this release is skipped by the old strict recovery parse, which can at worst duplicate a fallback report (same class as earlier optional additions like
groupKind/label).Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh