@shlouai/dsh-debate
Debate orchestrator for DeepSeek Harness: a dsh profile bundle shipping the debate_open/debate_round/debate_verdict tools and their Web card 适合需要多代理结构化辩论并得出裁决的任务,可配置轮次与工具。
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:shlouai/dsh-debateConfiguration
The patch layer sets provider: spawn. Every other field takes its schema default and can be overridden from the profile's own cordis.patch.yml, which applies after this bundle's layer:
- id: debate
config:
provider: spawn
maxRounds: 8 # largest round count a debate may open with
maxSpeechLength: 2000 # characters per speech
debaterTools: # global tool names a debater may use; [] denies all
- read # the workspace: read a file,
- glob # find files by name,
- grep # search their contents
- web_search # the open web: search it,
- web_fetch # and fetch one page in full
searchTools: # of those, the ones a credential gates; [] gates none
- web_search
searchCredential: DEEPSEEK_API_KEY # the credential that enables them
| Field | Default | Meaning |
|---|---|---|
provider | (required) | Subagent backend to run debaters on. Must support persona and toolFilter; spawn, mounted by dsh-base, does. |
maxRounds | 8 | Ceiling on the round count a debate may open with. Each round costs two children. |
maxSpeechLength | 2000 | Characters per speech. Every later speech reads every earlier one, so an unbounded speech grows the next prompt quadratically. |
debaterTools | read-only set above | Allow list over the tools the judging agent itself can see. |
searchTools | [web_search] | Which of those stay withheld until the credential resolves. |
searchCredential | DEEPSEEK_API_KEY | Credential reference name — never a literal secret. |
debaterPersona | competitive-debater persona | Shadows the deployment's persona for both debaters; states the shared craft only. |
provider: spawn is what gives each debater a fresh child that never sees the judging conversation — the mechanism that makes a debater argue its assigned side rather than agree with the judge.
What a debater may do
debaterTools is applied to the child as a tools.restrict(), so a name absent from it is missing from the debater's prompt and refuses to run. The default is deliberately read-only: a debater researches its own case, and a side that could write files or run commands would be doing work on the judge's behalf while pretending to argue. debaterTools: [] restores argument alone.
Two consequences worth knowing:
- A name this deployment does not mount is skipped, not fatal.
tools.restrict()rejects an unknown tool name outright, which would fail every round rather than one call, so the list is narrowed to what the judge can actually see and the dropped names are logged once.dsh-basealone carries every default butweb_fetch(it configurestool-webwithfetch: false); thestandardagent preset, which awebprofile uses, adds it. - Each speech is one child's single turn, so research spends model calls inside that turn: a round costs two debaters, and each may search or read several times before it speaks.
Web search is conditional on a credential
web_search is mounted whether or not its backend can authenticate a query, so being available and being usable are different things for it. A debater is therefore given it only when searchCredential resolves to a value; otherwise that name is withheld and the debate runs on the tools that need no credential — read, glob, grep, and web_fetch, which retrieves a URL anonymously. A withheld tool is absent from the debater's prompt as well as its dispatch table, so a debater is never told about a search tool it cannot use, and one notice is logged per process rather than per speech.
Set the key in any layer of the credential plane, which is read in this order:
| Layer | Where | Takes effect |
|---|---|---|
| Inherited process environment | DEEPSEEK_API_KEY=… dsh --profile web | that launch |
| Provider-managed store | ~/.dsh/.credentials.yaml, under refs: (or the Web UI's Models page) | immediately — the store publishes external edits, so a key stored mid-debate reaches the next round |
Invoking directory's .env | DEEPSEEK_API_KEY=… | after restarting the profile |
Harness home's .env | ~/.dsh/.env | after restarting the profile |
The two .env layers are a boot snapshot, which is why they need the restart. Only the presence of the value is ever read here: the check asks the credential plane whether resolution would return something, so the secret does not enter this plugin, its logs, or the debate.
searchCredential is a credential-reference name, not a literal secret — never put a key in cordis.patch.yml. DEEPSEEK_API_KEY is the variable dsh-base wires its search backend to; a deployment that points web's searchProvider at Exa or Perplexity sets searchCredential: EXA_API_KEY or PERPLEXITY_API_KEY to match, and note that those two backends read only the environment layers and are absent from a stock profile's dependency closure. searchTools: [] removes the gate entirely, which is the right setting for a search backend that needs no key at all.