Skip to content

fix(agent-core-v2): write refresh results in one atomic config transition - #2410

Merged
sailist merged 2 commits into
MoonshotAI:mainfrom
sailist:fix/atomic-model-refresh
Jul 30, 2026
Merged

fix(agent-core-v2): write refresh results in one atomic config transition#2410
sailist merged 2 commits into
MoonshotAI:mainfrom
sailist:fix/atomic-model-refresh

Conversation

@sailist

@sailist sailist commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

Starting kimi web (or creating the first session) could sporadically fail with "model is not configured" errors. Root cause: the background provider-model refresh wrote its results through the orchestrator's two-phase host contract (removeProvider, then setConfig), and each phase applied directly to kosong's in-memory registries plus separate config writes. Between the phases the model catalog was transiently empty/partially removed, so a session created concurrently (e.g. while the profile was binding its model) observed a halfway-removed catalog and failed.

What changed

1. IConfigService.replaceSections — atomic multi-domain config writes

Problem: ConfigService could only replace one domain per transition, so a multi-section update (providers + models + defaultModel + thinking) produced several intermediate effective states, each visible to readers and each firing its own change events.

What was done:

  • Added replaceSections(sections, target?): updates every domain's raw value first, persists the document with a single disk write (persistDomains), and rebuilds the effective view once — change events fire only after all domains have taken effect, so no reader can observe a half-applied multi-section write. A domain mapped to undefined is cleared, same as replace(domain, undefined).
  • persist(domain) now delegates to persistDomains([domain]).

2. ProviderDiscoveryService — single atomic refresh write

Problem: The two-phase host contract removed a provider from the runtime registries before the new shape landed, which is exactly the transiently-cleared catalog behind the startup race.

What was done:

  • removeProvider no longer touches the registries: shapeWithoutProvider computes the removal in memory only, because the host's patch always carries the FULL providers/models records under replace semantics — the removal is already expressed by the patch itself.
  • applyRefreshPatch now lands all four sections (providers, models, defaultModel, thinking) in ONE replaceSections transition; the bridge's synchronous event sync pushes the records into the kosong registries atomically.
  • The env-synthesized __kimi_env__ slice is no longer written to config at all (it lives in the effective overlay; the bridge carries it into the registries on its own), and the previous "preserve synthetic entries across registry replace" logic was dropped together with the direct replaceAll writes. IModelService is no longer a dependency of the service.
  • Tests updated: config.test.ts covers replaceSections (atomic persist, clear-on-undefined, memory target, event timing), discovery.test.ts covers the single-transition refresh behavior.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

…tion

- add IConfigService.replaceSections: applies several domains in a single
  state transition — one disk write, one effective rebuild, change events
  fire only after all domains took effect
- rework ProviderDiscoveryService to absorb the orchestrator's two-phase
  removeProvider/setConfig host contract into one replaceSections write, so
  the kosong registries never pass through a halfway-removed catalog
- stop writing the env-synthesized __kimi_env__ slice to config; the
  bridge's event-driven sync carries it into the registries on its own
- fixes sporadic "model is not configured" errors when starting kimi web,
  caused by the background refresh transiently clearing the model catalog
  while the first session was being created
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8fe7883

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@8fe7883
npx https://pkg.pr.new/@moonshot-ai/kimi-code@8fe7883

commit: 8fe7883

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fa85e8bbd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (stripped === undefined) {
delete this.raw[domain];
} else {
this.raw[domain] = this.registry.validate(domain, stripped);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stage batch writes before mutating raw config

When a multi-section write has a valid earlier domain followed by a later domain that fails stripEnv/validation, this assignment has already changed this.raw, but the method exits before persistDomains() and rebuildEffective(). Callers see a rejected atomic write while inspect() and future merges now use a partially applied user layer; stage all stripped/validated values in a temporary raw copy first, then swap it in only after the whole batch succeeds.

Useful? React with 👍 / 👎.

Comment on lines +206 to +210
/**
* Replaces several sections in ONE state transition: every domain's raw
* value is updated first, the document is persisted with a single disk
* write, and the effective view is rebuilt once — change events fire only
* after all domains have taken effect, so a reader can never observe a

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move method comments into the module header

This adds method-level documentation below the top-of-file block, but the agent-core-v2 guide requires comments to live solely in the header and not beside functions or methods. Please move the replaceSections contract details into the file header or another approved reference so this patch does not add a new exception to the local convention.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L11-L15

Useful? React with 👍 / 👎.

…config

Validate and strip every domain into a staged copy of the raw/memory layer
first, then swap it in only after the whole batch succeeds — previously a
later domain failing validation left earlier domains already applied to
this.raw/this.memory while the call reported failure, exposing a partially
applied user layer to inspect() and future merges.
@sailist
sailist merged commit f1a3475 into MoonshotAI:main Jul 30, 2026
14 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 30, 2026
zicochaos added a commit to zicochaos/kimi-code that referenced this pull request Jul 30, 2026
Conflict resolutions (6 files):
- agent-core-v2/docs/state-manifest.d.ts: took upstream, regenerated via gen:state-manifest.
- agent-core/test/harness/runtime.test.ts: kept all 3 added tests (fork stale plugin_session_start disabled-skill case + 2 upstream plugin agent layer cases).
- agent-core-v2/test/agent/loop/loop.test.ts + test/tool/tool.test.ts: tools-hash inline snapshots collided (fork Agent tool exact-alias model param vs upstream MoonshotAI#2379 non-blocking TaskOutput contract); kept fork side, regenerated snapshots from merged behavior — exact-alias model contract preserved.
- agent-core-v2/test/app/config/config.test.ts: kept both import additions (fork PERSIST_DEFAULT_MODEL_SECTION + upstream PROVIDERS_SECTION, atomic config refresh MoonshotAI#2410).
- kap-server/test/skills.test.ts: restartWithDisabledSkills helper now passes TEST_HOST_IDENTITY (upstream made StartServerOptions.hostIdentity required).

Verification:
- agent-core-v2: 4265/4265 pass; gen:state-manifest --check clean.
- kap-server: 927/927 pass.
- agent-core: 4147 pass; 4 failures in test/harness/coder-subagent-tools.test.ts are pre-existing spawn /bin/bash ENOENT on NixOS (not merge-related).
- kimi-code TUI: 2487 pass. typecheck: agent-core, agent-core-v2, kap-server clean.
- lint: 2 errors in packages/oauth/src/managed-userinfo.ts are pre-existing on upstream (no-redundant-type-constituents, MoonshotAI#2363), unchanged by this merge.

Closes the sync tracked in #23.
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
Pidbid added a commit to Pidbid/kkm that referenced this pull request Jul 31, 2026
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