Framework

Services & Dependencies

Master subclassing Service for cross-plugin singleton sharing, declared dependencies, and TypeScript context augmentation.

In sophisticated multi-agent and tool architectures, plugins frequently need to share foundational capabilities (such as vector database pools, distributed caches, or centralized telemetry pipelines).

In DeepSeek Harness, a Service represents the standard singleton abstraction for cross-plugin capability sharing. Combined with the Cordis microkernel's declarative dependency injection (inject), the system automatically resolves Directed Acyclic Graph (DAG) startup topologies, eliminating null reference bugs and dependency tangles.

This tutorial uses a “Vector Memory Store Service” as a real-world case study to explain extending the Service base class, TypeScript module augmentation for type safety, and consuming services in downstream plugins.


Conceptual Foundation: Core Harness Service Infrastructure

In Harness, major infrastructure capabilities are exposed as named singleton services mounted onto Context:

typescript
ctx.tools    // Tool registry service: manages model-callable DSL Tool instances
ctx.llm      // LLM inference service: handles streaming chunk feeds & adapter routing
ctx.agents   // Agent loop service: orchestrates turn sessions, Trajectory audits & planning

Four Key Characteristics of Services:

  1. Global Singleton: Exactly one instance per service name (e.g. vectorStore) exists within a microkernel context tree;
  2. Declarative Resolution: Downstream plugins simply declare inject = ['vectorStore']; Cordis handles activation order;
  3. Full TypeScript Type Safety: Module augmentation gives ctx.vectorStore complete IDE autocomplete throughout the project;
  4. Managed Lifecycle: Services are first-class plugins with deterministic mounting and unmounting lifecycles.

Step 1: Author the Custom Service Class & Declaration Merging

Create custom-plugins/memory-store/src/index.ts:

typescript
import { Service, type Context } from '@deepseek-ai/cordis';

// 1. Vocabulary Types
export interface MemoryDocument {
    id: string;
    text: string;
    embedding?: number[];
    metadata?: Record<string, any>;
}

// 2. TypeScript Module Augmentation: augment the Context interface
declare module '@deepseek-ai/cordis' {
    interface Context {
        vectorStore: VectorStoreService;
    }
}

// 3. Extend the Cordis Service singleton base class
export default class VectorStoreService extends Service {
    // Declare upstream dependencies required by this service
    static inject = ['llm'];

    private memoryCache: Map<string, MemoryDocument> = new Map();

    constructor(ctx: Context) {
        // super(ctx, 'vectorStore') binds this instance to ctx.vectorStore
        super(ctx, 'vectorStore');
    }

    // Public method: persist memory chunk
    public async storeMemory(doc: MemoryDocument): Promise<void> {
        this.memoryCache.set(doc.id, doc);
        console.log(`[MemoryStore]  Stored memory chunk: ${doc.id} (Total: ${this.memoryCache.size})`);
    }

    // Public method: semantic similarity retrieval
    public async searchSimilar(query: string, topK: number = 3): Promise<MemoryDocument[]> {
        console.log(`[MemoryStore]  Querying memories matching: "${query}"...`);
        return Array.from(this.memoryCache.values()).slice(0, topK);
    }
}

Step 2: Consuming the Service in Downstream Plugins

Downstream plugins declare the service name in their inject array:

typescript
import type { Context } from '@deepseek-ai/cordis';

export const name = 'agent-memory-retriever';

// Declare dependencies on vectorStore and tools services
export const inject = ['vectorStore', 'tools'];

export function apply(ctx: Context) {
    // Microkernel guarantee: ctx.vectorStore is in ACTIVE state when apply runs
    ctx.on('session/before-turn', async (session) => {
        const historyDocs = await ctx.vectorStore.searchSimilar(session?.userPrompt ?? '', 2);
        console.log(`[Retriever] Injected ${historyDocs.length} historical memory chunks for turn.`);
    });
}

DAG Topological Resolution and Inversion of Control

┌─────────────────────────────────────────────────────────────┐
│                 Cordis Service Dependency DAG               │
├─────────────────────────────────────────────────────────────┤
│         [ ctx.llm ] (Base LLM Inference Infrastructure)      │
│                          ▲                                  │
│                          │ (static inject = ['llm'])        │
│         [ ctx.vectorStore ] (Vector Memory Service)         │
│                          ▲                                  │
│                          │ (inject = ['vectorStore'])       │
│         [ agent-memory-retriever ] (Consumer Plugin)        │
└─────────────────────────────────────────────────────────────┘

During startup, Cordis constructs a Directed Acyclic Graph and boots services bottom-up. Circular dependencies are detected and halted immediately with clear error diagnostics.


Frequently Asked Questions (FAQ)

Q1: Why do we need declare module in addition to super(ctx, 'vectorStore')?

Answer: super(ctx, 'vectorStore') executes at runtime to bind the instance onto the ctx object; declare module '@deepseek-ai/cordis' provides compile-time TypeScript type declarations for IDE autocomplete. Both work together.

Q2: What happens if a plugin accesses ctx.vectorStore without declaring inject?

Answer: If VectorStoreService happened to initialize first, it might temporarily work; but during hot reload or deferred loading, undeclared access causes TypeError: Cannot read properties of undefined. Always declare consumed services in inject.

Q3: How do custom Service classes clean up their own resources on unload?

Answer: Service instances are Cordis plugins. Use this.ctx.effect(() => { return () => cleanup(); }) inside the constructor to register teardown callbacks for connection pools or sockets.

Q4: How do I mock ctx.vectorStore during unit tests?

Answer: Create a Mock class extending VectorStoreService and mount it into a test Context instance (testCtx.plugin(MockVectorStoreService)), allowing pure in-memory test execution.