feat(loop): per-task model selection + per-model token spend (hew-1tq)#54
Merged
Conversation
- LoopModelConfig nested inside LoopConfig; BTreeMap for deterministic serialization, all-None / empty by default. - hew config get/set: bulk paths (loop.model.by_priority) format/parse comma-separated KEY=VAL pairs; dotted paths (loop.model.by_priority.P0) read/insert individual entries; empty value clears. - Model names unvalidated per task Craft — free-form strings passed to the spawner; runtime CLI rejects unknown ones at invocation. - 7 new tests + probe values added to keys_includes_every_settable_path.
…(hew-d1j)
- new hew_core::loop_model module with TaskRecord<'_> + resolve_model()
- precedence: <!-- hew:model=X --> tag > model:X label > by_priority[P{n}]
> by_type[issue_type] > cfg.default > None
- 13 unit tests cover malformed-tag fall-through, empty label values,
non-model labels, trimmed tag, empty issue_type skips by_type
- RunConfig gains loop_model: LoopModelConfig; run_loop_with takes it
as a new parameter, run_loop pipes it in from cfg.loop_cfg.model.
- Iter call site builds TaskRecord from the ready task and resolves
the per-iter --model / -m override before spawning.
- New integration test loop_dynamic_model.rs asserts both the
description-tag threading path (Some("opus")) and the empty-config
no-op path (None) via MockSpawner.last_opts.
Closes hew-6et.
- Add model: Option<String> to IterLog with #[serde(default)] - Populate from SpawnOpts.model_override alongside runtime_used - Fixture hew-core/tests/fixtures/iter-log-pre-model.json proves legacy iter logs parse with model=None
- Add Summary::per_model: Vec<ModelBreakdown> populated from IterLog.model - Group iters by model label (None -> "(default)"); sum input/cached/ output/total tokens, iter_count, tasks_closed - Render new "by model" table section when any iter recorded a model; hidden otherwise (no flag, no config) - Cached column folds cache_read + cache_create - 4 new tests: mixed-model run, no-model hides section, default-label for unlabeled iters, render columns present
- docs/LOOP.md gains 'Per-task model selection' section: tag > label > config precedence, TOML config example, by-model summary sample, per-model prompt-cache caveat. - CHANGELOG [Unreleased] Added entry covering the precedence chain and the conditional 'by model' breakdown table. - skills/core/hew-decompose.md Step 6 nudge: flag heavy tasks with a tag or label so hew loop routes them to a stronger model. - skills/core/hew-plan.md craft-refinement tail nudge: surface per-task model needs during planning so decomposer can annotate. Closes hew-k7j. Closes epic hew-1tq (6/6 children done).
4477314 to
a09965b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the
hew-1tqepic: dynamic per-task model selection + per-model token spend inhew loop summary.What lands
4aaf79afeat(config): loop.model.{default,by_priority,by_type} schema (hew-b1l)3d287b2feat(loop): resolve_model() pure fn with tag>label>config precedence (hew-d1j)49bd2abfeat(loop): thread per-task resolve_model into SpawnOpts (hew-6et)dd34f53feat(loop): IterLog.model field with backward-compat parse (hew-2cq)2fbfb30feat(loop): per-model breakdown table in loop_summary (hew-cdn)4477314docs(loop): per-task model selection guide (hew-k7j)How model is resolved per task
model:<name>in the task body — highest precedence.bdlabelmodel:<name>on the task.loop.model.by_type.<type>thenloop.model.by_priority.<n>from config.loop.model.default.Config surface
CLI:
Summary impact
When at least one iter records a non-null
model,hew loop summaryrenders a new "by model" table (input/cached/output/total tokens, iter_count, tasks_closed). When no iter has a model label, the section is hidden — verified against a legacy run with all-null model fields.Backward compat
IterLog.model: Option<String>— old iter logs without the field deserialize asNoneand render under "(default)" if any other iter in the run has a label..hew/loop/<run-id>/run.jsonfiles unaffected.Smoke (hew-diyp)
cargo build— clean.hew config set loop.model.default "claude-opus-4-7"thengetreturns it.hew config set "loop.model.by_priority.0/2"thenget loop.model.by_priorityreturns the CSV.hew loop summary --run-id <legacy-run>correctly hides the "by model" section when iter logs lack the field.Stacking
Based on
feat/parallel-loop-worktrees(PR #53). Both branches touchhew-core/src/loop_log.rs; landing PR #53 first avoids a textual conflict.🤖 Generated with Claude Code