Skip to content

feat(agent-core-v2): add per-scope keyed state container#2192

Merged
sailist merged 10 commits into
MoonshotAI:mainfrom
sailist:feat/agent-state-container
Jul 26, 2026
Merged

feat(agent-core-v2): add per-scope keyed state container#2192
sailist merged 10 commits into
MoonshotAI:mainfrom
sailist:feat/agent-state-container

Conversation

@sailist

@sailist sailist commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

Services in the experimental engine (agent-core-v2) keep their mutable runtime state scattered across private instance fields. There is no single place to observe what a session or an agent currently holds: debugging tools cannot inspect live state, and disposing a scope leaves state lifetime implicit in each service's own cleanup. Two follow-on problems appeared once state became observable: naive snapshot/export of service state deep-copies live resource graphs (tool instances holding service references) until the process runs out of heap, and force-instantiating side-effect services at startup relied on hand-maintained lists that drift out of date.

Summary

This PR introduces a per-scope keyed state container (the state domain) for the experimental engine, migrates Session- and Agent-scope services' plain-data mutable state into it, makes snapshots safe by keeping resource-holding fields out of the container, and adds a statically generated manifest of every registered state key. kimi-inspect gains the matching observability UI: a Session pane and State tabs that render live snapshots.


1. Per-scope keyed state container (state domain)

Problem: Scoped runtime state had no observable owner; startup wiring of side-effect services was a hand-maintained list of get() calls.

What was done:

  • Add a _base StateRegistry: typed StateKey / defineState descriptors with register / get / set, per-key onDidChange and global onDidChangeAny events, and BugIndicatingError on duplicate or unregistered key access.
  • Bind thin per-scope services at each tier: IStateService (App), ISessionStateService (Session), IAgentStateService (Agent) — scoped plain-data state lives in one observable container that dies with the scope.
  • Instantiate every registered service eagerly at scope creation, following the dependency graph automatically, and drop the hand-maintained force-instantiation lists.

2. Migrate service state into the containers, snapshot-safe

Problem: Session- and Agent-scope services held mutable state in private fields; once registered, naive snapshot() deep-copied resource graphs reachable from registered values and could exhaust the heap.

What was done:

  • Migrate plain-data fields of 13 Session-scope services (cron, interaction, sessionActivity, catalogs, fs, log, metadata, skillCatalog, toolPolicy, workspaceContext, ...) to defineState keys registered in ISessionStateService, and Agent-scope services' mutable state (activityView, goal, toolDedupe, ...) into IAgentStateService, accessed through states-backed accessors.
  • Add StateRegistry.snapshot() with JSON-safe serialization for Map/Set/Date/circular values.
  • Keep fields holding live resources (tool entries, managed tasks, turn jobs, prompt records, MCP registrations, profile callbacks) as plain private fields so the container only holds snapshot-safe plain data; snapshots stay small and serializable by construction.

3. Statically generated state manifest

Problem: The set of registered state keys was only discoverable by reading every service constructor.

What was done:

  • Add gen:state-manifest: a ts-morph static pass that collects every defineState key and its register call sites into docs/state-manifest.d.ts, with value types expanded inline.
  • Enforce freshness with a test that rebuilds the manifest and fails when the checked-in file is stale.

4. kimi-inspect observability UI

Problem: The inspector had no way to view the new scoped state.

What was done:

  • Rework the chat layout: a SessionPane column next to the sidebar with Services (pending interactions + session Service panels) and State tabs polling ISessionStateService.snapshot() into a live diff tree, and an agent State tab in the merged Audit/Agent RightPanel polling IAgentStateService.snapshot().
  • Extract shared StateCard / InteractionsCard components; collapse multiline strings in the state tree into a compact hover preview.

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.

sailist added 8 commits July 26, 2026 09:14
…at scope creation

- add instantiateAll to scope creation: every service registered for a
  scope tier is constructed when the scope is created, following the
  static dependency graph; a failing constructor fails scope creation
- drop the hand-maintained eager-resolution lists: igniteEagerServices
  in AgentLifecycleService, the force-instantiated session services in
  SessionLifecycleService, and the kosong config bridge get in bootstrap
- update DI docs and agent-core-dev skill guidance for the new semantics
- adjust affected tests (registry hygiene, timer draining, listener
  ordering) and add scope-tree coverage for eager instantiation
