# How Cordis Context Manages Plugin Is isolation with Symbols: A Deep Dive into the Registry Architecture

> Discover how Cordis Context achieves robust plugin isolation using a two-layer symbol registry and Realm configuration for effective local and global scope management.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: deep-dive
- Published: 2026-09-12

---

**Cordis Context implements plugin isolation through a two-layer symbol registry system that maps service names to unique Symbols at the context level, while the Loader's Realm configuration extends this to support both local and global isolation scopes.**

The `cordiverse/cordis` framework provides a lightweight dependency injection system where multiple plugins share a global runtime without risking state leakage. By leveraging JavaScript's unique Symbol primitives, Cordis ensures that each plugin can receive its own instance of a service—such as a logger or database connection—without interfering with other plugins. This architecture is implemented across the core context module and the loader's configuration system.

## The Per-Context Symbol Registry

At the heart of Cordis isolation lies the `Context.isolate` mechanism, a static Symbol exported from [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts) that serves as the key for a private registry.

### Context Construction and the Isolate Map

When a new `Context` is instantiated in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts), the constructor initializes an empty isolation map using `Object.create(null)`:

```typescript
// packages/core/src/context.ts
this[symbols.isolate] = Object.create(null)

```

This map stores the relationship between service names (like `"logger"` or `"events"`) and their corresponding unique Symbols. Because `symbols.isolate` is a registered Symbol rather than a string property, it remains inaccessible to standard property enumeration, preventing accidental interference.

### The isolate() Method Shadowing

Plugins invoke `ctx.isolate(name, label?)` to create an isolated scope. This method, implemented in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) at lines 65-69, performs three critical operations:

1. Creates a **shadow** object that inherits the current isolate map
2. Generates a new `Symbol(name)` (or uses the provided `label`)
3. Returns a new `Context` instance extended with this shadow map

The new Context inherits all parent services but resolves the isolated service name to a different Symbol, effectively redirecting to a separate instance:

```typescript
import { Context } from 'cordis'

const root = new Context()

// Plugin A receives Symbol('logger') #1
const pluginA = root.isolate('logger')
pluginA.logger.info('Hello from A')

// Plugin B receives Symbol('logger') #2  
const pluginB = root.isolate('logger')
pluginB.logger.info('Hello from B')

```

## Loader-Level Realm Isolation

While the core Context provides manual isolation via `isolate()`, the Loader automates this through **Realms** defined in [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts). This system supports declarative isolation via configuration files.

### Local vs Global Realms

The Loader distinguishes between two isolation scopes through the `Realm` abstraction:

- **LocalRealm**: Creates entry-specific isolation using the entry ID as a suffix (`#<id>`). When a plugin declares `isolate: { foo: true }`, the loader generates a Symbol like `Symbol('foo#123')`.
- **GlobalRealm**: Creates shared isolation across multiple entries using a user-defined label (`@<label>`). When a plugin declares `isolate: { db: "shared-db" }`, the loader generates `Symbol('db@shared-db')`, allowing multiple plugins to intentionally share the same service instance while remaining isolated from the global default.

Each Realm maintains a `store: Dict<symbol>` where symbols are generated as `Symbol(`${key}${suffix}`)`.

### The patch-context Hook

During the `loader/patch-context` phase, the Loader replaces the entry's `Context.isolate` map with a new map containing the symbols returned by `Realm.access`. This method, located at lines 71-85 of [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts), resolves the appropriate realm and returns the correct Symbol for each service name.

The replacement preserves the prototype chain while swapping symbol references, allowing the plugin to use standard property access (`ctx.logger`) while actually resolving to an isolated instance.

## Service Resolution with ReflectService

The actual service lookup occurs in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts). The `ReflectService` queries the isolation map to determine which concrete implementation to return:

```typescript
// Inside a plugin
export default function (ctx: Context) {
  // Retrieve the unique Symbol for this context's logger
  const loggerSymbol = ctx[Context.isolate].logger
  
  // Access the concrete service instance from the reflect store
  const logger = ctx.reflect.store[loggerSymbol]
  logger.info('Running with isolated logger')
}

```

Because each plugin context may contain different Symbols for the same service name, `ctx.logger` resolves to different instances across plugins, even when accessed identically in code.

