Skip to content

Model Groups spawn router#14

Open
grzegorznowak wants to merge 8 commits into
mainfrom
model-groups-spawn-router
Open

Model Groups spawn router#14
grzegorznowak wants to merge 8 commits into
mainfrom
model-groups-spawn-router

Conversation

@grzegorznowak

@grzegorznowak grzegorznowak commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the Model Groups spawn-router story end to end: operators can define named model groups, get prompt/autocomplete guidance for them, and route spawned child agents through a group while the default inherited spawn path remains unchanged.

The latest follow-up completes the approved Model Groups data-layer/TUI story with trust-aware scope access, stricter persistence contracts, canonical display escaping, Pi-native TUI primitives, and a producer-facing type contract.

What changed

  • Added Model Groups persistence/state and TUI flows for global/project groups, validation, effective group resolution, and project-over-global overrides.
  • Enforced global-only behavior when project access is unavailable or untrusted, without probing project data.
  • Hardened persistence and naming around schema/version recovery, typed failures, canonical own keys, collision checks, and native max thinking support.
  • Reworked the TUI around Pi Container, SelectList, and Input primitives with bounded rendering, controlled-label escaping, focus propagation, and persistence-safe error handling.
  • Added #group-name autocomplete for effective groups; suggestions show compact provider/model/thinking details for the operator while inserting only prompt text.
  • Refreshed system prompt guidance with names-only group availability and confidence/fallback rules; provider/model/auth/persistence details are not exposed in the prompt.
  • Extended spawn with optional exact group routing and stopped advertising thinking; stale cached thinking arguments are accepted but ignored.
  • Routed spawn calls randomly choose one available authenticated member from the requested group per call. Empty groups or groups with no usable authenticated entries fail clearly; unknown groups fall back to parent model/thinking.
  • Routed child thinking uses the selected entry's configured thinking level when present, otherwise inherits parent thinking, then clamps to the selected model capability.
  • Child sessions prefer the parent runtime registry/auth while keeping session manager, messages, tools, and spawn isolation intact.
  • Spawn result rendering distinguishes inherited default routing, successful group routing, and unknown-group fallback identity.

Out of scope

  • Interactive picker/chooser at spawn time.
  • Automatic routing without explicit operator group intent.
  • Registering groups as model picker entries or meta-model proxies.
  • Child profiles, frontmatter, extra tools, budget/context policies, weighted/priority/health/rate-limit routing, retry-time group failover, fuzzy matching, or explicit provider/model override params on spawn.

Verification

Current at 30c0f89:

  • npm test — 204/204 passed.
  • Focused Model Groups data-layer/TUI suites — 36/36 passed.
  • npm run test:e2e — 10/10 passed.
  • npx tsc --noEmit -p tsconfig.model-groups-producer.json — passed.
  • npm ls @earendil-works/pi-ai @earendil-works/pi-coding-agent @earendil-works/pi-tui — coherent at 0.80.7.
  • Staged and working-tree git diff --check — passed before commit.

CI note / follow-up

  • Windows test homes are isolated by setting HOME, USERPROFILE, HOMEDRIVE, and HOMEPATH for model-groups tests.
  • CI runs a runtime dependency audit with --omit=dev. The full dev audit remains blocked by upstream shrinkwrapped Pi dev dependencies, so this PR does not claim the upstream Pi audit issue is fixed. Restore strict full-dev audit after the upstream fix is released and the lockfile is updated.

References

  • Initiative: model-tag-router
  • Stories: model-groups-spawn-router, model-groups-data-layer-tui

@grzegorznowak
grzegorznowak force-pushed the model-groups-spawn-router branch from 479d78e to 907b4e7 Compare June 16, 2026 04:53
@grzegorznowak
grzegorznowak requested a review from ofriw June 16, 2026 08:04

@ofriw ofriw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the strong PR — the Model Groups architecture is clean and the feature scope is well-contained.

Must fix before merge

1. /model-groups does not guard missing project/model context

The interactive /model-groups command calls createModelGroupsComponent(...) with ctx.modelRegistry and ctx.cwd unconditionally (model-groups/command.ts). But elsewhere in the same subsystem, missing cwd / modelRegistry is explicitly treated as a valid condition and safely skipped (index.ts, refreshModelGroupsState).

That inconsistency means /model-groups can fail when the command is invoked without a fully initialized project/model context, instead of degrading gracefully.

Expected fix: add the same guard pattern in the command path and surface a friendly message when the required context is unavailable.

2. deleteGroup() can report failure after the delete already succeeded

In model-groups/store.ts, deleteGroup() removes the group and saves the updated config first, then loads the other scope to compute otherScopeHasOverride.

If that opposite-scope load throws (for example because recovery hit a backup-failed malformed config), the function throws after the target delete already committed to disk.

That creates a bad correctness property: callers can observe an error for an operation that already mutated persistent state successfully.

