DeepSeek Harness Lark bridge: empty replies after format v2

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek HarnessLark botsession format v2assistant/messagebridge plugin
DeepSeek Harness format v2 dropped assistant/chunk, so a Lark bridge returns empty replies. Read assistant/message.content, roll back, or use session.event.

Lark replies suddenly go empty while the session log clearly contains text: the bridge's projection contract is out of date, and the model and network are not involved. One commit raised SESSION_FORMAT_VERSION from 1 to 2 and deleted assistant/chunk, leaving assistant/message.message.content as the only persisted carrier of the answer. A bridge that still renders the old events and takes only usage from the message finishes the turn normally while the user receives nothing (Discussion #10074).

DeepSeek Harness session format v2: symptoms and where the body lives

The two symptoms are two stages of the same contract break, and the fact that a rollback restores replies proves the break is on the write side. The thread pins the boundary to commit f99b06eaed (feat(session)!: embed assistant streams in format v2):

  1. 0.1.5 generation: after upgrading from 0.1.2-rc.1 to 0.1.5-rc.1, Lark replies are lost silently, 100 percent of the time, while the turn is recorded as completed and logs and tool channels look normal. Expected check: assistant/chunk is gone from the session events.
  2. 0.2.0 generation: on 0.2.0-rc.2 the bridge handles inbound messages and tool cards, but the body is always (No content returned). Expected check: assistant/message contains text and the bridge projects only usage.
  3. Version boundary: the commit is absent from dsh-v0.1.2-rc.1 and present from dsh-v0.1.3-alpha.1 onward; 0.1.5-rc.1, 0.1.7-rc.2, and 0.2.0-rc.2 all include it. Expected conclusion: any upgrade across that line is affected.
  4. Rollback check: dropping back to 0.1.2-rc.1 restores replies immediately. Expected conclusion: the old version still writes format 1, so the old rendering path has data to read.

Do not mix in the other problem: unreadable provider/model values in the same version family come from config resolution in the composed profile tree and are unrelated to the empty body.

After v2, core only guarantees settlement; turning that settlement into a card body is the consumer's job. The event shapes are:

  1. assistant/message carries { turn, step, message, stream, usage?, interrupted? }, where message.content holds the assembled content blocks and stream is the timed stream that produced them.
  2. assistant/attempt carries { turn, step, stream } for attempts that failed, retried, were cancelled, or hit a stream error and committed no visible message.
  3. Per-token assistant/chunk has been removed. agent/assistant-stream is an in-process event domain whose only remote consumer is the Web session-follow adapter, not a delivery surface for external channels.
  4. The empty-reply diagnosis is therefore direct: assistant/message exists, message.content has text blocks, and the bridge output is empty, which means the projection dropped it.

According to the thread, the cheap official cross-check is to diff docs/persistence-catalog.md between two tags. It pins a SHA-256 for each persisted type, so any change in event shape changes the hash without requiring a source read.

DeepSeek Harness: four checks before you upgrade

Judge by the format version and the events you subscribe to, not by the bridge's own version number. Four steps run on a single machine:

  1. Check whether the target host tag contains commit f99b06eaed. Expected: every tag from 0.1.3-alpha.1 onward contains it; 0.1.2-rc.1 does not.
  2. Search the bridge source for assistant/chunk. Expected: a hit means it still renders the old contract and will likely produce empty replies after the upgrade.
  3. Open an existing session's event log, find assistant/message, and inspect message.content. Expected: after v2 it should hold complete text blocks; if it is empty, the problem is on the model side.
  4. Compare the bridge's COMPATIBILITY.md with the SDK version it actually depends on. Expected: if the declaration covers only the old generation, apply Path 1 before upgrading the host.

Step 2 matters most: while the bridge still waits for chunks, the body will never appear no matter how complete the log is.

DSH plugin path 1: read assistant/message.message.content

This is the only way to keep the bridge moving forward with new releases, and the change is concentrated in one projection function. Steps:

  1. Find the session event branch in the bridge and confirm it subscribes to assistant/chunk. Expected: you see the old logic that accumulates text per chunk.
  2. Add handling for assistant/message: take event.message.content, filter text blocks, and concatenate them in order. Expected: a complete body is available even in non-streaming runs.
  3. Keep the usage projection but stop treating it as a source of body text. Expected: billing statistics stay the same and the body no longer depends on usage.
  4. Ignore unknown event types instead of failing on them. Expected: a later format upgrade does not crash the bridge because of an added event type.
  5. Verify with one plain message. Expected: Lark receives the complete body and (No content returned) disappears.

If the bridge still wants a typing effect, replay from assistant/message.stream rather than waiting for the deleted chunk events.

DeepSeek Harness path 2: roll back to 0.1.2-rc.1

This is for emergencies only, and it pins the whole host to the old generation. Order of operations:

  1. Confirm the SDK version the bridge depends on and which generation its compatibility statement covers. Expected: you know which host version satisfies both the bridge and your current plugins.
  2. Downgrade the host to 0.1.2-rc.1 and restart. Expected: Lark replies return and assistant/chunk reappears in the log.
  3. Record why and when you rolled back. Expected: the team treats this as a temporary path, not a long-term design.
  4. Assess the side effects: 0.2.x capabilities and any surrounding plugins that require the new generation become unavailable. Expected: the business cost of the rollback is explicit.

