Skip to content

refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states - #2909

Open
sailist wants to merge 1 commit into
MoonshotAI:mainfrom
sailist:refactor/event2-model-state
Open

refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states#2909
sailist wants to merge 1 commit into
MoonshotAI:mainfrom
sailist:refactor/event2-model-state

Conversation

@sailist

@sailist sailist commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

agent-core-v2 carried two overlapping write models: durable ops defined through defineOp/defineModel on the wire layer, and transient domain events declared via DomainEventMap module augmentation and published on an event bus. State changes therefore had two disjoint code paths (op apply + toEvent projection vs direct publish), event types were string-keyed maps rather than classes, and undo existed only as whole-snapshot checkpoint copies wired through a bespoke CHECKPOINTED_MODELS registry. There was no uniform patch history, and the op/event split made it hard to reason about what is persisted, replayed, or observable.

What changed

  • Unified event model: all ops and events are now Event2 subclasses carrying type + time. Durable events declare a schema and serialize() into the existing wire.jsonl record shape (byte-for-byte frozen, old v1.0–v1.5 journals still replay); transient events are never persisted. defineOp/Op/OpDescriptor/toEvent/PersistedOpMap/TransientOpMap/DomainEventMap are removed repo-wide.
  • Replayable states: the 25 former models are now defineState(...).replayable(...) definitions with mutation-style folds (.on(Event, fold)) powered by immer produceWithPatches. Durable fold results are journaled through the wire AppendLog; restore silently re-folds from the journal without re-publishing events.
  • Single dispatch pipeline: an Agent-scope dispatcher runs prepare → commit → journal → publish. Patch history, checkpoint(), and undoToCheckpoint() live on the dispatcher; the former whole-snapshot checkpointed models now ride on the same patch-based undo via an .undoable() trait, with context.undo semantics and UX unchanged.
  • State registry unification: replayable states register through the existing defineState/StateKey mechanism and scope state services; the read path is agentState.get(key) and the dispatcher no longer owns state instances. Ephemeral kv state keeps its imperative set unchanged.
  • Generators and lint: gen-wire-manifest / gen-state-manifest scan the new APIs (regenerated manifests), and the op-uniqueness lint became event-uniqueness covering the EVENT2_REGISTRY.
  • Downstream alignment: kap-server re-pins event types to the new classes (WS envelope timestamps now come from event.time), klient/node-sdk/acp-server type alignment, transcript package verified unchanged. Wire journal format, WS envelope shape, SDK flat events, and TUI behavior are intentionally frozen.

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.

@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: dea38ab

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 14, 2026

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

commit: dea38ab

…and replayable states

- replace defineOp/Op/OpDescriptor/toEvent and defineModel/defineCheckpointedModel with Event2 subclasses: durable classes declare static durable + schema, serialize() keeps the wire record shape byte-frozen, transient classes stay off the journal
- define states via defineState(...).replayable(...).on(Event2, fold): immer produceWithPatches folds with atomic prepare/commit, .undoable() trait drives prompt-submit checkpoints and context.undo, ephemeral kv keys keep imperative set
- degrade IWireService to a journal adapter; the agent event dispatcher owns the pipeline (fold -> set -> appendRecord -> publish) and silent restore
- align downstream event surfaces: kap-server WS envelope timestamp from event.time, klient event schemas gain time, node-sdk/acp-server/print wiring updated
- rewrite gen-wire-manifest/gen-state-manifest for the unified registry and replace the op-uniqueness lint with event-uniqueness
@sailist
sailist force-pushed the refactor/event2-model-state branch from 21b1e9d to dea38ab Compare August 14, 2026 03:34

@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: 21b1e9d584

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

Comment on lines +222 to +224
for (const entry of this.queue.splice(0)) {
entry.resolve();
}

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 Reject queued dispatches that were not run

When a dispatch fails after other reentrant dispatches have been queued, this catch resolves every remaining queued promise even though the finally clears the queue and none of those events were folded, published, or persisted. In the reentrant path (dispatching is true, e.g. a bus subscriber calling dispatch()), callers awaiting those returned promises will observe success with their event silently dropped; reject the pending entries or keep them queued for a later drain instead.

Useful? React with 👍 / 👎.

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