Where Agent Behaviour Lives: dsh, Claude Code and pi

Every coding agent has to answer the same design question: when the model does something, where was that behaviour decided? In the system prompt, in framework code, or by the user? I cloned dsh, DeepSeek’s new harness, and read it alongside the pi source I already run as a fork. The two repos, plus what the Claude Code leak showed in March, give three different answers.

I expected dsh to be the framework-heavy one and it is, more than I guessed. What I did not expect is how little prompt it has.

dsh: behaviour in code, prompt down to a sentence

The only prompt text dsh’s framework hardcodes is one line, in packages/core/system-prompt/src/index.ts:

'You are an AI agent powered by DeepSeek Harness.'

Everything else is assembled. Around 28 plugins each register a prompt section with an ordering number (harness identity at -100, persona at 0, tool guidance at 100-199). The repo’s own test snapshots show the assembled result: 24 lines and 3.4 KB for the standard coding toolchain. The 443-line “code mode” variant is bigger only because it embeds a generated TypeScript SDK derived from tool schemas, still not hand-written prose.

The behaviour that other harnesses put in prompt prose lives in plugins. Read-before-edit is enforced by fs-observation-policy listening to filesystem events. A model repeating the same tool call gets injected reminders at 3, 5 and 8 repeats from repeat-tool-reminder. Plan mode’s README says outright that its prompt guidance is soft and that sandbox mode and approval policy enforce restrictions independently. Even the escalation path is schema rather than prose: bash accepts a sandbox_permissions argument plus a required justification, valid only as a one-shot retry of a call the sandbox just denied.

The framework underneath is a vendored fork of Cordis, 2,693 lines. The whole product is composed from a 451-line YAML patch file (packages/bundle/base/cordis.patch.yml) listing 78 plugin rows, and one of those rows is agent-loop. The agent loop itself, a 496-line agent.ts, is a swappable row in a config file. The architecture doc claims no privileged core, and the composition file backs that up.

The sandboxing is OS-level: bwrap or Landlock on Linux, Seatbelt on macOS, a restricted-token runner on Windows, failing closed when unavailable. Compaction is code too, and cache-aware: the summariser replays the conversation prefix verbatim so the provider’s KV cache stays warm, and tags the request with an x-deepseek-harness-compact header on DeepSeek’s own API.

Claude Code: behaviour in prose

The leak coverage describes the opposite arrangement. Per Layer5’s write-up, multi-agent orchestration is expressed in prompt strings rather than framework machinery, sitting on top of a 46,000-line query engine and around 40 tools. The system prompt is modular with cache-aware boundaries. I covered the rest when it happened.

The consequence I keep coming back to: Anthropic can change agent behaviour by editing a string and shipping a release, and users get no version-level signal that it happened. That is the cost of prose-side logic. The benefit is that the orchestration improves with the model for free, because instructions a better model follows better need no code change.

pi: behaviour handed to the user

pi’s system prompt builder (packages/coding-agent/src/core/system-prompt.ts in my clone) is 177 lines including the option types. Default tools are four: read, bash, edit, write. Plan mode, permission popups and built-in subagents are all absent, and the README describes the omissions as deliberate. The extension API’s type definitions run 1,567 lines, larger than the system prompt builder by a factor of nine, which tells you where the project expects behaviour to come from: your extensions, loaded from npm, with an RPC mode so the whole agent embeds as a subprocess in something bigger.

Compaction exists (src/core/compaction/, including branch summarisation), so the context machinery is provided. What pi refuses to provide is opinion about how your agent should act.

dsh runs on pi’s plumbing

One dependency line makes the comparison less abstract. dsh’s multi-provider adapter, packages/llm/llm-pi-ai, is built on @earendil-works/pi-ai@^0.82.1, which is pi’s LLM layer. DeepSeek’s harness talks to non-DeepSeek providers through code from the minimal-harness project. There are also compatibility plugins that execute a user’s existing Claude Code hooks.json (hooks-claude-code, 514 lines) and load AGENTS.md/CLAUDE.md chains into history. dsh is built to be migrated into.

Which bet holds up

The prompt-side bet says models keep improving at following instructions, so logic written as instructions gets better without engineering effort. The leak gives one data point against: a bug-fix comment cited 250,000 wasted API calls a day from autocompact failures, per Layer5. Prose logic still needs the machinery around it to be right, and prose is hard to test.

The code-side bet says behaviour should be auditable, versioned and enforced independently of whether the model listens. dsh’s plan mode is the clearest statement of this: the prompt asks nicely, the sandbox enforces regardless. The cost is 219 workspace packages and a framework to learn before you can change anything.

pi’s bet is that neither default should be baked in, and 88k stars suggest a market for that. My interest is declared here: I run a pi fork, and I picked it because behaviour I write in my own extensions is behaviour I can read back.

If I had to bet on one thing it is dsh’s enforcement layer being copied. Prompt guidance backed by OS-level sandboxing, with escalation as a typed tool argument rather than a plea in prose, is a pattern the other harnesses can adopt without adopting Cordis.