Files
deepseek-harness/docs/rfc/implemented/architecture/2026-06-11-content-block-vocabulary.md
T
Tianyi Cui 83271191d1 Merge branch 'codex/simp-agent-entry-state' into codex/simp-unify-agent-session-id
# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/rfc/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md
#	docs/rfc/implemented/architecture/2026-06-20-branded-ids.md
#	docs/rfc/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md
#	docs/rfc/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md
#	docs/rfc/proposed/simplification/2026-06-20-unify-agent-and-session-id.md
#	packages/bash/tool-bash/README.md
#	packages/bash/tool-bash/src/index.ts
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/tests/properties.spec.ts
#	packages/core/agent/README.md
#	packages/core/agent/src/index.ts
#	packages/guard/repeat-tool-guard/src/index.ts
#	packages/hooks/hooks-claude/tests/bridge.spec.ts
#	packages/subagent/subagent-acp/tests/mock-acp-server.ts
#	packages/ui/acp/tests/dispose.spec.ts
#	packages/ui/stdio-agent/src/index.ts
#	packages/ui/stdio-agent/src/stdio-chat.ts
#	packages/ui/stdio-agent/tests/stdio-chat.spec.ts
#	packages/util/brand/src/index.ts
2026-07-14 18:51:24 +08:00

2.3 KiB

RFC: Provider-neutral content-block vocabulary owned by dsh-llm

Status: implemented

Problem

The harness needs one internal language for messages that the loop, session log, and all plugins speak.

Decision

Own the vocabulary: messages are arrays of typed content blocks (text, reasoning, tool-call, tool-result), with the union derived from the merge-extensible ContentBlockMap so plugins add block types via declaration merging. The same merge-extensible-map pattern types every "stringly" field (MessageSource, FinishReason, TurnTrigger, TurnEndReason). Streaming is a raw chunk protocol; BlockAssembler is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs.

In-session context injection (context/message, steering/message) renders as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. Live-adapter validation confirms this rendering for current DeepSeek behavior; a future provider-specific mismatch belongs in that adapter rather than a new canonical role.

Alternatives considered

  • Mirror the DeepSeek/OpenAI chat-completions shape — zero mapping cost for the first provider, but awkward for rich content (reasoning, tool results as structured blocks).
  • Adopt Anthropic's Messages block structure verbatim — battle-tested, but the canonical types would mirror a third-party API the harness does not target first.

Consequences

  • Reasoning has a core home without provider-specific shapes.
  • Multimodal blocks return only with coordinated adapter, UI, and compaction support; see the drop-image RFC.
  • Cache hints and assistant prefill remain absent until a shipping adapter can honor them; see the producer-less variants and inert request knobs RFCs.
  • Every adapter pays a translation cost; the first real adapters have since validated the streaming protocol, and new adapters should continue proving their provider-specific mapping in adapter-local tests.
  • IDs that cross package boundaries are branded (CallId, the shared agent/session SessionId) — nominal typing at zero runtime cost.