## Practical Implementation Examples

### Programmatic Isolation

For library authors or dynamic plugin systems, manual isolation provides fine-grained control:

```typescript
import { Context } from 'cordis'

const root = new Context()

// Create isolated child contexts
const analyticsPlugin = root.isolate('database', 'analytics-db')
const billingPlugin = root.isolate('database', 'billing-db')

// Each receives a distinct database connection
// despite both accessing `ctx.database`

```

### Configuration-Driven Isolation

For application developers using the Cordis Loader, isolation is configured declaratively:

```yaml

# cordis.yml

plugins:
  - name: analytics-worker
    isolate:
      logger: true          # LocalRealm: private logger instance

      cache: "shared-cache" # GlobalRealm: shared with other "shared-cache" entries

      
  - name: billing-worker  
    isolate:
      logger: true          # Distinct from analytics-worker logger

      cache: "shared-cache" # Same cache instance as analytics-worker

```

### Entry Attachment and Lifecycle

The linkage between entries and contexts occurs in [`packages/loader/src/config/entry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts), where each `Entry` attaches to its context via `Entry.key`. This key allows the Loader to track which isolation realm applies to which running plugin instance, enabling proper garbage collection of GlobalRealms when no entries reference them.

## Summary

- **Cordis Context plugin isolation** relies on a static Symbol registry (`Context.isolate`) that maps service names to unique Symbols stored in a private map initialized in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts).
- **The `isolate()` method** creates shadow contexts with new Symbols, allowing distinct service instances while maintaining inheritance from parent contexts.
- **Loader Realms** extend this to configuration-driven isolation, supporting both `LocalRealm` (entry-specific, suffix `#<id>`) and `GlobalRealm` (label-shared, suffix `@<label>`) in [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts).
- **Service resolution** occurs via `ReflectService` in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts), which looks up the concrete implementation using the context-specific Symbol from the isolate map.
- **The `loader/patch-context` hook** performs the actual substitution of isolate maps during plugin loading, ensuring isolation is established before the plugin executes.

## Frequently Asked Questions

### How does Cordis prevent symbol collisions between different plugins?

Cordis prevents collisions by using JavaScript's native `Symbol` primitive, which guarantees uniqueness even when multiple plugins request isolation for the same service name. When `Context.isolate('service')` is called, it generates `Symbol('service')`, and because Symbols are unique by identity rather than by string value, each call produces a distinct key for the internal registry. The Loader further distinguishes contexts through `LocalRealm` (appending entry IDs) and `GlobalRealm` (appending user-defined labels), ensuring no two active contexts accidentally share Symbol references unless explicitly configured to do so.

### What is the difference between LocalRealm and GlobalRealm in Cordis?

**LocalRealm** creates strictly entry-private isolation by appending the entry's unique ID to the service name when generating the Symbol (e.g., `Symbol('logger#42')`), ensuring that even if two plugins both request isolation for `logger`, they receive separate instances. **GlobalRealm** appends a user-defined label instead (e.g., `Symbol('db@shared-db')`), allowing multiple plugin entries to explicitly share the same isolated service instance while remaining isolated from plugins outside that realm. This enables both strict separation and intentional resource sharing as configured in the loader's isolation settings.

### Can a plugin access the parent context's services after calling `isolate()`?

Yes, calling `ctx.isolate()` returns a new Context that inherits from the original via prototype chain shadowing. The new context contains only the overridden Symbol mappings for the isolated services, while all other service lookups traverse up the prototype chain to the parent context. This means non-isolated services remain shared, allowing plugins to isolate only specific dependencies (like a database or logger) while retaining access to shared event buses or configuration services from the root context.

### Where does the actual service instance storage occur in Cordis?

Service instances are stored in the `ReflectService` internal store, accessible via `ctx.reflect.store`, which maps the unique Symbols (retrieved from `ctx[Context.isolate]`) to their concrete implementations. When a plugin accesses `ctx.serviceName`, the getter resolves the appropriate Symbol from the isolate map, then retrieves the corresponding instance from the reflect store. This separation between the isolation registry ([`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts)) and the service storage ([`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts)) allows Cordis to swap service implementations per context without affecting the global service registry.