feat(agent-core-v2): add per-scope keyed state container#2192
Conversation
…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 detectedLatest commit: 2423cba The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
commit: |
There was a problem hiding this comment.
💡 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".
| // 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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 👍 / 👎.
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
statedomain) 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 (
statedomain)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:
_baseStateRegistry: typedStateKey/defineStatedescriptors withregister/get/set, per-keyonDidChangeand globalonDidChangeAnyevents, andBugIndicatingErroron duplicate or unregistered key access.IStateService(App),ISessionStateService(Session),IAgentStateService(Agent) — scoped plain-data state lives in one observable container that dies with the scope.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:
defineStatekeys registered inISessionStateService, and Agent-scope services' mutable state (activityView, goal, toolDedupe, ...) intoIAgentStateService, accessed through states-backed accessors.StateRegistry.snapshot()with JSON-safe serialization for Map/Set/Date/circular values.3. Statically generated state manifest
Problem: The set of registered state keys was only discoverable by reading every service constructor.
What was done:
gen:state-manifest: a ts-morph static pass that collects everydefineStatekey and itsregistercall sites intodocs/state-manifest.d.ts, with value types expanded inline.4. kimi-inspect observability UI
Problem: The inspector had no way to view the new scoped state.
What was done:
ISessionStateService.snapshot()into a live diff tree, and an agent State tab in the merged Audit/Agent RightPanel pollingIAgentStateService.snapshot().StateCard/InteractionsCardcomponents; collapse multiline strings in the state tree into a compact hover preview.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.