Services and Context: Share Capabilities Without Binding Implementations
Use Service, Context, and declaration merging to understand how providers, consumers, and scopes connect DeepSeek Harness capabilities.
In one sentence: a service provides a stable capability name, Context provides the lookup, and consumers depend on the name instead of a provider file or class.
“Swap the implementation” therefore does not mean editing every caller. Change the provider in composition; consumers still read the same ctx.<key>.
01 / A service has three roles
A replaceable capability has at least three roles:
| Role | Responsibility | Cordis location |
|---|---|---|
| Service Definition | Names the capability and its callable interface | TypeScript types and service agreement |
| Service Provider | Creates and registers an instance | Service subclass or registry |
| Consumer | Uses the capability | inject plus ctx.service |
One role alone is not a complete replaceable capability. A provider without consumers is unused; a consumer that imports the provider has lost the replacement point.
02 / How a provider enters Context
import { Service, type Context } from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis' {
interface Context {
greeter: GreeterService
}
}
export class GreeterService extends Service {
constructor(ctx: Context) {
super(ctx, 'greeter')
}
greet(who: string) {
return 'Hello, ' + who + '!'
}
}
export function apply(ctx: Context) {
ctx.plugin(GreeterService)
}super(ctx, 'greeter') registers the instance in the greeter service slot. Registration is an effect owned by the current Fiber, so unloading the provider removes the service from the scope.
declare module performs TypeScript declaration merging only; it does not wire runtime behavior. Runtime wiring comes from Service registration. The declaration gives consumers type-safe access to ctx.greeter.
03 / A consumer declares what it needs
import type { Context } from '@deepseek-ai/cordis'
export const name = 'consumer'
export const inject = ['greeter']
export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}inject guarantees that the service exists when apply runs. The consumer does not import GreeterService, so composition can point greeter at a different provider.
For an optional dependency, probe with ctx.get('greeter'). By default, ctx.get() returns only an ACTIVE service; without an available provider it returns undefined and creates no dependency edge. Do not read an undeclared ctx.greeter property directly; the proxy can throw because inject is missing.
04 / Context has more than one plane
Cordis Context is the entry point for service resolution, plugin registration, and event dispatch. Child contexts can be created below the root:
extend()adds child metadata without mutating the parent.isolate(name)creates an independent service scope.intercept(name, config)adds local service configuration for a subtree.
The same service name can therefore resolve to different instances in different groups. DeepSeek Harness uses this idea for profiles, agent presets, and isolated capability compositions.
05 / Named capabilities in DSH
In the Harness architecture, the tool registry, LLM adapters, live Agent registry, and session store are Context-connected capabilities:
ctx.toolsowns tool registration and execution.ctx.llmcarries message, stream, and model-adapter capabilities.ctx.agentsowns live Agents andagent/*events.ctx.sessionsowns the durable session event log.
Providers can change while consumers keep the stable service name. That is why a capability seam can turn one provider choice into product-wide behavior.
Where to go next
- inject: Let Dependencies Decide Load Order
- Events: Notify Without Knowing Who Listens
- Cordis Inside DeepSeek Harness
Sources: services tutorial, Context implementation, Service implementation, and Harness architecture.
More Posts
inject: Let Dependencies Decide Load Order, Not YAML Line Numbers
Understand PENDING, dependency tracking, and optional service probes, including why provider swaps move consumers automatically.
waterfall: Intercept, Wrap, and Short-Circuit with next()
Follow a Cordis waterfall chain to see listeners delegate downstream, wrap results, or short-circuit when they own the decision.
Cordis Inside DeepSeek Harness: Making Everything a Plugin
Trace dsh-base, Context services, event domains, and the tool pipeline to see how Cordis organizes a replaceable Agent runtime.
Newsletter
Join the community
Subscribe to our newsletter for the latest news and updates