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

> Discover the differences between agent-core-v2 DI system and agent-core. Explore isolated service lifecycles, explicit multi-agent support, and easier testing.

- Repository: [Moonshot AI/kimi-code](https://github.com/MoonshotAI/kimi-code)
- Tags: comparison
- Published: 2026-07-25

---

**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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/MoonshotAI/kimi-code/blob/main/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.