Skip to content

feat(kanban): per-task model override on create - #95

Merged
exiao merged 2 commits into
live-configfrom
feat/kanban-model-override
Jul 12, 2026
Merged

feat(kanban): per-task model override on create#95
exiao merged 2 commits into
live-configfrom
feat/kanban-model-override

Conversation

@exiao

@exiao exiao commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

Finishes the half-landed per-task model override feature on Kanban create.

tasks.model_override already exists end-to-end on the read/spawn side:
the column, the Task dataclass field, the dispatcher spawn (cmd.extend(["-m", task.model_override])), and the kanban show print. But there was no write path — nothing could set the value:

  • no --model flag on hermes kanban create
  • no model_override kwarg on create_task()
  • no model property on the kanban_create tool schema

So the override could be honored but never created (grep finds zero UPDATE ... SET model_override and zero rows with a value in practice). This PR wires the write path, mirroring the existing --reasoning (NousResearch#47476) and --skill plumbing.

Changes

  • hermes_cli/kanban_db.pycreate_task() gains a model_override kwarg; added to the INSERT column list + values tuple. A blank/whitespace string coerces to NULL so the worker falls back to the profile's model.default.
  • hermes_cli/kanban.pyhermes kanban create --model <MODEL> flag + handler passthrough.
  • tools/kanban_tools.pykanban_create tool gains a model string property + handler passthrough.
  • tests/hermes_cli/test_kanban_db.py — behavior-contract test: override round-trips; omitted/blank leaves the column NULL.

No new column or migration (already present). No dispatcher change (already reads it).

Why

Lets a worker lane default to a strong model (e.g. claude-opus-4-8) while an individual simple card runs on a cheaper one, without cloning a whole profile:

hermes kanban create "one-line lookup" --assignee dev --model claude-sonnet-5-0

Test

  • pytest tests/hermes_cli/test_kanban_db.py → 283 passed (282 + new).
  • Manual E2E against a temp HERMES_HOME: --help shows --model; real CLI create --model claude-sonnet-5-0 persists to the row; create_task(model_override=...) and the kanban_create tool ({'model': ...}) both persist; blank/omitted → NULL; spawn builder emits -m.

Wire the missing write path for tasks.model_override. The column, the
Task dataclass field, the dispatcher spawn (-m <model>), and the
kanban-show print already existed, but nothing could SET the value:
no --model CLI flag, no create_task() kwarg, no kanban_create tool prop.
This finishes the feature (mirrors the --reasoning / --skill plumbing).

- create_task(model_override=...): new kwarg + INSERT column/value; blank
  string coerces to NULL so the worker falls back to the profile model.
- CLI: hermes kanban create --model <MODEL>.
- Tool: kanban_create 'model' string prop + handler passthrough.
- Test: round-trip + NULL-on-omit/blank behavior contract.

Lets a lane default to opus while an individual simple card runs on a
cheaper model, e.g. --model claude-sonnet-5-0.
@claude

claude Bot commented Jul 12, 2026

Copy link
Copy Markdown

Claude finished @exiao's task in 52s —— View job


Review — PR #95: per-task model override on create

  • Read the diff (5 files) against origin/live-config
  • Verify INSERT column/placeholder/value alignment
  • Verify blank→NULL coercion on all write paths
  • Check read path + dispatcher consume the column
  • Check prompt-cache / narrow-waist / env-var invariants
  • Confirm tests are behavior contracts

LGTM. This cleanly completes the write side of an already-landed read/spawn feature — no new column, no migration, no dispatcher change.

Verified against the focus invariants:

  • INSERT alignment is exact. Column list, VALUES (...) placeholders, and the value tuple are each 21 entries with model_override last in all three (kanban_db.py#L3146-3179). No positional drift.
  • Blank/whitespace → NULL, centralized. (model_override.strip() or None) if model_override else None at kanban_db.py:3178 handles both the CLI passthrough (getattr(args, "model_override", None)) and the tool passthrough (args.get("model") or None). The read path re-guards empty string at kanban_db.py:1063, so a stray "" never reaches -m.
  • Read + dispatch pre-exist. cmd.extend(["-m", task.model_override]) at kanban_db.py:8793-8794 already consumed the column; this PR only wires the writers.
  • No narrow-waist cost. No new core model tool — model is a new property on the existing kanban_create schema, which is byte-stable for a conversation's life. No prompt-cache mutation, no role-alternation surface, no HERMES_* env var, no hardcoded HERMES_HOME path.
  • P3 from the earlier review is addressed (9144d21): _task_to_dict now includes model_override (kanban.py:79), so create/show/list --json can read back what --model wrote — parity with the plain-text show path.
  • Tests are genuine contracts. test_create_task_persists_model_override round-trips set/omit/blank→NULL; test_kanban_json_includes_model_override covers the JSON surface. Neither freezes a model catalog.

0 blocking, 0 minor.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a per-task model override feature to the Kanban system. It adds a --model command-line argument to the task creation parser, updates the database schema and task creation logic to persist the model_override value, and exposes this parameter in the tool schema. Additionally, a unit test has been added to verify that the model override is correctly persisted and defaults to None when omitted or blank. There are no review comments, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@claude claude 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.

LGTM — write path (CLI --model, create_task(model_override=), kanban_create tool model prop) cleanly completes the already-landed read/spawn side. Verified INSERT column/placeholder/value alignment (21 each), blank→NULL coercion matches the read mapping, and the new test is a genuine round-trip behavior contract (no frozen model catalog). No new env vars, no new core tool, no prompt-cache surface. 0 blocking, 0 minor.

@exiao

exiao commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Code Review — PR #95 (per-task model override on create)

Local Codex/Claude-persona review ran the repo's rubric against live PR HEAD (7c39b1536, base live-config) in an isolated worktree, plus the real hermes kanban create CLI path and the unit suite.

Verdict: APPROVE-WITH-NOTES (0 P0/P1/P2, 1 P3). Do not merge from this lane — flagging for a write lane / Eric.

Verified (the 4 focus points all hold)

  1. INSERT alignment is correct. Column list, VALUES (...) placeholders, and the value tuple are each exactly 21 entries, with model_override last in all three. No positional drift. model_override was already a real column (kanban_db.py:1230) with an idempotent ALTER TABLE migration (:2060), so this PR adds only the write path — no new column, no migration, no cache/alternation concern.

  2. Blank/whitespace coerces to NULL on BOTH paths. The coercion is centralized in create_task: (model_override.strip() or None) if model_override else None (kanban_db.py:3178). The tool passes args.get("model") or None and the CLI passes getattr(args, "model_override", None); both flow through that same strip. The read path also re-guards empty string (:1063). Proven live — creating three cards through the real CLI produced exactly:

    • --model claude-sonnet-5-0model_override='claude-sonnet-5-0'
    • --model " "model_override=NULL
    • no --modelmodel_override=NULL
  3. --model plumbing matches the surrounding pattern. dest="model_override" (kanban.py:348) mirrors --skilldest=skills / --goaldest=goal_mode; _cmd_create reads it via getattr(args, "model_override", None) (:1376).

  4. Dispatcher read path pre-exists. cmd.extend(["-m", task.model_override]) at kanban_db.py:8793 already consumed the column; this PR completes the write side.

Tests: scripts/run_tests.sh tests/hermes_cli/test_kanban_db.py283 passed, 0 failed, including the new test_create_task_persists_model_override (covers set / omit / blank-→NULL).

P3 (non-blocking) — --json output omits model_override

_task_to_dict (hermes_cli/kanban.py:60), which serializes hermes kanban create --json (:1380) and list/show --json, does not include model_override. The plain-text show path does print it (:1547), so this is an inconsistency: a scripted consumer that sets --model and reads back via --json cannot see the value it just wrote. Not a write-path defect — the DB row is correct — but worth a one-line add to _task_to_dict for observability parity. Optional; safe to merge without it.


Local rubric review — correctness PASS, scope-fidelity PASS, test-coverage PASS, plan-compliance MATCH. No merge/approve action taken from this lane.

Review of #95 flagged that _task_to_dict omitted model_override, so
create/show/list --json couldn't read back a value --model had just
written (plain-text show already printed it). Add the field to
_task_to_dict for observability parity + a JSON-surface round-trip test.

Write path was already correct; this is read-side serialization only.
@exiao

exiao commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Addressed the P3 from review (9144d21): _task_to_dict was omitting model_override, so create/show/list --json couldn't read back a value --model had just written (plain-text show did print it). Added the field for observability parity, plus a JSON-surface round-trip test (test_kanban_json_includes_model_override).

Write path was already correct; this is read-side serialization only. test_kanban_cli.py (48) and test_kanban_db.py (283) both green.

@claude claude 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.

LGTM — write path (CLI --model, create_task(model_override=), kanban_create tool model prop) cleanly completes the already-landed read/spawn side. Verified INSERT column/placeholder/value alignment (21 each, model_override last), blank→NULL coercion centralized in create_task and re-guarded on read, dispatcher -m consumption pre-exists, and P3 (task_to_dict JSON parity) addressed in 9144d21. No new core tool, no prompt-cache mutation, no HERMES* env var. Tests are behavior contracts. 0 blocking, 0 minor.

@exiao
exiao merged commit 6b0e405 into live-config Jul 12, 2026
33 checks passed
@exiao
exiao deleted the feat/kanban-model-override branch July 12, 2026 08:41
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