# Understanding the Agent-Core Dependency Injection System in Kimi Code

> Explore Kimi Code's agent-core dependency injection system. Learn how ServiceCollection, SyncDescriptor, and InstantiationService manage lazy instantiation, cycle detection, and child containers.

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

---

**Kimi Code's core uses a custom dependency injection container in `packages/agent-core/src/di` built around `ServiceCollection`, `SyncDescriptor`, and `InstantiationService` to manage lazy instantiation, cycle detection, and scoped child containers.**

Kimi Code implements a VS Code-inspired dependency injection (DI) system that powers its agent-core architecture. Located under `packages/agent-core/src/di`, this container handles service registration, deferred instantiation, and dependency resolution while actively guarding against cyclic dependencies through graph-based detection.

## Core DI Components

The system centers on three primary abstractions defined in the `agent-core` package:

- **`ServiceCollection`**: Stores the mapping of service identifiers to either concrete instances or lazy descriptors in [`src/di/serviceCollection.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/serviceCollection.ts)
- **`SyncDescriptor`**: Packages constructors with static arguments for deferred instantiation in [`src/di/descriptors.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/descriptors.ts)  
- **`InstantiationService`**: Resolves services, creates instances, and manages the dependency graph in [`src/di/instantiationService.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/instantiationService.ts)

### ServiceCollection: The Service Registry

The `ServiceCollection` class maintains a private `_entries` Map that associates each `ServiceIdentifier<T>` with either an instantiated object or a `SyncDescriptor`. As implemented in [`src/di/serviceCollection.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/serviceCollection.ts), the collection distinguishes between immediately available instances and lazy factories using `instanceof SyncDescriptor` checks (lines 4-5).

When registering services, developers populate the collection with tuples of identifiers and their corresponding implementations:

```ts
import { ServiceCollection } from '#/agent-core/src/di/serviceCollection';
import { SyncDescriptor } from '#/agent-core/src/di/descriptors';

const collection = new ServiceCollection(
  [LoggerService, new LoggerService()],                 // Concrete instance
  [DatabaseService, new SyncDescriptor(DatabaseService)] // Lazy descriptor
);

```

### SyncDescriptor: Controlling Instantiation Timing

The `SyncDescriptor<T>` class defined in [`src/di/descriptors.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/descriptors.ts) encapsulates a constructor, static arguments, and an optional delayed-instantiation flag. When the `supportsDelayedInstantiation` parameter is set to `true`, the container creates a proxy via `GlobalIdleValue` that delays actual object creation until a property is accessed.

This mechanism enables expensive services—such as event emitters or database connections—to remain uninitialized until explicitly needed, reducing startup overhead for Kimi Code's agent sessions.

## Service Resolution Workflow

When code invokes **`instantiationService.invokeFunction`** or **`instantiationService.createInstance`**, the container executes a multi-phase resolution process defined in [`src/di/instantiationService.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/instantiationService.ts).

### Phase 1: Trace Initialization and Lookup

The container optionally starts a diagnostic trace via `Trace.traceInvocation` or `Trace.traceCreation` (lines 40-52). It then attempts to retrieve the service through `_getOrCreateServiceInstance`, which checks the `ServiceCollection` for existing instances or descriptors.

If the entry is a concrete instance, the container returns it immediately (line 99). For `SyncDescriptor` entries, the system proceeds to instantiation.

### Phase 2: Dependency Extraction and Recursive Resolution

For services requiring construction, the container extracts constructor dependencies using `_util.getServiceDependencies` (lines 51-53). Each dependency triggers a recursive call to `_getOrCreateServiceInstance`, building a complete dependency chain before invocation.

The actual instantiation occurs through `Reflect.construct` (line 80), with the gathered dependencies injected into the constructor. After creation, the container caches the result by replacing the `SyncDescriptor` with the actual instance in `_setCreatedServiceInstance` (lines 39-44).

### Handling Delayed Instantiation

For services registered with `supportsDelayedInstantiation: true`, the container performs a special proxy construction (lines 52-85). The `GlobalIdleValue` utility creates a placeholder object that instantiates the real service only when a property is first accessed, enabling truly lazy initialization for event-heavy services.

## Dependency Graph and Cycle Detection

The DI container constructs a **directed graph** (`Graph<Triple>`) during the resolution process to track dependencies and prevent circular references (lines 45-66).

### Graph Construction and Root Extraction

Each node in the graph represents a `SyncDescriptor` awaiting instantiation. Edges represent "depends-on" relationships between services. The algorithm repeatedly extracts **roots**—nodes with no outgoing edges—which are safe to instantiate because all their dependencies are already resolved.

