# DeepSeek Harness Architecture English | [中文](architecture.zh.md) Read this before changing anything under `packages/`. It assumes you know Cordis; if you do not, start with the [primer](cordis-primer.md) or the [tutorial](cordis-tutorial/index.md). We recommend using an agent to explore the codebase and understand its architecture. ## Cordis [Cordis](cordis-primer.md) is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration. There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads. ## Profiles and bundles A running `dsh` is a plugin tree composed at boot from ordered layers. A **profile** is a named composition stored in the Harness home. It lists the bundles it stacks, holds any out-of-tree plugins it installs, and keeps the user's own `cordis.patch.yml`. `web` and `headless` ship as templates. A **bundle** is a distribution format for Cordis config rows and the code they mount, so whatever it inserts stays patchable by the layers above it. Each declares itself in its own `package.json` under a `dsh` field: `dsh.profile` lists a profile's bundles, and `dsh.bundle` points at a bundle's patch file. [`dsh-base`](../packages/bundle/base/README.md) is the first layer of every profile: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry. [`dsh-web-app`](../packages/bundle/web-app/README.md) adds the browser application; [`dsh-headless`](../packages/bundle/headless/README.md) adds a one-shot runner with no server at all. Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's `cordis.patch.yml`, then the home-level one, then any `--patch` overlay. A patch targets a row by id and replaces its whole config, or inserts new rows. To see the tree your machine actually boots: ```sh dsh --profile web --dump-config ``` Any row it prints can be replaced by a patch of your own. Composition mechanics are in [app-boot](../packages/boot/app-boot/README.md#profiles); config fields are in the generated [config catalog](config-catalog.md). ## Core packages Here are some core packages that contribute to the Cordis tree. | Package | Owns | `ctx` key | |---|---|---| | [`core/session`](subsystems/session.md) | The append-only `SessionEvent` log and in-memory store | `ctx.sessions` | | [`core/system-prompt`](subsystems/system-prompt.md) | Prompt-section and tool-schema assembly | `ctx.systemPrompt` | | [`core/tools`](subsystems/tools.md) | The scoped tool registry and guarded execution pipeline | `ctx.tools` | | [`core/agent`](subsystems/core.md) | The `Agent` interface, live registry, and `agent/*` events | `ctx.agents` | | [`core/agent-loop`](subsystems/core.md) | The default driver implementing that interface | `ctx.agentLoop` | | [`core/scope`](subsystems/scope.md) | The per-agent scoped-registration primitive | library, no key | | [`llm/llm`](subsystems/llm-streaming.md) | Message and stream vocabulary plus the adapter seam | `ctx.llm` | ## Events Events are the extension points, and picking the right domain is the first decision in most changes. - **Session events** are durable facts appended to the log and broadcast through `session/event`. Use one when the fact must survive a reload. - **Agent events** (`agent/*`) carry a live `Agent`: inbox, step, status, request, validation, continuation. Use one to observe or intercept work in flight. - **Capability events** attach policy and adapters to a seam (`fs/*`, `tools/*`, `telemetry/*`) without importing the loop. The [event map](event-producer-consumer.md) lists every event's producers and consumers. ## Turn flow A **step** is one model request plus the tools it calls. A **turn** is zero or more steps: it opens before its first input is claimed and closes once nothing is owed. ```text turn/start claim next-step input plus one queued message assemble prompt sections + tool schemas -> agent/pre-step reject | enter(messages) reject, or a first enter rewritten empty -> close the turn with no step step/start append entered messages as user/message derive model history from the log agent/request -> llm/stream -> assistant/chunk* -> assistant/message tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result* step/end tools owe another request, or next-step input arrived -> claim -> next step -> agent/turn-stopping turn/end ``` `turn/*`, `step/*`, `user/message`, `assistant/*`, and `tool/*` are durable session events; the rest are live extension points across three domains. `agent/pre-step`, `agent/request`, `llm/stream`, and the three `tools/*` events are waterfalls, whose listeners must call `next()` to delegate; `agent/turn-stopping` is serial and has no `next()`. Input reaches the driver through one inbox. Some messages wake it immediately; injected context waits in the inbox until another message does. `agent/pre-step` decides what the model sees. Listeners may rewrite the claimed messages or reject them outright; a rejected or empty first claim still closes a durable turn that spent no step, so the log records the attempt. Each step reads the prompt sections and tool schemas that plugins registered. Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-execution-pipeline.md), and [cancellation and error recovery](subsystems/core.md#the-agent-handle). ## Session log The session log is the source of the context the model sees. `deriveMessages()` projects model history from it, and raw `assistant/chunk` events preserve replay and UI fidelity. Fork, resume, transcripts, telemetry, and persistence all derive from this stream. **Model-visible means logged.** Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. This is why a new model-visible input requires a new session event: extend `SessionEventMap` and render from the log. ## Capability seams A **seam** is a swappable capability with three roles: a **Service Definition** declaring the interface, a **Service Provider** implementing it, and a **Consumer** using it, commonly a model-facing tool. A package may combine roles, but one role alone is not a seam; adding a capability means designing all three ([capability graph](capability-seams.md)). Seams are why one provider swap changes the whole product. Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them, with no provider forks. [Subagent providers](subsystems/subagent.md) vary just as widely behind one interface, from a fresh child agent to a delegated turn in another product. ## Where new behavior goes New behavior attaches to a documented extension point. Changing the loop itself updates this map. | Goal | Mechanism | |---|---| | Add a model provider | register its adapter on `ctx.llm` | | Add a model-facing capability | register on `ctx.tools`; its schema joins prompt assembly | | Give one session a different capability set | compose an agent preset; a service row there needs an `isolate` realm | | Add shell execution | register a `ctx.shell` backend; the local one spawns through `ctx.subprocess` | | Add persistent terminal execution | register a `ctx.terminals` backend plus `dsh-tool-terminal` | | Add a human command | register on `ctx.commands`; it dispatches without a model turn | | Add background work | register on `ctx.jobs`; `job_*` tools collect or stop it | | Add filesystem access or policy | register a `ctx.fs` provider or listen to `fs/*` events | | Confine spawned processes | use a `ctx.sandbox` backend; consumers wrap argv before spawning | | Intercept a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/turn-stopping` stops a turn | | Add model-facing context | call `agent.inject()`; it lands in the next admitted request | | Add UI or editor integration | drive `ctx.agents` and render from `session/event` | | Add a Web Client Chat node | register a `ConversationNodeDefinition` + keyed renderer | | Add durable session state | extend `SessionEventMap`; render and replay from the log | | Generate session titles | register the sole `ctx.sessionTitle` provider | | Manage a same-session objective | use `ctx.goals`; continue through `agent/*` | | Fork a live session | `ctx.sessions.fork(source, boundary?, childSessionId?)` | | Scope a registration to one agent | use that agent's `agent.ctx` | The [extension cookbook](cookbook/extension-cookbook.md) maps features to capabilities and indexes the step-by-step guides for [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [Chat nodes](cookbook/adding-a-conversation-node.md).