Skip to content

feat(agent-core-v2): add DI scope engine and domain services#1091

Open
sailist wants to merge 2 commits into
MoonshotAI:mainfrom
sailist:kimi-code-di-v4
Open

feat(agent-core-v2): add DI scope engine and domain services#1091
sailist wants to merge 2 commits into
MoonshotAI:mainfrom
sailist:kimi-code-di-v4

Conversation

@sailist

@sailist sailist commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces packages/agent-core-v2, the next-generation agent engine built on a four-level DI Scope architecture (Core / Session / Agent / Turn). It ports VSCode-style dependency-injection primitives into a reusable base layer, then re-implements all domain services on top of it. The v1 engine (packages/agent-core) is left untouched; switching the server/SDK over is a follow-up step.


Related Issue

No linked issue. This is foundational groundwork for the v4 agent engine: the current packages/agent-core wires services together ad hoc, which makes lifecycle ownership, per-session/per-turn isolation, and testing harder than they should be. agent-core-v2 makes service lifetimes and dependencies explicit through a typed DI container and a scope tree.

Problem

Today, agent-core services are constructed and composed manually. There is no unified notion of service lifetime (singleton vs. per-session vs. per-turn), no enforced dependency direction between domains, and no ergonomic way to override services in tests. As the engine grows, this leads to scattered construction logic, hidden coupling, and brittle test setup.

What changed

1. DI Scope engine

Problem: There is no shared infrastructure for declaring services, their lifetimes, and how they depend on each other.

What was done:

  • Ported VSCode-style DI primitives into _base/di (service identifiers, decorators, instantiation service, disposable store).
  • Added a Scope layer on top: LifecycleScope, registerScopedService, a Core/Session/Agent/Turn scope tree, and IScopeHandle for scoped resolution and disposal.

2. Domain services

Problem: Domain logic currently lives in hand-wired modules without a consistent lifetime or registration model.

What was done:

  • Implemented 59 domain services across layers L1-L7 and cross-cutting domains: log, telemetry, environment, kaos, kosong, records, config, tool, skill, permission, approval, question, context, message, turn, injection, compaction, plan, goal, swarm, usage, tooldedup, background, cron, mcp, agent-lifecycle, session-context, session-activity, session, hooks, event, gateway, terminal, fs, workspace, filestore, auth.

3. Tooling, tests, and docs

Problem: Without guardrails, a large DI graph drifts: domains start importing across layers, and test setup diverges.

What was done:

  • Added a domain-layer import boundary checker and a service dependency-graph generator.
  • Refactored tests onto shared DI helpers (TestInstantiationService, DisposableStore) and consistent #/ import paths.
  • Standardized file-header and JSDoc style and added package docs (di-testing, errors, flag) plus AGENTS.md.

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 2 commits June 25, 2026 00:01
Introduce packages/agent-core-v2, the next-generation agent engine built
on a four-level DI Scope architecture (Core/Session/Agent/Turn).

- Port VSCode-style DI primitives into _base/di and add a Scope layer
  (LifecycleScope, registerScopedService, Scope tree, IScopeHandle).
- Implement all 59 domain services across L1-L7 and cross-cutting
  domains (log, telemetry, environment, kaos, kosong, records, config,
  tool, skill, permission, approval, question, context, message, turn,
  injection, compaction, plan, goal, swarm, usage, tooldedup, background,
  cron, mcp, agent-lifecycle, session-context, session-activity, session,
  hooks, event, gateway, terminal, fs, workspace, filestore, auth).
- Add a domain-layer import boundary checker and a service dependency
  graph generator.
- Register the package in flake.nix (workspacePaths / workspaceNames).

v1 (packages/agent-core) is left untouched; the server/SDK switchover is
a later step. See plan/ for the design, roadmap, skeleton spec, and the
generated dependency graph.
- normalize file-header and JSDoc comment style across src domains
- refactor tests to shared DI helpers (TestInstantiationService,
  DisposableStore) and consistent `#/` import paths
- add package docs (di-testing, errors, flag) plus AGENTS.md and
  gap analysis
@changeset-bot

changeset-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7749108

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 Jun 25, 2026

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

commit: 7749108

@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: 7749108e39

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

@ITelemetryService _telemetry: ITelemetryService,
@ILogService _log: ILogService,
@IAgentLifecycleService _agentLifecycle: IAgentLifecycleService,
@ILoopRunner private readonly loopRunner: ILoopRunner,

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 Resolve turn runners from a reachable scope

In a real Agent scope, ITurnService is constructed from the Agent collection and its ancestors, but ILoopRunner is registered only at Turn scope in src/turn/loopRunner.ts, which is a child scope and therefore not visible here. Resolving ITurnService from an agent handle will throw for an unknown loopRunner before prompt() can run; create a Turn scope per turn and resolve the runner there, or give the runner a lifetime reachable from Agent scope.

Useful? React with 👍 / 👎.

Comment on lines +43 to +47
const collection = new ServiceCollection();
for (const entry of getScopedServiceDescriptors(LifecycleScope.Session)) {
collection.set(entry.id, entry.descriptor);
}
const child = this.instantiation.createChild(collection);

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 Seed session context when creating sessions

This child collection contains only Session-scoped descriptors and never registers the required ISessionContext seed. Services returned from a session created by this registry that declare @ISessionContext (for example AgentLifecycleService and CronService) will fail to resolve under the strict scoped container, so a gateway-created session cannot create agents or service prompts. Add the session context seed, or otherwise register that token, before creating the child scope.

Useful? React with 👍 / 👎.

constructor(
@IKaosFactory _kaosFactory: IKaosFactory,
@ILogService _log: ILogService,
private readonly registry: WorkspaceRegistry = new WorkspaceRegistry(undefined as never, undefined as never),

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 Inject the shared workspace registry

When WorkspaceFsService is instantiated by DI, no third static argument is supplied, so this default creates a fresh empty WorkspaceRegistry instead of using the Core-scoped registry. Workspaces registered through IWorkspaceRegistry.register() are therefore invisible to IWorkspaceFsService.resolve(), causing valid workspace ids to throw as unknown; inject @IWorkspaceRegistry here instead of constructing a separate registry.

Useful? React with 👍 / 👎.


registerMcpTools(serverId: string, tools: readonly ToolDefinition[]): void {
for (const def of tools) {
this.mcp.set(`${serverId}:${def.name}`, def);

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 Store MCP tools under executable names

After registerMcpTools('srv', [{ name: 'm', ... }]), list() advertises a tool named m, but execute('m', ...) calls find() which looks up this.mcp.get(name). Because the map key is srv:m, listed MCP tools cannot be executed by their listed name unless callers know an unexposed prefixed key; use the same key that execute() accepts, or expose the prefixed name consistently.

Useful? React with 👍 / 👎.

constructor(
@IConfigService _config: IConfigService,
@ITelemetryService _telemetry: ITelemetryService,
private readonly oauth?: OAuthService,

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 Inject OAuth into the auth summary service

When this service is created by DI, the undecorated optional oauth parameter is never supplied, so summarize() always returns an empty array even after callers log in through the Core-scoped IOAuthService. Inject the registered @IOAuthService contract instead of relying on an optional concrete constructor argument.

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