Repository navigation
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.
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
},
}),
)
}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 |
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.
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).
| 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 |
- 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.