A rollback does not fix the bridge; it swaps the contract back. As soon as you want 0.2.x, Path 1 or Path 3 is still required.

While rolled back, do not upgrade the bridge or the SDK at the same time. Moving two variables together makes the next diagnosis harder. Keep the host version, SDK version, and bridge version on one line so a future failure shows immediately which component crossed the generation boundary.

DeepSeek Harness path 3: consume the session.event stream

The most stable posture for a channel plugin is to stop parsing session logs and consume only the SDK's stable event surface; check existing bridge plugins on DSH Plugin Hub for their compatibility claims. The minimal surface in the thread is small:

  1. Notifications: session.event ({ sessionId, event }), session.status (idle or running), subagent.started, and subagent.finished. Expected: inbound messages, status indicators, and subagent progress are all covered.
  2. Requests: initialize, session/prompt, session/resume, session/wait, session/working-directory/get, session/working-directory/set, and shutdown. Expected: a complete send-and-receive loop for a chat channel.
  3. Project assistant/message from session.event into the card body and drive typing state from session.status. Expected: no dependency on any in-process event.
  4. Check compatibility by format version rather than client version. Expected: before upgrading, look at whether SESSION_FORMAT_VERSION changed instead of waiting for users to report empty replies.
  5. Run a real send and receive with a temporary DSH_HOME. Expected: the body works on the new generation and nothing errors on the old one.

According to the thread, ACP is for automation and is not a channel API, so do not build a bridge on it.

DeepSeek Harness: acceptance checklist after migration

Verify four things: the body, failed attempts, unknown events, and tool blocks. Check in order:

  1. Send a plain text message. Expected: a complete body with no (No content returned).
  2. Trigger a model failure or retry. Expected: the bridge sees assistant/attempt, does not mistake it for body text, and does not send twice.
  3. Inject an unknown event type. Expected: the bridge ignores it and keeps working without a fatal error.
  4. Replay a history that contains tool calls. Expected: tool cards and body text are rendered separately and the body is not overwritten by tool blocks.
  5. Run the same suite against an older host. Expected: the bridge reports a version mismatch instead of silently returning an empty body.

According to the thread, this gap must be fixed in the third-party bridge's projection; there is no host-side switch to roll back. Writing "render by settlement" into the bridge's contract tests is more reliable than manually sending one message after every upgrade.

If the bridge already has a test directory, turn the four items above into cases and run them before upgrading the host. That costs far less than waiting for users to report empty replies in production. Treat event payloads as external data in those tests and do not depend on in-process state.

These four cases also work as a gate before any SDK upgrade.

Source: Discussion #10074, deepseek-ai/deepseek-harness. For other causes of a silent Lark bot, see DSH bot not replying.

FAQ

After upgrading DeepSeek Harness, every Lark bot reply is empty while the turn shows completed. Did the model produce nothing?

DeepSeek Harness produced output; the Lark bridge reads the wrong event. Session format v2 removed assistant/chunk, and the only persisted carrier of the answer is assistant/message.message.content. A bridge that still renders from chunks and only projects usage from the message completes the turn normally while printing nothing.

Why does rolling the Lark bridge back to DeepSeek Harness 0.1.2-rc.1 restore replies?

Because DeepSeek Harness 0.1.2-rc.1 still writes format 1 and its log still contains assistant/chunk, so the old rendering path finds the text. The commit that changed the contract exists from dsh-v0.1.3-alpha.1 onward, so rolling back restores the old contract without fixing the bridge.

Should a third-party bridge read DeepSeek Harness assistant/chunk or assistant/message?

DeepSeek Harness bridges should read assistant/message.message.content and treat it strictly as a settled message body. According to the minimal stable surface in the thread, a channel plugin only needs to subscribe to session.event. Do not parse session logs yourself, and do not depend on the in-process assistant-stream event domain.

The Lark bridge cannot read DeepSeek Harness provider and model after upgrading. Is that the same cause?

DeepSeek Harness empty bodies and unreadable provider/model values are not the same cause. Empty bodies come from a change in the session format projection contract, while unreadable provider/model values belong to config resolution in the composed profile tree. Use dump-config to see whether that section is missing or overridden, and troubleshoot the two separately rather than patching both at once.

Related Terms

SESSION_FORMAT_VERSION
SESSION_FORMAT_VERSION is the version number of the DSH session log format. It determines event types and field shapes and is declared centrally in core/session types; the currently published format version is 4.— https://github.com/deepseek-ai/deepseek-harness/discussions/10074
assistant/message
Since format v2, assistant/message is the single persistent settlement event for each model attempt. It carries turn, step, the assembled message, the raw stream, and optional usage; the body lives in message.content.— https://github.com/deepseek-ai/deepseek-harness/discussions/10074
assistant/attempt
assistant/attempt is an event added in format v2 for failed, retried, cancelled, or stream-error attempts. It carries turn, step, and stream, and adds no message to the model-visible history.— https://github.com/deepseek-ai/deepseek-harness/discussions/10074
session.event
session.event is the server-to-client notification in the SDK protocol. Its payload is a sessionId plus one SessionEvent, and it is the minimal stable surface recommended for channel plugins.— https://github.com/deepseek-ai/deepseek-harness/discussions/10074

Sources