mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
183 lines
15 KiB
Markdown
183 lines
15 KiB
Markdown
# DeepSeek Harness Architecture
|
|
|
|
The **DeepSeek Harness SDK** builds agent harnesses on Cordis. The principle is simple: **everything is a plugin**. The shipped loop is one plugin, not a privileged kernel.
|
|
|
|
## Overview
|
|
|
|
A harness is one [Cordis](cordis-primer.md) context. Packages add services (`ctx.llm`, `ctx.tools`, `ctx.sessions`), typed interception and notification events (`agent/request`, `tools/pre-execute`, `session/event`), and disposable registrations for prompts, tools, providers, adapters, and listeners.
|
|
|
|
`packages/core/` groups the default agent flow; surrounding capabilities are equally first-class Cordis plugins.
|
|
|
|
### Default Services
|
|
|
|
| ctx key | Package | Role |
|
|
|---|---|---|
|
|
| — | [`dsh-scope`](../packages/core/scope/README.md) | scoped-context registration primitive (library) |
|
|
| `ctx.sessions` | `dsh-session` | in-memory event-sourced sessions |
|
|
| `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections, tool schemas, and prompt variables |
|
|
| `ctx.tools` | `dsh-tools` | tool registry and [execution pipeline](tool-execution-pipeline.md) |
|
|
| `ctx.agents` | `dsh-agent` | live agent registry, public `Agent` handle, `agent/*` events |
|
|
| `ctx.agentLoop` | `dsh-agent-loop` | shipped `ReactLoopAgent` driver |
|
|
|
|
### Capability Services
|
|
|
|
| ctx key | Package family | Role |
|
|
|---|---|---|
|
|
| `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls |
|
|
| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | replay-aware request/surface pressure per model |
|
|
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution |
|
|
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | same-world process confinement (argv wrapping, per-call policy) |
|
|
| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | model-written program execution |
|
|
| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives and policy events |
|
|
| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill provider registry and progressive disclosure |
|
|
| `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries |
|
|
| `ctx.compact` | [`compact/`](../packages/compact/README.md) | session-log compaction |
|
|
| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers |
|
|
| `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | background task registry + generic `task_*` control tools |
|
|
| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | script-driven multi-agent orchestration |
|
|
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable storage for session logs |
|
|
| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | live-preferred logical-corpus and exact-event reads |
|
|
|
|
## Event
|
|
|
|
Events form the service extension API; see the exhaustive [events catalog](cordis-catalog/events.md) and [producer/consumer map](event-producer-consumer.md).
|
|
|
|
### Event Domains
|
|
|
|
- **Session events** are durable replay facts: boundaries, messages, tools, steering, compaction, and tool-owned state flow through `session/event`.
|
|
- **Agent events** carry the live `Agent` for status, diagnostics, prompt admission, request shaping, result validation, and continuation.
|
|
- **Capability events** belong to their action owner. `tools/*`, `llm/*`, `system-prompt/*`, `fs/*`, and `subagent/*` attach policy and adapters without importing the loop.
|
|
|
|
### Interception Semantics
|
|
|
|
Waterfall events behave like around-middleware: a listener delegates by calling `next()`; returning without it vetoes or takes over. Full rule: [Cordis waterfall semantics](cordis-primer.md#cordis-waterfall-semantics).
|
|
|
|
## Default Loop Lifecycle
|
|
|
|
The shipped loop drains work, assembles requests, streams answers, executes tools, applies continuation policy, and checkpoints state through plugin-visible calls and events.
|
|
|
|
A **session** is an agent's append-only log; a **turn** drains one queued batch; a **step** is one model request and its tool executions. Quoted names below are durable events, and unquoted event names are extension points ([sequence companion](agent-lifecycle.md)).
|
|
|
|
### Turn Flow
|
|
|
|
```text
|
|
prepare private session + agent.ctx -> await unpublished setup
|
|
-> enter session + agent -> session/created -> agent/created
|
|
-> enable driving -> agent/session-start(source) -> start driver
|
|
forever:
|
|
wait for queued messages
|
|
emit agent/status(running)
|
|
TURN:
|
|
'turn/start'
|
|
each queued message -> agent/prompt-submit
|
|
allowed prompt -> 'user/message' plus injected context
|
|
every prompt blocked -> 'turn/end'(rejected)
|
|
STEP loop:
|
|
drain steering
|
|
assemble system prompt and tool schemas
|
|
agent/session-prefix (first step)
|
|
agent/pre-step
|
|
snapshot the derived messages (the reconstruction boundary)
|
|
'step/start'
|
|
agent/request (config only) -> log request/header -> llm/stream (frozen)
|
|
on final adapter failure or terminal in-band error/aborted finish:
|
|
'step/end'
|
|
agent/request-error(original error, consecutive retry attempt, signal)
|
|
retry in the next numbered step or preserve the original error
|
|
otherwise:
|
|
'assistant/chunk'
|
|
agent/step-result
|
|
'assistant/message' (transformed content or empty success anchor after step-result rejection)
|
|
each tool call:
|
|
'tool/call'
|
|
tools/pre-execute -> monotonic guards -> tools/execute -> tools/post-execute -> tools/result
|
|
'tool/result'
|
|
append post-tool context and steering
|
|
agent/post-step
|
|
'step/end'
|
|
agent/turn-continuation
|
|
agent/turn-stop (terminal policy)
|
|
stop unless tools or continuation policy ask for another step
|
|
'turn/end'
|
|
checkpoint persistence and notify idle/running status
|
|
```
|
|
|
|
Each step renders one prompt assembly. Plugins contribute ordered sections, tool schemas, and `{{name}}` variables; missing values fail the turn. `dsh-system-prompt` owns harness identity and the default persona, which an agent-scoped persona may shadow. The loop supplies `model` and `cwd` ([prompt ownership](rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)).
|
|
|
|
Post-tool context follows all results, preserving call/result adjacency. Steering drains before `agent/post-step`, which observes durable output, results, context, and steering while the step signal remains open. Leftover steering becomes next-turn input. `agent/turn-stop` is terminal through close and flush: later steering is discarded, while ordinary queued prompts survive.
|
|
|
|
When loaded, `dsh-compact-basic` consumes that post-step checkpoint for `ctx.tokenMeter` pressure under the actual routed header. It also consumes canonical context overflow at `agent/request-error`, but authorizes retry only after a tool-balanced compaction advances `surface.replaceGeneration`. The same turn signal owns both summarization paths.
|
|
|
|
### Failure Boundaries
|
|
|
|
The turn is the containment boundary. `LlmService` preserves and privately tags errors from final adapter selection, dispatch, and iteration. Those errors and terminal in-band error/aborted finishes close the failed step before `agent/request-error`; retry reconstructs the next numbered step from the log, while decline or failed recovery preserves the provider error. Attempts count consecutive failures and reset after success.
|
|
|
|
Prompt, middleware, result, tool, post-step, and continuation failures remain ordinary `agent/error` failures. Cancellation and disposal beat recovery. Durable undispatched tool calls receive synthetic `ABORTED` results, preventing dangling replay. `cancel()` clears queues and aborts active work; disposal awaits quiescence before unregistering.
|
|
|
|
Every session event is turn-enclosed. Reload preserves a crashed tail and closes it with synthetic `interrupted`; post-close failures report only through `agent/error`. A turn has one `TurnEndReason` (`completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`), detailed in [session.md](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap).
|
|
|
|
### Agent Handles
|
|
|
|
`ctx.agents` owns live agents and returns `AgentHandle { agent, dispose() }`. Plugins drive `Agent` through `send()`, `steer()`, `inject()`, `cancel()`, and `whenIdle()`. The caller fiber and factory provider structurally co-own programmatic lifecycles; the consumer handle is the sole non-structural teardown capability, and all owners share one awaited disposer.
|
|
|
|
### Agent Scope
|
|
|
|
Each agent owns `agent.ctx`; its registrations shadow globals, receive only that agent's dispatches, and unwind on disposal, including awaited background-task cleanup. `CreateAgentOptions.setup(agentCtx)` composes it before publication. Typed resolvers derive carrier checks from merged events and `scopeTarget` ([semantic gates](rfc/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md), [agent scope](rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md), [subagent controls](rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)).
|
|
|
|
## State
|
|
|
|
### Session Log
|
|
|
|
The session log is the source of truth. `deriveMessages()` projects session events into the `Message[]` sent to the model; raw `assistant/chunk` events stay in the log for replay and UI fidelity. Replay, fork, resume, transcript rendering, telemetry, and persistence all derive from the same event stream.
|
|
|
|
**Model-visible ⟺ logged**: the log reconstructs every request — messages at `step/start` fronted by the header's session prefix, headers by folding `request/header` — and dev invariants assert this ([reconstructability RFC](rfc/implemented/architecture/2026-07-05-reconstructable-requests.md)).
|
|
|
|
Durability is a plugin concern. Persistence backends buffer synchronous `session/event` notifications and the loop awaits a turn-end checkpoint before moving on. The `SessionPersistence` seam stores `SessionEvent` directly, with metadata in `SessionHeader`; JSONL and SQLite share one contract suite.
|
|
|
|
### Model Content
|
|
|
|
Messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`). The union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New block types require coordinated adapter, UI, token-meter, and persistence changes; replay measurement types live in [token-meter.md](core-data-structures/token-meter.md).
|
|
|
|
Streaming is a raw chunk protocol (`block-start` through `finish`) with `BlockAssembler` as the shared chunk-to-block assembler. The loop logs raw chunks while assembling them for dispatch. `LlmAdapter` is the provider seam: subclass, implement `stream()`, and register with `ctx.llm.registerAdapter(models, adapter)`. StreamChunk conventions live in [llm-streaming.md](core-data-structures/llm-streaming.md).
|
|
|
|
## Extension And Composition
|
|
|
|
### Capability Pattern
|
|
|
|
A swappable capability usually splits into **interface / implementation / consumer**: the interface owns its `ctx` key and events, an implementation registers a backend, and a consumer exposes model behavior through tools or prompts. Bash is the reference; the [capability graph](capability-seams.md) shows every family.
|
|
|
|
Some seams bend the template deliberately. LLM keeps interface and consumer vocabulary together because adapters are the implementations. Filesystem adds policy gates around provider primitives. Web is one service with search and fetch provider registries, so provider swaps do not rename model tools. Skills and subagents use named provider registries; local skills scan project/user roots, and other providers can add embedded or remote catalogs without registry/tool changes. Subagents spawn fresh, fork from the parent's completed-turn prefix, or use ACP children ([subagent.md](core-data-structures/subagent.md)).
|
|
|
|
### Bundles And Apps
|
|
|
|
`dsh-agent-spine-demo` is the default composition bundle: one plugin loading the shared spine ([README](../packages/examples/agent-spine-demo/README.md)). App packages compose it with a front door and boot `bin`: `dsh-stdio-demo` for terminal REPL, and `dsh-acp-demo` for ACP over JSON-RPC stdio with no stdout logger ([ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` instead boots an external `cordis.yml`; the Python SDK injects the package default only when no explicit config channel is set and drives `dsh-jsonrpc` over line-delimited stdio JSON-RPC ([Python SDK](../python/README.md)). A deployment is a thin `cordis.yml` leaf: swappable backends, one app entry, and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
|
|
|
|
### Where New Behavior Goes
|
|
|
|
New behavior should attach to a documented extension point; changing the shipped loop requires updating this map.
|
|
|
|
| Goal | Mechanism |
|
|
|---|---|
|
|
| Add a model provider | register an adapter on `ctx.llm` |
|
|
| Add a model-facing capability | register a tool on `ctx.tools`; schemas flow into prompt assembly |
|
|
| Add command execution | implement and register a `ctx.bash` backend |
|
|
| Add a long-running/background capability | register the work on `ctx.tasks`; the generic `task_*` tools collect/stop it |
|
|
| Add filesystem access or policy | implement a `ctx.fs` provider or listen on `fs/*` policy events |
|
|
| Confine spawned processes | a `ctx.sandbox` backend; consumers wrap their argv before spawning |
|
|
| Intercept prompts, requests, model completion/failure, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` event; use serial `agent/turn-stop` for a monotonic terminal stop |
|
|
| Add a session-stable request prefix outside history | compose it on `agent/session-prefix`, once per loop instance; logged on the request header |
|
|
| Add UI or editor integration | drive `ctx.agents` and render from `session/event` |
|
|
| Add durable session state | add a `SessionEventMap` member and render/replay from the log |
|
|
| Fork a live session | use `ctx.sessions.fork(source, boundary?, childSessionId?)` |
|
|
| Scope a tool, prompt section, or listener to ONE agent | register it through that agent's `agent.ctx` (see Agent Scope) |
|
|
|
|
The [extension cookbook](cookbook/extension-cookbook.md) carries plugin skeletons and the feature-to-seam map; step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md).
|
|
|
|
## Quick Reference
|
|
- Domain terms in the [glossary](glossary.md)
|
|
- Type definitions in [core-data-structures/](core-data-structures/core.md)
|
|
- Exact event and service signatures in [events](cordis-catalog/events.md)
|
|
- [services](cordis-catalog/services.md) catalogs
|
|
- package contracts in the [package map](../packages/README.md)
|
|
- [RFCs](rfc/README.md)
|