- add _base StateRegistry: typed StateKey/defineState descriptors with
  register/get/set, per-key onDidChange and global onDidChangeAny events,
  and BugIndicatingError on duplicate or unregistered key access
- bind thin per-scope services at each tier: IStateService (App),
  ISessionStateService (Session), IAgentStateService (Agent), so scoped
  plain-data state lives in one observable container that dies with the scope
- register the state domain in the layer check script and export the new
  services from the package index
- add StateRegistry unit tests and scoped resolution tests
…onStateService

- add StateRegistry.snapshot() with JSON-safe serialization for Map/Set/Date/circular values
- migrate plain-data fields of 13 session-scope services (cron, interaction, sessionActivity, agentProfileCatalog, fs, fsWatch, log, metadata, skillCatalog, toolPolicy, workspaceCommand, workspaceContext) to defineState keys registered in sessionState
- register SessionStateService in affected tests and add test/state/stubs.ts helper
…ight dock

- add SessionPane column next to the sidebar: Services tab (pending
  interactions + session Service panels) and State tab polling
  ISessionStateService.snapshot() every second, rendered as a live
  diff tree
- merge the transcript audit panel and the agent inspector into one
  RightPanel with Audit/Agent tabs that keep panel state across switches
- extract InteractionsCard from Inspector into its own component
- collapse multiline strings in StateTree into a compact hover-preview
  button with a viewport-clamped fixed popup
- test: cover StateRegistry.snapshot() conversions (Map/Set/circular);
  pass a session state service to SessionInteractionService in kap-server
  test fakes
- update the AGENTS.md project map for the new layout
…agentState

Register each Agent-scope service's mutable fields into the agent-state
container (IAgentStateService) via defineState keys, and access them
through get/set accessors backed by states.get/set. Promise locks,
disposables, and other mechanism-only fields stay plain instance fields.

- define per-service state keys (e.g. activityView.*, goal.*, toolDedupe.*)
  and register them in each service constructor
- replace direct field reads/writes with states-backed accessors across
  the touched services
- update the affected unit tests for the new IAgentStateService dependency
- StateRegistry.snapshot() now stops at custom-prototype boundaries and
  emits '(ClassName)' markers, so resource graphs reachable from registered
  values (e.g. tool instances holding service references) can no longer
  exhaust the heap during export; plain data keeps recursing
- add agent-scope state test covering the full assembled agent scope
- kimi-inspect: extract the session State card into a shared StateCard and
  add an agent State tab in RightPanel polling IAgentStateService.snapshot()
- document the per-scope state container pattern in the agent-core-dev skill
… container

- move fields holding live resources (tool entries, managed tasks, turn
  jobs, prompt records, MCP registrations, profile callbacks) back to
  plain private fields so the agent state service only holds
  snapshot-safe plain data
- add gen:state-manifest script that statically collects defineState
  keys and their register call sites into docs/state-manifest.d.ts
- add state manifest freshness test and update agent-state docs
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2423cba

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

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

commit: 2423cba

@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: 3b58d66243

ℹ️ 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 +92 to +96
// Eagerly instantiate every service registered for this scope: importing a
// service module is enough — registration plus scope creation construct it,
// nobody has to `get()` it first. Iteration order is irrelevant because each
// `get` runs a DFS over the static dependency graph, so dependencies are
// always constructed before their dependents and cycles still throw

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the eager-instantiation comment into the header

Move or remove this implementation-side comment block. The scoped package guide requires comments to live solely in the top-of-file /** */ header and explicitly forbids comments beside functions or statements; this new narration violates that convention.

AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L13-L13

Useful? React with 👍 / 👎.

queryKey,
queryFn: async () => ({ id, snapshot: await fetchSnapshot() }),
refetchInterval: 1000,
placeholderData: (previous) => previous,

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 Clear placeholder state when switching agents

When the selected agent changes, this retains the previous query's {id, snapshot} as placeholder data, so the effect continues rendering the old agent's tree while the badge already names the new agent. If the new agent is stopped or its request fails, that stale state remains visible indefinitely; reset/suppress the tree until data for the current id arrives instead of carrying the prior owner forward.

Useful? React with 👍 / 👎.

@sailist
sailist merged commit 7799bd7 into MoonshotAI:main Jul 26, 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