Expected fix: make the opposite-scope lookup non-fatal, or compute it before the delete is committed.

3. Test helper duplication

The new model-group test files duplicate helpers like registry(), group(), and withTemp() in several places. That is valid DRY cleanup work, but it is maintenance debt rather than a release blocker.

AGENT_REVIEW.md

…fix deleteGroup error-after-commit

- model-groups/command.ts: add guard for missing ctx.cwd or ctx.modelRegistry
  before calling createModelGroupsComponent; surface friendly notification
  instead of crashing. Matches refreshModelGroupsState pattern in index.ts.
- model-groups/store.ts deleteGroup(): load opposite-scope config before
  saving the delete so a load failure doesn't throw after the delete is
  already persisted on disk. Follows moveGroup's pre-validation pattern.
@grzegorznowak

Copy link
Copy Markdown
Collaborator Author

Addressed the two must-fix items:

1. /model-groups missing context guard → fixed in command.ts

Added check for !ctx.cwd || !ctx.modelRegistry before calling createModelGroupsComponent, surfacing a friendly notification instead of crashing. Matches the existing guard in refreshModelGroupsState().

2. deleteGroup() throwing after successful delete → fixed in store.ts

Reordered deleteGroup() to load the opposite-scope config before calling saveModelGroups. If loadScopeConfig for the other scope throws, nothing was persisted yet — clean failure. Mirrors the pre-validation pattern already used in moveGroup().


This comment is AI-generated.

- Extract duplicated withTemp() and group() into tests/unit/model-groups-helpers.ts
- withTemp: unified async version replacing 2 local variants (crud + integration)
- group: unified opts-object signature replacing 3 local variants (tui, router, autocomplete)
- registry() kept per-file — each test file needs different model sets
@grzegorznowak

Copy link
Copy Markdown
Collaborator Author

3. Test helper DRY cleanup

Extracted shared withTemp() and group() helpers into tests/unit/model-groups-helpers.ts:

  • withTemp() — unified async version (was duplicated in crud and integration tests)
  • group() — unified opts-object signature (was duplicated across tui, router, and autocomplete tests)
  • registry() kept per-file — each test file needs different model sets, not worth forcing into a shared helper

Existing helpers.ts untouched — it stays domain-agnostic.

190/190 pass.


This comment is AI-generated.

grzegorznowak added a commit to grzegorznowak/pi-agenticoding that referenced this pull request Jun 24, 2026
…del-groups, fix deleteGroup error-after-commit

- model-groups/command.ts: add guard for missing ctx.cwd or ctx.modelRegistry
  before calling createModelGroupsComponent; surface friendly notification
  instead of crashing. Matches refreshModelGroupsState pattern in index.ts.
- model-groups/store.ts deleteGroup(): load opposite-scope config before
  saving the delete so a load failure doesn't throw after the delete is
  already persisted on disk. Follows moveGroup's pre-validation pattern.
@grzegorznowak
grzegorznowak requested a review from ofriw June 24, 2026 14:22

@ofriw ofriw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #14 Review — Model Groups & Spawn Routing

AGENT_REVIEW.md

Assessment

Clean architecture, good test coverage, well-contained scope. One correctness issue to fix before merge. Rest is tech debt.

Issues

1. 🔴 Model group state can go stale when context is unavailable

refreshModelGroupsState in index.ts:47 returns early without clearing state.modelGroups when ctx.cwd or ctx.modelRegistry is missing. Stale groups from a previous valid session remain in state and get injected into the system prompt via before_agent_start (line 208). The session reset in resetState does clear this, but that only runs on /new — not between agent runs within the same extension lifetime. Fix: clear state.modelGroups.groups and state.modelGroups.validation before the early return.

2. 🟡 createModelGroupsComponent is 378 lines

model-groups/tui.ts:76–453. Project guideline: functions above 20 lines should be broken into smaller units. The TUI component contains 7 screen renderers, input handling, and CRUD operations in a single closure. Track as tech debt.

3. 🟡 modelRegistry accessed via as any casts

Four occurrences across index.ts:46–47 and spawn/index.ts:224–225. The ExtensionContext type doesn't include modelRegistry, so every access bypasses type safety. If the upstream property changes shape or name, there's no compile-time protection.

4. 🟢 Unused state field: wizardThinking

model-groups/tui.ts:88. Declared in the TUI state object but never read or written anywhere. The wizard reads thinking options directly via thinkingOptionsFor(currentWizardModel())[state.row].

5. 🟢 Missing SpawnRouteError integration test

model-groups-router.test.ts covers SpawnRouteError at the router level (empty group, no usable models). But no test in spawn.test.ts verifies the error propagates correctly through executeSpawn to the caller — that boundary is untested.

Already addressed

The contributor fixed 3 items from an earlier review pass: context guard in /model-groups command, deleteGroup() save-before-load ordering, and test helper extraction. All verified.

