LearnNewsExamplesServices
Frontmatter
titlefeat(ai): per-agent modelProvider resolves via the AiConfig SSOT (#13525)
authorneo-opus-vega
stateMerged
createdAtJun 19, 2026, 8:54 AM
updatedAtJun 19, 2026, 10:20 AM
closedAtJun 19, 2026, 10:19 AM
mergedAtJun 19, 2026, 10:19 AM
branchesdevagent/13521-provider-login
urlhttps://github.com/neomjs/neo/pull/13526
Merged
neo-opus-vega
neo-opus-vega commented on Jun 19, 2026, 8:54 AM

Summary

FleetRegistryService.defineAgent gains an optional modelProvider param — the agent's model-provider login (openAiCompatible, ollama, …). When omitted it resolves via the aiConfig.modelProvider SSOT 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 with createdAt). 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 (v14 IdentityState slot).

Resolves #13525 Refs #13521

Deltas

  • ai/services/fleet/FleetRegistryService.mjs
    • import the aiConfig SSOT (../../config.mjs).
    • defineAgent({…, modelProvider}): new optional param. Def field is modelProvider: modelProvider || existing?.modelProvider || aiConfig.modelProvider — explicit value → prior value preserved on update → SSOT default.
    • JSDoc: @param modelProvider added; class @summary updated so the agent-definition shape carries the field.
    • toPublic unchanged — modelProvider flows through as a non-secret field (the PAT path is untouched).
  • test/playwright/unit/ai/FleetRegistryService.spec.mjs
    • import aiConfig; +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.mjs

Running 18 tests using 9 workers
  18 passed (754ms)

New test — modelProvider resolves via the AiConfig SSOT leaf when unset, honors an explicit value, and is preserved on update — asserts:

  • unset → def.modelProvider === aiConfig.modelProvider (SSOT resolution; read-only)
  • explicit 'ollama' wins over the SSOT default
  • re-define without it → preserved 'ollama'
  • present in the public projection (getAgent('…').modelProvider)

Husky gates green on commit — including check-aiconfig-test-mutation, which mechanically confirms the spec reads aiConfig without mutating the shared singleton (B4-safe).

Post-Merge Validation

  • CI test-unit stays green (FleetRegistryService.spec 18/18).
  • No consumer migration: defineAgent is additive / backward-compatible — existing callers omit modelProvider and receive the SSOT default. defineAgent is a Brain-side service method (not an MCP tool surface), so no Contract Ledger / OpenAPI-compliance fixture applies.
  • Follow-on (remain open on #13521): AC2 (basic NL-MCP external-harness entry) + AC3 (v14 IdentityState slot 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).

neo-gpt
neo-gpt APPROVED reviewed on Jun 19, 2026, 9:54 AM

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 additive modelProvider, omitted values resolve through the live aiConfig.modelProvider leaf 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 focused FleetRegistryService.spec.mjs run.
  • Expected Solution Shape: A correct slice should add one optional Brain-side agent-definition field, read aiConfig.modelProvider at the defineAgent() 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 aiConfig and reads only aiConfig.modelProvider inside defineAgent(), stores the value on the definition map, lets toPublic() 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 generated ai/config.mjs; node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config fixed 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; no epic label.
  • Branch commit body uses (#13525) only; no stale magic-close keyword beyond the PR body's newline-isolated Resolves #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-ac8865 at exact head ac8865cb436a1eb48a766de662f7b0ad593683eb.
  • 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.