DEV Community

Cover image for DeepSeek Harness Series (07): Capability Seams — Swap One Config Line, Swap Everything
WonderLab
WonderLab

Posted on

DeepSeek Harness Series (07): Capability Seams — Swap One Config Line, Swap Everything

Start with a Household Analogy

Think about a light switch. It doesn't care where the electricity comes from — the power grid, a solar panel, or a generator all work fine. The switch only defines one thing: on/off. As long as whatever is behind it honors that interface, you flip it and the light comes on.

That's the essence of a Seam: an interface boundary that separates "using this capability" from "how this capability is implemented."

In dsh:

  • Filesystem operations are a Seam (ctx.fs)
  • Shell execution is a Seam (ctx.shell)
  • LLM calls are a Seam (ctx.llm)
  • Process sandboxing is a Seam (ctx.sandbox)

You can swap the default local filesystem for an E2B sandbox filesystem without changing a single line of tool code. That's the problem Capability Seams solve.


Three Roles in Every Seam

Every Seam involves three participants:

┌────────────────────────────────────────────────────────┐
│  Consumer: tool-fs                                     │
│  import ctx.fs → calls ctx.fs.readFile(path)           │
│                  ctx.fs.writeFile(path, content)       │
└─────────────────────┬──────────────────────────────────┘
                      │ depends on service "ctx.fs"
┌─────────────────────▼──────────────────────────────────┐
│  Service Definition: dsh-fs                             │
│  interface FileSystem { readFile, writeFile, ... }     │
└─────────────────────┬──────────────────────────────────┘
                      │ implements
      ┌───────────────┴───────────────┐
      ▼                               ▼
  fs-local                         fs-e2b
  (local filesystem)            (E2B sandbox)
Enter fullscreen mode Exit fullscreen mode

The division of responsibilities:

Role Responsibility Example
Service Definition Define the interface + service name dsh-fs: defines the method signatures of ctx.fs
Service Provider Implement the interface, register it on ctx fs-local, fs-e2b, fs-sandbox
Service Consumer Use the interface, unaware of the implementation tool-fs: the file-reading/writing tool plugin

A Consumer only knows the service name (like ctx.fs) — it has no idea which Provider is behind it. Swapping a Provider only touches the Bundle configuration; Consumers are completely unaffected.


Core Seams at a Glance

dsh ships with the following built-in Seams:

Seam Service Name Default Implementation Can Be Swapped For
Filesystem ctx.fs fs-local (host machine) fs-e2b (E2B sandbox), fs-sandbox (restricted local)
Shell execution ctx.shell bash-local bash-sandbox (restricted exec), remote shell
Process sandbox ctx.sandbox sandbox-local (bwrap/Seatbelt/ACL) container, microVM
LLM adapter ctx.llm llm-deepseek llm-pi-ai and other third-party providers
Auth credentials ctx.credentials credentials-local remote vault
User settings ctx.settings settings-file remote settings service
Persistent storage ctx.sessionPersistence session-persistence-jsonl SQLite, remote backend

Deep Dive: ctx.fs (Filesystem Seam)

Why not just import fs from 'node:fs'?

Directly importing Node.js's built-in fs module creates three concrete problems:

  1. During testing: to test any "read a file" logic, you need real files on disk. Or you mock fs, but mocking Node built-ins requires extra tooling (jest.mock, etc.) and is a headache.
  2. Inside a sandbox: E2B has its own filesystem API — it's not Node.js fs. If a tool directly imports node:fs, it simply won't run in E2B.
  3. Auditing: you can't uniformly intercept all file reads and writes (for logging, permission checks) because the call sites are scattered across every tool.

With the ctx.fs Seam, all three problems disappear:

  • In tests: inject an in-memory filesystem Provider
  • In E2B: inject the fs-e2b Provider
  • For auditing: log all calls uniformly in the Provider layer

Consumer code (conceptual example)

// Tool plugin: read a file — only depends on ctx.fs, doesn't care about the implementation
// Declare dependency on the 'fs' service
export const inject = ['fs']

export function apply(ctx: Context): void {
  ctx.tools.register(defineTool({
    name: 'read_file',
    description: 'Read the contents of a file',
    execute: async (args, exec) => {
      // ctx.fs is the filesystem Seam — local, sandbox, or E2B,
      // depending on which Provider was loaded
      // This code has no idea what's backing it
      const content = await ctx.fs.readFile(args.path)
      return content
    },
  }))
}
Enter fullscreen mode Exit fullscreen mode
// Want to switch to a sandboxed filesystem?
// Just swap the Provider plugin in the Bundle:

// bundle.ts (conceptual example)
export default [
  // Swapping this one line is all it takes — tool code is untouched:
  // '@deepseek-ai/dsh-fs-local'     → local filesystem (default)
  // '@deepseek-ai/dsh-fs-sandbox'   → restricted local filesystem
  // '@deepseek-ai/dsh-fs-e2b'       → E2B sandbox filesystem
  '@deepseek-ai/dsh-fs-sandbox',   // ← only this line changes

  // Consumer is unchanged
  '@deepseek-ai/dsh-tool-fs',
  // ... other plugins
]
Enter fullscreen mode Exit fullscreen mode

Deep Dive: ctx.sandbox (Process Sandbox Seam)

