feat(model-groups): surface effective modalities and fail early on spawn (issue #26) - #27
feat(model-groups): surface effective modalities and fail early on spawn (issue #26)#27grzegorznowak wants to merge 7 commits into
Conversation
Implement issue agenticoding#26: spawn and main session become modality-aware. - Add pure derivation module model-groups/modalities.ts (common/supported/effective sets from live ModelRegistry input+reasoning). - Persist per-group modalityOverride with v2 schema-version guard: lossless normalization, opaque-key preservation, v1 in-memory migration, future-version write refusal. - Inject effective modalities per group into the main-session system prompt via before_agent_start. - Add optional spawn requiredModalities checked at the router gate; fail early before child session when the routed model/group cannot satisfy a requirement. - TUI: modality display, empty-common + stale-override warnings, editor for automatic/supported subsets. - Add focused AC1-AC7 test coverage incl. new model-groups-modalities test. Validators: typecheck, npm test 618/618, e2e 16/16, snapshots 11/11, compat:floor, package-host all pass. test:compat:current is skipped (pre-existing host-skew, unrelated; tracked separately).
On the failing
|
…se review gaps A_R: normalizeGroups now runs validateOverride for every accepted source version, so a malformed hand-added modalityOverride in a legacy (missing/0/1) config surfaces as a schema-invalid load issue + backup + empty recovery instead of a raw TypeError from cloneDef. Minimal stabilization; no v1 valid-override migration feature. B (coverage, 619->628): - crud: A1 regression (legacy malformed override), store-level derivation of empty-common + stale flags via summarizeBootValidation counts, CRUD gate rejects unsupported override on create and combined member-change update (0 writes, byte-for-byte unchanged), v2 load rejects non-array/duplicate/ out-of-vocabulary override - tui: modality editor commits override + Automatic path through updateGroup, error-retention on updateGroup failure - integration: session_start boot notification counts for empty-common and stale overrides - router: plain inherited route honors requiredModalities with empty no-op - spawn: tool schema validated via Value.Check, inherited requiredModalities forwarded and succeeding when satisfied PR agenticoding#27 review1 gaps A1/A2 closed (A2 wording corrected in PR description).
# Conflicts: # tests/unit/spawn.test.ts
Code-review findings (implementation pass) — in scope for this PRReviewed against base 1. HIGH — v1 config with a valid hand-added
|
Code-review findings — all addressed (triage + patch)Thanks for the thorough pass. Every in-scope item is resolved. Note: the repo restructured during the #23 merge (
Validation after patch: typecheck PASS · unit 652/652 (+1 new test) · e2e 16/16 · snapshots 11/11 · compat:current 0.84.2 PASS · package-host PASS · Pre-existing HIGH store findings (fsync/locking/symlink/backup) remain tracked in #28, out of scope here as you filed them. |
… TUI editor - documented the locked v1 valid-override pass-through as intentional (no migration) - documented saveModelGroups as low-level CRUD-only cap enforcement (no signature change) - registered spawn-tool test: requiredModalities rejection throws SpawnRouteError before any child session (zero factory calls, both session maps empty) - happy-path registered-spawn test now asserts liveChildSessions cleared - corrected integration fixture comment: claude unresolved -> empty common, override stale - TUI modality editor commit test selects rows by rendered label, not row numbers
…bel, stale editor, prose single-source - store: strip runtime-derived keys (name/scope/sourcePath/modalities/validation) at the persistence boundary; opaque user keys + modalityOverride preserved - tui: draft projection persists only models/override; MODALITIES editor choices union supported + stale override members; empty effective labels unambiguous - index: empty effective renders '(no common modalities)' instead of '(none)' - prose vocab single-sourced from MODEL_GROUP_MODALITIES (spawn + prompt section) - router: documented absent == empty requiredModalities semantics
Resolve the pluggability gap with a typed, compile-time constraint registry + generic envelopes, absorbed into PR agenticoding#27 (unmerged-v2, no migration; version stays 2). - constraints/: pure generic kernel (engine/registry/resolution/presentation) with injectable registries; production registers only modalities. - Modality constraint descriptor owns extraction, aggregation, reconciliation, requirements, diagnostics, presentation, editor; model-groups/modalities.ts becomes thin compatibility façades (parity). - Persisted envelope: ModelGroupDef.constraints (canonical) + modalityOverride (conflict-safe deprecated alias; equal coalesces, unequal rejects; unknown slots round-trip opaquely). - Spawn envelope: constraints (descriptor-generated TypeBox) + requiredModalities alias, normalized at one boundary pre-route. - Router: iterates registry descriptors; modality violations keep the exact missing-modality SpawnRouteError arrays; injected scalar routes to additive constraint-unsatisfied. - Prompt/boot-summary/TUI iterate descriptor presentation metadata; notification text and (no common modalities) fallback byte-identical. - AC6 proof: synthetic testMinContext descriptor traverses the full seam via router + registered spawn (0 factory calls, both maps empty); production registry stays [modalities], no production testMinContext. Refactor-only: no materialized cost/context/param dimension. Full battery green: typecheck, unit 669/669, e2e 16/16, snapshots 11/11, compat:current 0.84.2, package-host, git diff --check.
Debt review — all 7 items triaged + resolved (Option C landed)Reviewed the operator's code-debt pass against the current tree (head #1 (HIGH, pluggability gap) — resolved via Option C. The modality vertical slice (vocab, derivation, persistence, validation, boot counts, TUI editor, spawn schema, route gate) is now backed by a typed, compile-time constraint registry with generic persisted override and spawn requirement envelopes ( #2 (four divergent group semantics) — deferred with the auth dimension (deferred debt #2). The engine now receives an explicit #3 (TUI → config projected pollution) — verified fixed by the earlier B29 derived-key strip (persisted config carries only authored/persisted + the new generic envelope; runtime-derived keys and the old #4 (CRUD-only cap) — documented. Generic envelope normalization runs against the registry before mutation; low-level #5 ("name (none)" collision + stale-override editor blind spot) — fixed: the empty-effective prompt label is now unambiguous #6 (requiredModalities machinery) — reconciled: schema is descriptor-generated from the registry; prose stays modality-specific by design (refactor-only); empty == absent documented; the router rejects unknown requirement keys before session creation. #7 (unbounded uncached derivation) — still deferred (perf), tracked separately. Not resolved by this refactor; a cache/version boundary is a separate follow-up. All prior review items and B-cycle fixes remain green. Full battery at |
…ses (clean v2 surface) Since v2 never shipped, the alias layer had no audience. Make the generic 'constraints' envelope the single public surface: - ModelGroupDef: constraints only; modalityOverride removed. - Spawn tool: constraints only; requiredModalities removed from schema, SpawnParameters, and the normalizer. - Router: requirements come only via constraints. - Store/TUI/modalities: read/write constraints.modalities only. - v1 files: a modalityOverride key is preserved opaquely (not interpreted) and dropped on the first v2 mutation; a constraints key in legacy config is still rejected. - Prompt guidance now instructs passing requirements as constraints. - Tests migrated to the constraints shape; alias-conflict/coalesce tests replaced with canonical round-trips + B1 legacy-opaque tests. Full battery green: typecheck, unit 669/669, e2e 16/16, snapshots 11/11, compat:current 0.84.2, package-host, git diff --check.
Implements issue #26 — "Let the spawn system read model modalities to understand the tasks it's good/bad at".
Related to
agenticoding/pi-agenticodingissue #26 (spawn reading model modalities). No issue is auto-closed by this PR.What this PR does
The spawn system and the main session now understand what each Model Group is capable of, and fail early when a delegated task asks for a capability the selected model/group cannot deliver.
Capabilities are pluggable, not hard-coded to modalities.
model-groups/constraints/kernel generalizes how model capabilities are derived and checked. One constraint descriptor owns the whole concern for a single capability: how to read the fact off aModel, how to combine it across the group's members, how to reconcile a user override, how to check a spawn requirement, and how to present it in the UI/prompt.modalities). Future capabilities (e.g. a minimum context-window) can be added as another descriptor plus tests, with no edits to the layer that loads config, routes spawns, or renders the UI. This is demonstrated by a synthetic test-only descriptor that runs through the whole path without ever appearing in production code.Persisted overrides use a single versioned, forwards-compatible shape.
constraintsmap on the group (e.g.constraints.modalities).2; since this branch has not shipped v2 yet, no migration path is needed.modalityOverridekey is preserved as opaque data and dropped on the first v2 write).Spawn fail-early.
constraints(a keyed object, schema-generated from the descriptors), normalized at one boundary before routing.missing-modalitySpawnRouteError; unknown requirement keys are rejected; the check holds before any child session is created.Main-session awareness and TUI.
before_agent_startinjects each group's capability summary (e.g.websearch (text, image)) into the orchestrator system prompt, reusing the existing hook.Scope note: intentionally refactor-only
This PR deliberately does not add a cost, minimum-context, or parameter-count feature. It lays the pluggable foundation and exercises it with a synthetic test descriptor so the extension point is proven rather than aspirational. Two constraints of the derivation are handled separately: deciding whether group capability means "all registered members" vs "only authenticated/usable members" is a separate issue, and a parameter-count source does not exist in the host
Modeltype today (model-name inference is not an option).Review feedback addressed
Review cycle 1 (contract + first implementation review)
modalityOverridepreviously crashed load; they now validate cleanly and recover with a.bakbackup.Code-review implementation pass
requiredModalitiesrejection throws before any child session (zero factory calls, both session maps empty).liveChildSessionsis cleared.Pluggability review
Validation (full battery, all PASS)
npm run typechecknpm test(669/669)npm run test:e2e(16/16)npm run test:snapshots:check(11/11)npm run test:compat:current(0.84.2)npm run test:package-hostgit diff --checkclean; no test-only descriptor key leaks into product files; no stray console outputRefers to #26.