Skip to content

refactor(agent-core-v2): decouple kosong from config persistence#2068

Merged
sailist merged 4 commits into
MoonshotAI:mainfrom
sailist:refactor/kosong-config-sections
Jul 22, 2026
Merged

refactor(agent-core-v2): decouple kosong from config persistence#2068
sailist merged 4 commits into
MoonshotAI:mainfrom
sailist:refactor/kosong-config-sections

Conversation

@sailist

@sailist sailist commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No tracking issue — this is a planned internal refactor of the experimental agent-core-v2 engine; the problem is explained below.

Problem

kosong, the engine's pure provider/model abstraction layer, was coupled to config persistence in both directions:

  • It owned the providers / models / thinking config-section zod schemas, TOML transforms, and env bindings, and hosted discovery / env-overlay / OAuth-token concerns, so a persistence-free layer knew about storage.
  • Runtime mutations that land in kosong's provider/model registries (discovery refresh, OAuth login provisioning, default-model selection) had no sanctioned path back to config.toml.

What changed

1. Two-way persistence bridge (app/kosongConfig)

Problem: kosong's in-memory provider/model registries and config.toml could drift apart.

What was done:

  • Hydrate the registries from the effective config at startup, push later config section edits into them, and persist registry-side mutations (discovery refresh results, OAuth provisioning, default-pointer changes) back through config.replace. Both directions are idempotent by deep comparison, so the sync loop terminates without reentrancy flags.
  • Move discovery orchestration, the KIMI_MODEL_* env overlay, and the OAuth token adapter into the same wrapper domain, so kosong never reaches up to persistence concerns.

2. Section declarations consolidated; kosong is types-only

Problem: kosong still owned the config-section zod schemas and section constants — knowledge of the persisted shape.

What was done:

  • Declare every kosong-owned section (constant + zod schema + env bindings + TOML transforms) in a single app/kosongConfig/configSection.ts.
  • kosong now declares hand-written types only (ProviderConfig, ModelRecord, ThinkingConfig, …); each schema is re-derived from its type and pinned at compile time via AssertExact<Equal<z.infer<typeof Schema>, Type>> (_base/utils/typeEquality), so type/schema drift in either direction fails typecheck.
  • Consumers (profile, llmRequester, auth, discovery) read/write sections through IConfigService with the constants from their new home; auth keeps a local OAuthRef schema (an L2 domain must not import the L3 wrapper) and a transitional auth>kosongConfig exception is registered in the domain-layer checker.

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.

- keep the kosong provider/model registries in memory and sync them with
  config.toml through a new app/kosongConfig two-way bridge: hydrate on
  startup, push config section changes into the registries, and persist
  runtime mutations (discovery refresh, OAuth provisioning, default-model
  pointer changes) back to disk
- declare the providers/models/thinking section constants, zod schemas,
  env bindings, and TOML transforms in a single app/kosongConfig
  configSection.ts; kosong keeps hand-written types only
- pin every schema to its kosong type at compile time via
  AssertExact<Equal<...>> (_base/utils/typeEquality)
- register a transitional auth>kosongConfig exception in the domain-layer
  checker for the OAuth provisioning flows
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b568629

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 22, 2026

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

commit: b568629

@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: e90056a220

ℹ️ 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".

// reference, conflicting credentials, ...) must not become the default.
const model = this.get(modelId);
await this.config.set(DEFAULT_MODEL_SECTION, modelId);
await this.models.setDefaultModel(modelId);

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 Preserve env-pinned defaults when setting the model

When KIMI_MODEL_NAME is set, app/kosongConfig/envOverlay.ts forces the effective defaultModel to __kimi_env_model__, and the discovery path explicitly writes default changes through config because only the effective config view knows about that pin. This direct registry update bypasses the overlay: the bridge later persists the raw value, but the effective defaultModel does not change, so no config event resets IModelService back to the env model. After calling setDefaultModel in that environment, catalog/auth APIs that read models.getDefaultModel() report the user-selected model while agent profile resolution still reads the env-pinned config default.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — fixed in 9a205f0. The write path stays registry-side (kosong cannot see the config service by design), and the bridge now re-asserts the pinned effective value into the registry after persisting, so registry reads can no longer diverge from the effective config view. Added a pinned-overlay test to the bridge suite.

sailist added 2 commits July 22, 2026 23:23
A registry-originated default-model/provider write lands only in the
config user layer when an effective overlay pins the section
(KIMI_MODEL_NAME pins defaultModel to the reserved env model): the
effective value does not move and no change event fires, so the registry
diverged from the effective config view — catalog/auth reads reported the
user pick while profile resolution kept the pinned model. After
persisting, the bridge now re-asserts the effective value into the
registry, restoring the pre-refactor behavior where every default-pointer
write was arbitrated by the effective view.
…y mutations

ProviderService/ModelService mutations resolved as soon as the in-memory
registry updated, while the kosongConfig bridge persisted the change on an
unawaited private chain — callers (klient kosong.*, set_default route,
OAuth provision, discovery refresh) could observe success before the write
reached config.toml, and a restart right after could lose it.

- fire registry change events through AsyncEmitter and await delivery in
  set/delete/replaceAll/setDefaultX, so a mutation resolves only after
  listeners' waitUntil work completes; loadAll keeps synchronous timing
- the bridge hooks its persists into waitUntil, hoists the equality guards
  into the listeners so config-originated echoes stay fully synchronous,
  and serializes persists on the chain as before
- retry a failed persist with bounded backoff (3 attempts); failures are
  logged, never rejected to callers, and the in-memory change stands
- default-pointer change events now carry an { id } payload (fireAsync
  requires object events)
- add the klient kosong-config stress example covering read-after-write,
  concurrent bursts, and restart durability
@sailist
sailist merged commit 188c0fc into MoonshotAI:main Jul 22, 2026
14 checks passed
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