(A1) terminate()/dispose()/service teardown keyed on direct-child settlement could leak a TERM-trapping descendant that outlived the leader (Codex reproduced it with a disowned trap-SIGTERM helper). kill()/terminate() now gate on tree liveness instead of outcome settlement; the SIGKILL escalation timer survives settle (unref'd, re-probing the tree); dispose's tier quiescence is whole-tree exit via a bounded waitForExit; the service's live set releases handles only when their tree is gone, and its teardown awaits tree exit. Three new suites pin the survivor scenarios end to end (terminate, dispose, service teardown). (A2) the escalation branch is now real tested behavior — its ignore is gone; the one remaining signalTree guard ignore states why it is unreachable through the handle verbs. (A3) docs contradictions fixed: the impl README's stale POSIX-only bullet now states the contained best-effort Windows tree story; the lsp-local README no longer claims taskkill failures stay visible (containment + the tree-liveness wait is the actual contract); the architecture tables (en+zh) list all three consumer families. (B1) OutputCollector keeps a byte-exact tail across uneven chunk boundaries (trim the head chunk instead of dropping it whole) — the LSP diagnostic-tail contract; pinned by a cross-chunk test. (B2) the subagent-acp coverage ignore is narrowed to exactly the never-settling success arm.
204 lines
16 KiB
Markdown
204 lines
16 KiB
Markdown
# DeepSeek Harness Architecture
|
|
|
|
English | [中文](architecture.zh.md)
|
|
|
|
**DeepSeek Harness SDK** uses Cordis: **everything is a plugin**, including the loop.
|
|
|
|
## Overview
|
|
|
|
Harnesses are [Cordis](cordis-primer.md) contexts with package-contributed services, typed events, and disposable registrations.
|
|
|
|
`packages/core/` groups the default agent flow; capabilities remain plugins.
|
|
|
|
### Default Services
|
|
|
|
| ctx key | Package | Role |
|
|
|---|---|---|
|
|
| — | [`dsh-scope`](../packages/core/scope/README.md) | scoped-context registration and shared layer storage (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 agents, delegated creation, `agent/*` events, and process-local initiator scope |
|
|
| `ctx.agentLoop` | `dsh-agent-loop` | concrete `Agent` 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) | singleton replay-aware request/surface pressure |
|
|
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution |
|
|
| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | managed child-process trees for the bash executors, the LSP host, and the ACP subagent backend |
|
|
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | owner-scoped persistent terminal sessions |
|
|
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | same-world process confinement (argv wrapping, per-call policy) |
|
|
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | shared sandbox policy home |
|
|
| `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.lsp` | [`lsp/`](../packages/lsp/README.md) | semantic navigation registry |
|
|
| `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`, `ctx.toolResultPrune` | [`compact/`](../packages/compact/README.md)/[`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune/README.md) | summary compaction; optional model-free result pruning |
|
|
| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers |
|
|
| `ctx.planMode` | [`plan/`](../packages/plan/README.md) | logged plan collaboration state |
|
|
| `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.goals` | [`goal/`](../packages/goal/README.md) | persisted same-session goals |
|
|
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable session-log storage |
|
|
| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | Live-preferred exact/filter/trace interface, SQLite FTS backend, and workspace-authorized model tools |
|
|
| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | log-backed fallbacks plus one optional asynchronous provider |
|
|
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | package-name-selected registry for package-owned runtime checks |
|
|
|
|
## Event
|
|
|
|
Events form the service extension API; see the [catalog](cordis-catalog/events.md) and [producer/consumer map](event-producer-consumer.md).
|
|
|
|
### Event Domains
|
|
|
|
- **Session events** are durable facts appended to the log and emitted through `session/event`.
|
|
- **Agent events** carry the live `Agent` for status, prompt admission, request shaping, validation, and continuation.
|
|
- **Capability events** let owning seams 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 loop runs through plugin services and events.
|
|
|
|
A **session** is append-only. Each ordinary **turn** claims one queued message; injection claims none. Successors await the preceding checkpoint but may share its `running` interval ([decision](../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md)). A **step** is one model request plus tools; quotes in the [sequence below](agent-lifecycle.md) mark durable events.
|
|
|
|
Creation without an id mints `<config-id>-session-<uuid>`; `sessionId` resumes or creates, while `resumeSessionId` requires history. Resume restores lineage and delegation depth before publication. Setup failures emit `agent-loop/config-start-failed`; teardown is silent.
|
|
|
|
### Turn Flow
|
|
|
|
```text
|
|
choose declarative identity and fresh/resume path
|
|
-> 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 a queued message
|
|
emit agent/status(running)
|
|
TURN:
|
|
'turn/start'
|
|
claimed message + contexts -> agent/prompt-submit
|
|
allowed prompt -> 'user/message' with prompt-prefix context baked in; append separate contexts
|
|
blocked prompt -> 'prompt/blocked' -> 'turn/end'(rejected)
|
|
STEP loop:
|
|
drain steering with the same prefix/separate context placement (no prompt-submit)
|
|
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 -> checkpoint -> llm/stream (frozen)
|
|
on final adapter-path or terminal in-band failure:
|
|
'step/end'
|
|
agent/request-error(original error, failure facts, immutable prior failures, 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)
|
|
schedule tool calls by ctx.tools.executionMode:
|
|
exclusive -> one-call barrier
|
|
parallel -> rolling pool, <= maxParallelToolCalls in flight; reclassify before start
|
|
each start -> 'tool/call' -> ordered tools/pre-execute -> checkpoint -> concurrent tools/execute
|
|
each model-order result -> ordered tools/post-execute -> 'tool/result'
|
|
append accepted tool-batch context after all recorded results, then steering
|
|
agent/post-step -> checkpoint complete response/results
|
|
'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
|
|
```
|
|
|
|
Steps assemble ordered prompt sections, tool schemas, and variables; unknown references fail turns. `dsh-system-prompt` owns identity and persona; the loop supplies `model` and `cwd` ([ownership](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)).
|
|
|
|
Async `inject()` and post-tool `additionalContexts` settle after results; steering drains before `agent/post-step`. Leftovers queue. Terminal `agent/turn-stop` remains authoritative through close/flush and discards later steering, not queued prompts.
|
|
|
|
Pruning precedes summaries; overflow retries require durable progress. Bounded retries compose on `agent/request-error`; cancellation wins ([compaction](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md), [retry](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md)).
|
|
|
|
### Failure Boundaries
|
|
|
|
Adapter failures close the step before `agent/request-error` with exact `Error`, `LlmFailure`, and history. Retries open steps; success clears history; exhaustion stores failure on `turn/end`. Failed chunks commit nothing.
|
|
|
|
Other failures use `agent/error`. Cancellation and disposal beat recovery; undispatched tools get synthetic `tool/call`/`ABORTED_BEFORE_DISPATCH` pairs. The signal retires before `turn/end`. Effective `cancel()` emits its cause, clears queues, and aborts; observers cannot veto, idle calls emit nothing, and durability records `aborted`. Disposal awaits quiescence ([decision](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)).
|
|
|
|
Session events are turn-enclosed; reload closes an interrupted tail with a synthetic `interrupted` turn end. Post-close failures use `agent/error`. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap).
|
|
|
|
### Agent Handles
|
|
|
|
`ctx.agents` returns `AgentHandle { agent, dispose() }`. Plugins use intent helpers `followup()`, `queue()`, `steer()`, and `inject()`; callers with exact routing facts use mandatory-field `send()` ([decision](../.agents/notes/implemented/architecture/2026-07-24-intent-named-agent-delivery.md)). `cancel()` and `whenIdle()` control lifecycle. Caller, provider, and handle co-own teardown.
|
|
|
|
### Agent Scope
|
|
|
|
Each agent owns a scoped `agent.ctx` over global tool, prompt, and command storage ([decision](../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md)); scoped listeners filter and contributions unwind with awaited cleanup. `CreateAgentOptions.setup(agentCtx)` composes before publication; typed resolvers derive carrier checks from `Events` and `scopeTarget` ([gates](../.agents/notes/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md)). `AgentLoop` runs inside `ctx.agents.withInitiator()`; private orchestration derives `agent.session`, while turn, step, signal, cwd, and authority stay explicit ([decision](../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)). See [agent scope](../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md) and [subagent composition](../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md).
|
|
|
|
## State
|
|
|
|
### Session Log
|
|
|
|
The session log is authoritative. `deriveMessages()` projects model history; raw `assistant/chunk` events preserve replay and UI fidelity. Fork, resume, transcripts, telemetry, and persistence share that stream.
|
|
|
|
**Model-visible ⟺ logged**: `step/start` messages plus the header's session prefix and folded `request/header` reconstruct every request; `dsh-agent-loop/invariant` asserts this through `ctx.invariants` ([decision](../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)).
|
|
|
|
Durability is a plugin concern; backends buffer synchronous `session/event` notifications. Checkpoints drain before adapter dispatch, recorded top-level tool calls before tool dispatch, complete response/result batches at `agent/post-step`, and final turn ends. `SessionPersistence` stores `SessionEvent` plus `SessionHeader` metadata; JSONL defaults to checksummed Zstandard, with SQLite under one contract ([decision](../.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md)).
|
|
|
|
`ctx.sessions.appendOutOfBand()` joins plugin-owned log-only events to an open turn or creates a balanced, flushed zero-step turn. `session/title` folds latest-wins with source seqs and provenance; its immediate fallback and sole optional async provider never delay the agent response. Forks inherit titles ([decision](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)).
|
|
|
|
### Model Content
|
|
|
|
Messages use typed blocks from merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in [token-meter.md](core-data-structures/token-meter.md).
|
|
|
|
Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report facts and `agent/request-error` owns recovery. The loop logs chunks and successful provenance/replay state. Remote adapters use per-read idle watchdogs. Replay state crosses routes only when they share an adapter instance ([contract](core-data-structures/llm-streaming.md)).
|
|
|
|
## Extension And Composition
|
|
|
|
### Capability Pattern
|
|
|
|
A swappable capability usually splits into **interface / implementation / consumer**: service/events, a backend, and model-facing tools/prompts. Bash is the reference; the [capability graph](capability-seams.md) maps each family.
|
|
|
|
Exceptions combine layers: LLM interface/consumer; filesystem policy; web registries; named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, or use ACP children ([subagent.md](core-data-structures/subagent.md)).
|
|
|
|
`dsh-workspace-context` composes baselines on `agent/session-prefix` and appends `ctx.fs`-discovered nested changes on `tools/post-execute`; its [decision](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) records isolation. `dsh-paths` owns shared paths.
|
|
|
|
### Bundles And Apps
|
|
|
|
`dsh-agent-spine-demo` bundles a spine and optional goals. App packages own TUI, CLI, ACP automation, and JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [acp/](../packages/acp/README.md), [ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies a default only without explicit config ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
|
|
|
|
### Where New Behavior Goes
|
|
|
|
New behavior attaches to a documented extension point; a loop change updates this map.
|
|
|
|
| Goal | Mechanism |
|
|
|---|---|
|
|
| Add a model provider | register an adapter on `ctx.llm` |
|
|
| Add a model-facing capability | register on `ctx.tools`; schemas enter prompt assembly |
|
|
| Add shell execution | implement and register a `ctx.bash` backend (the local one spawns through `ctx.subprocess`) |
|
|
| Add persistent terminal execution | register a `ctx.pty` backend and `dsh-tool-pty` |
|
|
| Add a human command | register on `ctx.commands`; adapters discover and dispatch it without a model turn |
|
|
| Add background work | register on `ctx.tasks`; generic `task_*` tools collect or 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 a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/turn-stop` is the serial terminal stop |
|
|
| Add a session-stable prefix outside history | compose `agent/session-prefix`; the request header logs it |
|
|
| Add UI or editor integration | drive `ctx.agents` and render from `session/event`; terminal-only overlays use `ctx.tui` |
|
|
| Add durable session state | add a `SessionEventMap` member and render/replay from the log |
|
|
| Add asynchronous session-title generation | register the sole provider on `ctx.sessionTitle` |
|
|
| Manage a same-session objective | use `ctx.goals`; continue through `Agent` and `agent/*` |
|
|
| Fork a live session | use `ctx.sessions.fork(source, boundary?, childSessionId?)` |
|
|
| Scope a registration to one agent | use 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 signatures in the [event](cordis-catalog/events.md) and [service](cordis-catalog/services.md) catalogs
|
|
- package contracts in the [package map](../packages/README.md)
|
|
- [Agent Notes](../.agents/notes/README.md)
|