If the algorithm encounters a non-empty graph with no available roots, it throws a `CyclicDependencyError` (lines 70-74). For debugging purposes, [`src/di/graph.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/graph.ts) provides a `findCycleSlow` method that traces the specific path causing the cycle.

## Child Containers and Service Scoping

The `InstantiationService` supports hierarchical containers through the **`createChild`** method. A child container inherits services from its parent via fallback lookup in `_getServiceInstanceOrDescriptor` (lines 55-58), while maintaining its own `ServiceCollection` for local overrides.

This pattern enables Kimi Code to isolate per-session or per-plugin services without polluting the global container:

```ts
const child = parent.createChild(
  new ServiceCollection([
    LoggerService, 
    new SyncDescriptor(MockLogger, [], true)
  ])
);

```

Child containers resolve dependencies by checking their local registry first, then traversing up to parent containers, enabling sophisticated scoping for testing and plugin isolation.

## Tracing and Performance Monitoring

When instantiated with tracing enabled (`_enableTracing: true`), the container records every service creation and function invocation. The `Trace` class captures:

- **Operation type**: Creation vs. Invocation (`TraceType.Creation`/`Invocation`)
- **Dependency tree**: Hierarchical relationships via `branch` nodes  
- **Timing data**: Duration measured at `stop()`

If instantiation exceeds 2ms or triggers additional service creation, the trace is added to `Trace.all` for diagnostic review. This visibility helps developers identify eager instantiation hotspots and optimize startup performance in complex agent configurations.

## Practical Implementation Examples

### Basic Service Registration and Retrieval

```ts
import { InstantiationService } from '#/agent-core/src/di/instantiationService';
import { ServiceCollection } from '#/agent-core/src/di/serviceCollection';
import { SyncDescriptor } from '#/agent-core/src/di/descriptors';

class Logger {
  log(msg: string) { console.log(msg); }
}

class Greeter {
  static deps = [{ id: Logger, index: 0 }];
  constructor(private logger: Logger) {}
  greet(name: string) {
    this.logger.log(`Hello, ${name}!`);
  }
}

const services = new ServiceCollection(
  [Logger, new SyncDescriptor(Logger)],
  [Greeter, new SyncDescriptor(Greeter)]
);

const container = new InstantiationService(services);
container.invokeFunction((accessor) => {
  const greeter = accessor.get(Greeter);
  greeter.greet('Kimi'); // Outputs: Hello, Kimi!
});

```

### Implementing Scoped Overrides with Child Containers

```ts
class MockLogger {
  log(msg: string) { /* silent */ }
}

const parent = new InstantiationService(
  new ServiceCollection([Logger, new SyncDescriptor(Logger)])
);

const child = parent.createChild(
  new ServiceCollection([Logger, new MockLogger()])
);

child.invokeFunction((accessor) => {
  const greeter = accessor.get(Greeter);
  greeter.greet('test'); // No output due to MockLogger
});

```

### Configuring Delayed Instantiation

```ts
class EventBus {
  listeners: Function[] = [];
  onDidChange(fn: Function) { this.listeners.push(fn); }
}

class Component {
  static deps = [{ id: EventBus, index: 0 }];
  constructor(private bus: EventBus) {}
  attach() {
    this.bus.onDidChange(() => console.log('changed'));
  }
}

const services = new ServiceCollection(
  [EventBus, new SyncDescriptor(EventBus, [], true)], // delayed
  [Component, new SyncDescriptor(Component)]
);

const container = new InstantiationService(services);
// Note: EventBus is not yet instantiated

container.invokeFunction((accessor) => {
  const comp = accessor.get(Component);
  comp.attach(); // EventBus instantiated here on first access
});

```

## Summary

- **Lazy instantiation** is the default behavior when registering services as `SyncDescriptor` instances, reducing startup overhead until dependencies are actually accessed.
- **Cycle detection** uses a directed graph (`Graph<Triple>`) to identify circular dependencies before instantiation, throwing `CyclicDependencyError` when detected.
- **Child containers** enable scoped service overrides and isolation through hierarchical resolution, supporting per-session and per-plugin boundaries in [`src/di/instantiationService.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/instantiationService.ts).
- **Tracing capabilities** in `InstantiationService` provide performance insights by logging construction timing and dependency chains exceeding 2ms.

## Frequently Asked Questions

### How does Kimi Code's DI container differ from standard JavaScript DI libraries?

Kimi Code's `agent-core` dependency injection system is purpose-built for the VS Code extension host environment, featuring tight integration with `SyncDescriptor` for synchronous lazy loading and built-in cycle detection through graph traversal. Unlike generic JS DI libraries, it specifically handles "delayed instantiation" via `GlobalIdleValue` proxies to support expensive service initialization without blocking the extension host.

### What triggers the CyclicDependencyError in the DI system?

The container throws `CyclicDependencyError` when the dependency graph algorithm in [`src/di/instantiationService.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/instantiationService.ts) (lines 70-74) cannot find a root node—meaning every remaining service depends on another service that hasn't been instantiated yet. This indicates a circular dependency chain that would cause infinite recursion during construction.

### Can services be registered after the InstantiationService is created?

No, the `ServiceCollection` is effectively sealed once passed to the `InstantiationService` constructor. While child containers can extend or override registrations via `createChild`, the root container's service map remains immutable to ensure deterministic resolution behavior and thread-safe access patterns.

### How does delayed instantiation work with constructor dependencies?

When a service is registered with `supportsDelayedInstantiation: true`, the container creates a proxy object using `GlobalIdleValue` from [`src/di/util/idleValue.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/di/util/idleValue.ts). This proxy intercepts property access and invokes the real constructor only when a method or property is first used, while still maintaining proper dependency injection through the static `deps` metadata array.