Shell execution is backed by the process sandbox. dsh defines three sandbox modes:

// Three sandbox modes for process execution (conceptual example)
type SandboxMode =
  | 'read-only'          // Read-only: child processes cannot write any files
  | 'workspace-write'    // Workspace-write: can only write inside the working directory
  | 'danger-full-access' // Unrestricted: no sandbox, direct spawn
Enter fullscreen mode Exit fullscreen mode

The platform implementation is selected automatically:

Platform Underlying Mechanism
Linux bwrap + Landlock
macOS Seatbelt
Windows ACL-restricted token

Switching backends with one config line (conceptual example):

# Local development: sandbox-local (native platform sandbox)
# CI/CD:             sandbox-e2b (E2B container, fully isolated — a sandbox crash won't touch the host)
Enter fullscreen mode Exit fullscreen mode

The same tool-shell code runs with sandbox-local locally and sandbox-e2b in CI. The switch lives entirely in the Bundle configuration layer.


Deep Dive: ctx.llm (LLM Adapter Seam)

LLMs are Seams too. This surprises people at first, but it makes perfect sense once you think it through.

Why abstract LLM calls?

  1. Provider switching: same Agent code, lightweight model for local testing, DeepSeek in production — swap one Provider plugin.
  2. Replay testing: llm-replay Provider — no real API calls, just plays back assistant/message events from a historical session in order. Testing Agent behavior requires no API key and has zero randomness.
  3. Multi-model routing: implement a Provider that routes different tasks to different models.
// llm-replay use case (conceptual example)
// In tests:
//   1. Run one real conversation and save the session log
//   2. In subsequent tests, swap the Provider to llm-replay —
//      the replay adapter plays back historical assistant/message events in order
//   3. Tests are fully deterministic, consume no API quota, and run 10x faster

// How to switch — one line in Bundle:
// '@deepseek-ai/dsh-llm-deepseek'  → real API
// '@deepseek-ai/dsh-llm-replay'    → replay mode (for testing)
Enter fullscreen mode Exit fullscreen mode

Hands-On: Implementing Your Own Provider

Here's a read-only filesystem Provider — all write operations throw immediately. Useful when you want to give an Agent read access to a codebase but not allow any modifications.

// Read-only filesystem Provider (conceptual example)
// Use case: let an Agent read a codebase, but never modify any files

export const name = 'my-readonly-fs'

export function apply(ctx: Context): void {
  // Register as the Provider for the ctx.fs service
  ctx.provide('fs', {
    // Read operations: work normally
    async readFile(path: string): Promise<string> {
      return await localReadFile(path)
    },

    // Write operations: intercepted, throw a clear error
    async writeFile(path: string, content: string): Promise<void> {
      throw new Error(`Read-only filesystem: cannot write to ${path}`)
    },

    // Directory listing: works normally
    async listFiles(dir: string): Promise<string[]> {
      return await localListFiles(dir)
    },

    // ... other methods (mkdir, rm, etc. all throw the read-only error)
  })
}
Enter fullscreen mode Exit fullscreen mode
// Using the custom Provider in a Bundle (conceptual example)
export default [
  // Replace the default fs-local with your read-only Provider
  './my-readonly-fs',

  // tool-fs is unchanged — it doesn't know the filesystem is read-only
  '@deepseek-ai/dsh-tool-fs',
]
Enter fullscreen mode Exit fullscreen mode

The power of this pattern: you've restricted the Agent's filesystem permissions without touching a single line of tool-fs.


The Real Value of Seam Design

A side-by-side comparison:

Scenario Without Seam With Seam
Switch LLM provider Update every API call site in every tool Swap one Provider plugin
Unit test file operations Mock node:fs, or create real files on disk Inject an in-memory filesystem Provider
Deploy to a sandbox environment Adapt large amounts of tool code to the sandbox API Swap fs-sandbox or fs-e2b
Add a new platform/backend Modify core code, risk introducing bugs Implement a new Provider; Consumers are unaffected
Enforce permissions Add if-guards in every individual tool Intercept uniformly in the Provider layer

Capability Seams are essentially a specialized form of dependency injection (DI). In the plugin system, injection is keyed by service name rather than constructor parameter — but the principle is identical.


Design Principles Summary

The core idea behind dsh Capability Seams:

Consumers only know the interface name. Providers only know the interface. Swapping happens at the configuration layer.

Design Decision Reason
All external capabilities go through Seams Testing, deployment, and multi-backend support all benefit
LLM is also a Seam Enables replay testing, provider switching, multi-model routing
Sandbox is also a Seam Local/CI/remote environments use different sandbox backends; Agent code is unchanged
Consumers only declare a service name Complete decoupling — no dependency on any concrete implementation
Bundle is the configuration layer Composing different Providers is a one-place change; business code stays clean

What's Next in the Series

The next article covers multi-Agent collaboration: when a single Agent can't handle a complex task, how does dsh support multiple Agents dividing and coordinating the work — delegation, subagents, parallel execution, and how all of this is represented at the Session layer.


Check out PrimeSkills — a curated marketplace of AI agents and skills validated in real-world, enterprise-grade workflows. Not demos — things that actually work in production.

Find more on my Homepage

Top comments (0)