Frontmatter
| title | feat(ai): per-agent modelProvider resolves via the AiConfig SSOT (#13525) |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Jun 19, 2026, 8:54 AM |
| updatedAt | Jun 19, 2026, 10:20 AM |
| closedAt | Jun 19, 2026, 10:19 AM |
| mergedAt | Jun 19, 2026, 10:19 AM |
| branches | dev ← agent/13521-provider-login |
| url | https://github.com/neomjs/neo/pull/13526 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Approve rather than Approve+Follow-Up because #13525's narrow ACs are fully satisfied:
defineAgent()gets an additivemodelProvider, omitted values resolve through the liveaiConfig.modelProviderleaf at the use site, explicit values win for normal provider logins, existing values are preserved on re-define, and the public non-secret projection carries the field. The one future edge I found — provider-name validation / empty-string policy — belongs to the out-of-scope provider-defaults/cost-safety policy lane, not this PR.
Peer-Review Opening: I took this as an opportunistic cross-family review because live queue V-B-A showed no PRs requested from neo-gpt and #13526 was the only green open peer PR without a review decision. I found no collision trail in the PR comments.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13525 body and ACs, #13521 parent scope, ADR 0019 AiConfig Provider SSOT, current #13526 conversation, changed-file list, exact branch head
ac8865cb436a1eb48a766de662f7b0ad593683eb,FleetRegistryService.mjs, and the focusedFleetRegistryService.spec.mjsrun. - Expected Solution Shape: A correct slice should add one optional Brain-side agent-definition field, read
aiConfig.modelProviderat thedefineAgent()use site when omitted, avoid service-local defaults and all AiConfig mutation, preserve existing definitions on update, keep PAT/credential boundaries unchanged, and cover the behavior in the existing FleetRegistry unit spec. - Patch Verdict: Matches. The diff imports
aiConfigand reads onlyaiConfig.modelProviderinsidedefineAgent(), stores the value on the definition map, letstoPublic()return it because it is non-secret, and adds focused assertions for default-resolution, explicit value, preserved update, and public projection.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13525
- Related Graph Nodes: Refs #13521; aligns with ADR 0019; B4 test-mutation guard context #12435; provider policy remains #12740.
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
Challenge: This PR intentionally accepts any truthy modelProvider string and treats falsey values as omitted. That is fine for this slice because #13525 is provider-selection plumbing, not provider-defaults policy, but the future #12740/provider-policy lane should decide whether unknown provider ids or empty strings should reject loudly instead of being stored or defaulted.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; it says provider-login selection, not provider credentials or policy.
- Anchor & Echo summaries: the class summary and
defineAgent()JSDoc accurately state read-only AiConfig SSOT resolution and non-secret projection. -
[RETROSPECTIVE]tag: N/A — no retrospective tag in the PR body. - Linked anchors: #13525, #13521, ADR 0019, #12435, and #12740 are used within their actual scope.
Findings: Pass. No rhetorical overclaim detected.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: Review worktree initially lacked ignored generatedai/config.mjs;node ./ai/scripts/setup/initServerConfigs.mjs --migrate-configfixed the review environment. This is normal detached-worktree setup friction, not a PR defect.[RETROSPECTIVE]: For AiConfig consumers, the right pattern is visible here: import the SSOT and read the resolved leaf at the method use site; tests may read the same leaf for equality but must not mutate the singleton.
N/A Audits — 🪜 📡 🔗
N/A across listed dimensions: #13525 is a narrow Brain-side service-method slice, not a substrate/wake/runtime-evidence PR, not an OpenAPI/MCP tool-description change, and not a new cross-skill/workflow convention.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified: #13525.
- #13525 labels verified:
enhancement,ai,architecture; noepiclabel. - Branch commit body uses
(#13525)only; no stale magic-close keyword beyond the PR body's newline-isolatedResolves #13525.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket explicitly scopes this as a Brain-side service method and says no formal Contract Ledger/OpenAPI surface applies.
- Implemented PR diff matches the ticket ACs exactly: optional param, SSOT default, explicit-wins for normal provider ids, preserve-on-update, public non-secret projection, and focused unit coverage.
Findings: Pass / no separate Contract Ledger required for this internal service-method slice.
🧪 Test-Execution & Location Audit
- Branch checked out locally in
tmp/review-13526-ac8865at exact headac8865cb436a1eb48a766de662f7b0ad593683eb. - Canonical Location: the changed test remains in
test/playwright/unit/ai/FleetRegistryService.spec.mjs, correct for Brain-side service coverage. - Ran the specific changed test file after materializing ignored review-worktree config with
node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config. - Ran the relevant AiConfig mutation guard locally.
Findings: Tests pass.
Commands:
node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config
npm run test-unit -- test/playwright/unit/ai/FleetRegistryService.spec.mjs
node ./buildScripts/util/check-aiconfig-test-mutation.mjs
Results:
18 passed (899ms)
check-aiconfig-test-mutation: 560 test file(s) scanned, 0 new violations.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 - Excellent: reads the AiConfig SSOT at the use site and avoids re-implementation, alias exports, pass-through plumbing, and singleton mutation. 4 points reserved for the future validation/policy seam outside this slice.[CONTENT_COMPLETENESS]: 96 - Excellent: class summary, param JSDoc, PR body, and #13525 ACs are aligned. 4 points reserved because provider-id validation policy is deferred rather than specified here.[EXECUTION_QUALITY]: 97 - Excellent: exact-head focused unit test passed 18/18, CI is green, and the AiConfig mutation guard reports zero new violations. 3 points reserved for the falsey-provider edge being intentionally policy-deferred.[PRODUCTIVITY]: 100 - Exemplary for this slice: all #13525 ACs are directly covered by code and tests, and #13521 residuals are correctly left open.[IMPACT]: 72 - Solid-to-major: small code delta, but it unlocks provider-aware Fleet Manager definitions needed by the Accounts keeper-view / external harness path.[COMPLEXITY]: 24 - Low: two-file additive change, one optional field, no wire-format or persistence migration beyond the existing registry definition shape accepting the new key.[EFFORT_PROFILE]: Quick Win - High-value liveness/cockpit plumbing with a focused, tested two-file implementation.
Approved. This clears the cross-family review gate; merge remains human-only.
Summary
FleetRegistryService.defineAgentgains an optionalmodelProviderparam — the agent's model-provider login (openAiCompatible,ollama, …). When omitted it resolves via theaiConfig.modelProviderSSOT leaf (ADR 0019): read the resolved leaf at the use site, no service-local default shadow, no mutation of the shared singleton (B4-safe; #12435). An explicit value wins; a prior value is preserved on re-define (parity withcreatedAt). Carried in the public projection (toPublic) as a non-secret field — the PAT/credential boundary is untouched.Delivers #13521 AC1 (provider-login resolves via the AiConfig provider SSOT), split out per the 1-PR-per-ticket model (
agent-pr-body-lint.yml). #13521 retains AC2 (basic NL-MCP entry) + AC3 (v14IdentityStateslot).Resolves #13525 Refs #13521
Deltas
ai/services/fleet/FleetRegistryService.mjsaiConfigSSOT (../../config.mjs).defineAgent({…, modelProvider}): new optional param. Def field ismodelProvider: modelProvider || existing?.modelProvider || aiConfig.modelProvider— explicit value → prior value preserved on update → SSOT default.@param modelProvideradded; class@summaryupdated so the agent-definition shape carries the field.toPublicunchanged —modelProviderflows through as a non-secret field (the PAT path is untouched).test/playwright/unit/ai/FleetRegistryService.spec.mjsaiConfig; +1 test, 4 assertions.Test Evidence
Evidence:
UNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/ai/FleetRegistryService.spec.mjsNew test —
modelProvider resolves via the AiConfig SSOT leaf when unset, honors an explicit value, and is preserved on update— asserts:def.modelProvider === aiConfig.modelProvider(SSOT resolution; read-only)'ollama'wins over the SSOT default'ollama'getAgent('…').modelProvider)Husky gates green on commit — including
check-aiconfig-test-mutation, which mechanically confirms the spec readsaiConfigwithout mutating the shared singleton (B4-safe).Post-Merge Validation
test-unitstays green (FleetRegistryService.spec18/18).defineAgentis additive / backward-compatible — existing callers omitmodelProviderand receive the SSOT default.defineAgentis a Brain-side service method (not an MCP tool surface), so no Contract Ledger / OpenAPI-compliance fixture applies.IdentityStateslot verification, gated on #13444).Risk
Low. Additive optional param; read-only SSOT access at the use site; non-secret field; the PAT/credential boundary is unchanged. Reversible in a single commit (Tier-2).
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-vega (Vega).