What's good

  • Pure routerresolveSpawnModelRoute is side-effect-free with an injectable rng seam for deterministic testing
  • Boot validation — eagerly validates groups against the model registry at startup, surfaces misconfigurations via operator notifications instead of failing at spawn time
  • Scoped persistence — backup-before-overwrite, typed ModelGroupsPersistenceError with phase tracking, and partialMove state for moveGroup
  • Names-only prompt guidance — group internals (provider, model, thinking, auth) are never exposed to the child agent
  • Test coverage — 6 new test files covering CRUD, routing, TUI, autocomplete, integration, and spawn rendering with good edge-case depth

@grzegorznowak

Copy link
Copy Markdown
Collaborator Author

Updated this PR with 30c0f89 (feat(model-groups): complete data layer and TUI).

Data-layer/TUI follow-up

  • Added trust-aware global-only access when project scope is unavailable or untrusted.
  • Hardened schema/version recovery, typed persistence failures, canonical own-key handling, and collision checks.
  • Added reversible controlled-label display escaping.
  • Reworked the interactive flow around Pi-native TUI primitives with bounded rendering and focus propagation.
  • Aligned the Pi dependency family at 0.80.7 and added a producer-facing typecheck contract.

Verification

  • npm test: 204/204
  • Focused data-layer/TUI suites: 36/36
  • npm run test:e2e: 10/10
  • Producer contract tsc: passed
  • Pi dependency coherence: 0.80.7
  • Diff checks: passed

This update does not include or claim the deferred sibling prompt/autocomplete/router/spawn follow-ups or FB-004/FB-006/FB-008.


This comment is AI-generated.

@grzegorznowak

Copy link
Copy Markdown
Collaborator Author

Addressed review item 4 in 41d6d28: removed the unused wizardThinking initializer from model-groups/tui.ts. The focused TUI suite (17/17), unit suite (204/204), producer typecheck, and diff check pass.

The separately identified stale-state issue remains blocking and is not resolved by this refactor.

…uter

# Conflicts:
#	.github/workflows/test.yml
#	index.ts
#	package-lock.json
#	package.json
#	spawn/index.ts
#	state.ts
@grzegorznowak

Copy link
Copy Markdown
Collaborator Author

Merged current origin/main (26e65f9) into this branch in merge commit 181502c and pushed it.

Resolution highlights:

  • composed Model Groups session/spawn behavior with readonly rehydration, branch lifecycle, and child bash/tool guards
  • cleared Model Groups state before unavailable-cwd/registry early returns to prevent stale routing
  • preserved Pi 0.80.7 while adding main's typecheck/Husky/audit tooling; regenerated the lockfile
  • reconciled audit-ci.jsonc with the clean resolved 0.80.7 dependency graph
  • added regression coverage for stale Model Groups state and grouped + readonly spawn composition
  • updated the test ExtensionAPI mock and spawn thinking type for Pi 0.80.7 API/type changes

Local validation passed:

  • npm ci
  • npm run typecheck
  • npx tsc --noEmit -p tsconfig.model-groups-producer.json
  • focused merge suite: 110/110
  • full unit suite: 439/439
  • E2E suite: 16/16
  • npx audit-ci --config audit-ci.jsonc
  • npm audit --audit-level=moderate (0 vulnerabilities)
  • conflict-marker/unmerged-entry/diff whitespace checks

The GitHub matrix (Linux Node 22/24, macOS Node 24, Windows Node 24) is currently queued, and the PR still has a CHANGES_REQUESTED review state, so this comment does not claim final readiness yet.

@grzegorznowak

grzegorznowak commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. I reassessed every item against the current head (e988f72). All correctness blockers are now resolved, including the unavailable-context stale-state regression; the earlier context guard, delete ordering, duplicated helpers, and unused wizardThinking items are also addressed.

For the remaining points:

  • (ctx as any).modelRegistry: Pi 0.80.7's ExtensionContext does declare modelRegistry, so the rationale that this property is absent from the public context type does not apply here. Removing the casts would still be reasonable cleanup to restore stronger type checking, but it is not a correctness blocker.
  • createModelGroupsComponent size: Although the factory is long by raw line count, it encapsulates one coherent stateful Model Groups TUI component and its screen-state machine. Its rendering and behavior are already divided into named internal helpers that depend closely on shared component state. I also could not find any repository instruction establishing a 20-line function limit. Further extraction may be worthwhile as optional maintainability work, but function length alone does not establish a defect or merge blocker.
  • Missing spawn-boundary SpawnRouteError test: The router's failure behavior is covered, and executeSpawn invokes routing before session creation and directly propagates that error. A dedicated boundary test would improve coverage, but this is non-blocking test debt rather than an unresolved behavior defect.

The current head is cleanly mergeable, and the full Linux/macOS/Windows CI matrix is green. @ofriw, please re-review when you have a chance.

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.

2 participants