Files
deepseek-harness/packages/scaffold/server
Yichen Jiang 1cfbafab6a fix(preset): keep the token meter host-plane and name unjoined agents
Moving the agent plane behind presets left two readers on the wrong side of
the host/agent line.

`dsh-token-meter` was disabled on the host and mounted inside each preset's
`compaction` realm, but its three projection units register into the
process-wide `sessionProjections` table. A unit registered from one preset
answers for every session, so whether a `minimal` session showed a context
meter depended on whether some other session had mounted `standard` since
boot, and a process that only ever ran `minimal` showed none. The meter takes
no configuration, keys every fold by Session, and registers no tool or prompt
section, so it returns to the host composition and leaves the presets'
`isolate` map; the realm and `compact-basic` stay, because what a preset
chooses is whether its agent compacts, not whether its tokens are counted.

Nothing named an agent that joined no preset. The join is a scope-parent link,
and without it the tools, prompt-section, and skill views resolve the empty
global layer: the agent publishes, the turn runs, and the model receives
nothing. `AgentPresets` now logs one warning per such agent while a roster is
configured, and the invariant companion fails outright — at
`system-prompt/assemble` rather than at publication, because an unjoined agent
is legal until it addresses a model and `recompose` binds exactly such an
agent. The warning stays advisory: a synchronous `agent/created` throw vetoes
publication, and the ACP bridge, SDK server, and headless bundle all create an
unjoined agent today.

Three limits are recorded rather than fixed: projection key presence is not a
per-session capability signal, a superseded standing generation is never
reclaimed, and a `cordis_mount` temporary plugin belongs to the composition
rather than the session that mounted it.

Fixes #2203
2026-08-10 22:36:06 +08:00
..

@deepseek-ai/dsh-jsonrpc

English | 中文

The jsonrpc plugin serves newline-delimited JSON-RPC over stdio so out-of-process SDK clients can drive harness agents. HarnessSdkServer owns the protocol methods and notifications; the transport and the named wire types live in dsh-sdk-protocol, shared with the client SDKs; jsonrpc-demo supplies the surrounding cordis.yml application.

Wiring

inject: ['agents']. The server gets or creates one agent per sessionId. It forwards subagent completions only when the service-snapshotted lifecycle local flag is true; provider names, child ids, and durable lineage never establish locality. A registered adapter wins, an unowned deepseek-official route mounts dsh-llm-deepseek, and any other unowned provider fails initialization. Other capabilities come from the surrounding cordis.yml.

Config

maxTokensAsSuccess defaults to false and affects only the deployment-mapped status on subagent.finished; root-session prompts have no prompt-level status. JsonRpcConfig.input, output, and exit are runtime-only transport hooks; production uses process stdio and process.exit.

stdout is the protocol

Stdout carries only JSON-RPC frames. The deployment must not compose a stdout logger; diagnostics belong on stderr.

Shutdown and exit semantics

The plugin answers shutdown, flushes the response, disposes the root context so SDK-owned agents, subscriptions, and persistence reach quiescence, then exits with code 0. EOF and signal exits belong to the app bin, which also disposes the root context. Unloading only this plugin stops serving without exiting the process.

Wire notes

initialize.serverInfo.name is the wire-stable deepseek-harness-sdk-runtime. An optional positive initialize.maxTokens becomes the request output cap of each SDK-created agent and its in-process descendants; invalid values reject initialization, while omission sends no SDK cap and allows the selected adapter or provider route default to apply. session/prompt queues one identified user message and immediately returns { messageId }. The server streams every durable fact as session.event and every whole-agent lifecycle transition as session.status; it does not assign an assistant message or turn/end to that prompt. Independent requests may enqueue more work on the same session. Persistence roots and persona come from cordis.yml.

Model Experience

SDK user message

What the model sees

For each accepted session/prompt, the conversation model receives the caller-supplied contentBlocks verbatim as one user message in that SDK session. This package adds no system-prompt prose or tool schema; those come from the plugins in the surrounding cordis.yml.

Token effect

Data-dependent user-message tokens enter retained session history and are resent on later turns until another package compacts them. The JSON-RPC frames, session notifications, and server bookkeeping add zero model-context tokens.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

  • The wire has no per-session close or prompt-cancel method — SDK-created agents remain live until process shutdown.
  • There is no per-prompt resultMessageId identifies inbox admission only; clients that own an automation interval must define and observe that interval themselves.
  • stdout purity is deployment-enforced — a surrounding config can still load a stdout logger and corrupt the JSON-RPC channel; this plugin does not inspect or veto sibling loggers.
  • Automatic adapter mounting is DeepSeek-specificinitialize can reuse any pre-registered model adapter, but its only fallback mounts dsh-llm-deepseek.