Skip to content

Plugin Development

Dipanshu Kamboj edited this page Aug 31, 2026 · 1 revision

🧩 Plugin Development

Everything is a plugin. The harness has no closed core — every capability you saw on Tool Arsenal is a package mounted on a capability seam, and yours can be too.

The shape of a plugin

A plugin is a Cordis package: package.json + src/index.ts with a service that declares its seams and config.

// my-tool/src/index.ts
import { Context } from '@pentest-harness/cordis'
import z from '@pentest-harness/schemastery'
import { defineTool } from '@pentest-harness/dsh-tools'

export const name = 'my-tool'

export interface Config { /* your fields */ }
export const Config = z.object({ /* … */ })

export function apply(ctx: Context, config: Config): void {
  ctx.tools.register(
    defineTool({
      name: 'my_tool',
      description: 'What it does and when to use it.',
      input: { /* zod-ish schema */ },
      async execute(input, signal) {
        // your logic — return the ToolResult
      },
    }),
  )
}

The seams your plugin can touch

Each seam is a typed service on the Cordis context — plugins never reach around them:

Seam What it fronts
ctx.llm Model adapters and request streaming
ctx.tools The model-facing tool registry
ctx.shell Command execution (bash/pwsh backends)
ctx.fs Filesystem access with audit + policy
ctx.web Fetch/search backends
ctx.sessions / persistence Session state, replay, checkpoints
ctx.subagents Delegation to child agents
ctx.credentials The owner-only secret store
ctx.settings User configuration surface
ctx.sandbox Confinement modes and escalation

Adding a capability to a profile

You don't fork the app to add something — you add a patch layer:

# $DSH_HOME/cordis.patch.yml
plugins:
  # relative, npm, or workspace package spec
  - './my-tool'

Layers compose in order: bundle patches → profile patch → home patch → --patch overlays. Later layers shadow earlier ones, so a user patch always wins.

Hot reload at runtime

cordis_define / cordis_run / cordis_inspect_* / cordis_stop let the agent itself define, inspect, and drive new plugins mid-session — the harness can extend itself without a restart. HMR is wired through the vendored Cordis loader, so a file change hot-swaps the running service and disposes the old instance cleanly (observers and in-flight work get settled first).

Where to look in the repo

I want to build… Study
A tool packages/shell/tool-bash (canonical small tool)
An executor/backend packages/shell/bash-local (seam consumer + settings section)
A provider packages/llm/llm-deepseek (LLM seam adapter)
A settings surface packages/client/ui-settings-models
A bundle/profile packages/bundle/base, apps/cli
The full walk-through docs/cordis-tutorial/ in the repo

Design rules worth copying

  • Fail loud, not silent — a missing service throws a coded, explanatory error.
  • Contain failures per observer — one bad listener can't veto a registry mutation.
  • Own your cleanup — timers, listeners, in-flight promises drain on dispose; the runtime awaits your teardown.
  • Redact secrets at the boundary — descriptors out, literals never.

Clone this wiki locally