How the agent-core-v2 DI System Differs from agent-core

The agent-core-v2 DI system replaces the global InstantiationService with per-agent scope handles (IAgentScopeHandle), enabling isolated service lifecycles, explicit multi-agent support, and simplified testing APIs.

The MoonshotAI/kimi-code repository powers the Kimi engine with a modular architecture where both agent-core and agent-core-v2 provide dependency injection (DI) capabilities for composing services like logging, telemetry, and tool execution. While the original agent-core relies on a single global container, the agent-core-v2 DI system was completely redesigned to better support multi-agent scopes, lazy initialization of event-driven services, and traceable graph construction.

Core Architectural Differences

Root Container Architecture

In agent-core, the DI layer centers on a single global InstantiationService created at application startup. This singleton stores all service registrations and handles instantiation across the entire process.

The agent-core-v2 DI system introduces a per-agent scope handle (IAgentScopeHandle) defined in packages/agent-core-v2/src/_base/di/scope.ts. Each running agent receives its own container instance that owns a private service map, preventing cross-talk between agents operating in the same process.

Service Registration API

Legacy agent-core uses a ServiceCollection class that stores pairs of <ServiceIdentifier, Service | SyncDescriptor> in a global registry. Services are registered once and shared universally.

In contrast, agent-core-v2 uses the addHandle(agentId, profileName, services) pattern demonstrated in packages/agent-core-v2/test/tool/tool.test.ts. This method registers a Map<unknown, unknown> of services specific to an individual agent, allowing different agents to bind different implementations of the same interface without conflict.

Service Resolution Patterns

The resolution APIs differ significantly between versions:

Legacy agent-core requires consumers to use instantiationService.invokeFunction((accessor) => …) or instantiationService.createInstance(MyClass), receiving a ServicesAccessor object with a get(id) method.

agent-core-v2 simplifies this to scopeHandle.get(serviceId), where scopeHandle is obtained from the current agent context. The handle can be passed directly between components, eliminating the need for accessor callbacks everywhere.

Scope Hierarchy and Parenting

agent-core supports child containers via InstantiationService.createChild(services), which creates a fallback chain to the parent for missing dependencies.

agent-core-v2 flattens this model: each agent’s scope can be parented to another scope (e.g., a session scoped to a workspace), but the mapping is explicit per-agent rather than a dynamic tree of generic children. This makes dependency flows predictable in multi-agent scenarios.

Lazy Initialization Strategy

For services exposing events (onDid…/onWill…), agent-core creates a proxy using GlobalIdleValue within _createServiceInstance (see packages/agent-core/src/di/instantiationService.ts). The real object instantiates only when a method is first accessed, but this delay operates globally.

agent-core-v2 shifts to handle-based lazy creation. A service descriptor can be stored in the agent’s map and only materializes when handle.get is called for that specific agent. This scopes the delay to the agent lifecycle rather than a global proxy.

Dependency Graph and Cycle Detection

Both systems use a Graph<string> algorithm to detect cyclic dependencies, throwing CyclicDependencyError when found. However, agent-core builds a global dependency graph that could be shared unintentionally across contexts.

agent-core-v2 constructs the graph per-agent, including implicit dependencies introduced by the scope itself. This isolation prevents side-effects where one agent’s service dependencies corrupt the graph of another.

Diagnostic Tracing

The legacy system attaches an optional Trace object to the global InstantiationService via an _enableTracing flag, recording creation versus invocation chains universally.

In agent-core-v2, tracing remains available but is scoped to the individual agent handle. This allows separate, non-contaminating traces for each agent without flooding the global log.

Testing and Mocking

Tests in agent-core exercise the global container directly (packages/agent-core/test/di/*), requiring careful cleanup to avoid state leakage between tests.

agent-core-v2 tests focus on per-agent APIs (packages/agent-core-v2/test/tool/tool.test.ts), demonstrating how developers can create mock agents with custom service maps. The explicit Map storage makes unit testing straightforward by allowing pre-populated mocks without affecting global state.

Code Examples

Legacy agent-core Usage

import { InstantiationService } from '@moonshot-ai/agent-core';
import { ILogService } from '@moonshot-ai/agent-core';

const root = new InstantiationService();
const logger = root.invokeFunction(accessor => accessor.get(ILogService));

New agent-core-v2 Usage

import { IAgentScopeHandle } from '@moonshot-ai/agent-core-v2';
import { ILogService } from '@moonshot-ai/agent-core-v2';

// Assume `agentId` is the identifier of the running agent.
const scopeHandle: IAgentScopeHandle = getAgentScopeHandle(agentId);
const logger = scopeHandle.get(ILogService);

Registering Mock Services for Testing

import { ILogService } from '@moonshot-ai/agent-core-v2';
import { SyncDescriptor } from '@moonshot-ai/agent-core-v2';

const mockLog: ILogService = { /* mock methods */ };
addHandle('test-agent', 'default', new Map([
  [ILogService, mockLog],
]));

Summary

  • agent-core-v2 replaces the global InstantiationService with isolated IAgentScopeHandle instances, ensuring each agent maintains its own service lifecycle.
  • Service registration shifts from a global ServiceCollection to per-agent Map objects via addHandle, simplifying testing and mocking.
  • The resolution API moves from invokeFunction with accessors to direct scopeHandle.get() calls, aligning with an agent-first mental model.
  • Lazy initialization is now scoped to individual agents rather than global proxies, reducing memory overhead and startup time in multi-agent processes.
  • Cyclic dependency detection and tracing operate per-agent, eliminating cross-contamination between concurrent agents.

Frequently Asked Questions

What is the primary architectural difference between the agent-core and agent-core-v2 DI systems?

The primary difference is the shift from a single global container to per-agent scoped handles. agent-core uses one global InstantiationService shared across the entire application, while agent-core-v2 creates an IAgentScopeHandle for each agent, isolating service instances and preventing interference between agents running in the same process.

How does service registration differ when migrating from agent-core to agent-core-v2?

In agent-core, you register services once in a global ServiceCollection using identifiers and SyncDescriptor objects. In agent-core-v2, you call addHandle(agentId, profileName, services) with a Map containing the specific services for that agent, as shown in packages/agent-core-v2/test/tool/tool.test.ts. This allows different agents to use different implementations of the same interface without global conflicts.

Can agent-core-v2 handle multiple agents without service cross-talk?

Yes. The agent-core-v2 DI system is explicitly designed for multi-agent scenarios. Because each agent receives its own scope handle with a private service map (stored as servicesByAgentId), services created for one agent cannot be accessed by another. When an agent finishes, its handle disposes all created services, ensuring clean lifecycle management without manual tracking.

How does lazy initialization work in agent-core-v2 compared to the legacy system?

agent-core uses GlobalIdleValue proxies that delay instantiation until a method is accessed, but these proxies exist in the global scope. agent-core-v2 implements handle-based lazy creation, where service descriptors remain unmaterialized in the agent's Map until handle.get() is called. This delays initialization per-agent rather than globally, improving performance when only a subset of agents require specific services.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →