Files
deepseek-harness/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md
T
Turtle f593ef5ab5 fix(session): drop the <context> envelope and project context verbatim
context/message previously defaulted to a <context source="…">…</context>
wrapper. No model is trained on a <context> tag either, and message
framing does not belong on the session surface: the surface projects the
durable log, and a caller that wants a frame formats its own content —
which the one heavy producer (workspace-context) already does with its own
<system-reminder> frame, opting out via 'raw'. The tag only added
machinery — ContextEnvelope plus an envelope field threaded through
InjectOptions, HookContext, the context/message event, and the agent-loop
inject/additionalContexts plumbing.

context/message now projects its content verbatim as a user-role message,
sharing one deriveEventMessage case with user/message and steering/message.
ContextEnvelope and every envelope field are removed; context/message.meta
still carries durable, model-hidden JSON state. Regenerated catalogs and
website API; refreshed the three affected keyless snapshots (envelope field
only; timestamps unchanged).

Broadens and renames the steering Agent Note to cover both envelope
removals as one decision.

Agent Note: .agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md
2026-07-20 16:22:20 +08:00

2.6 KiB

Agent Note: 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) and mid-turn steering (steering/message) originally rendered as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. Both now project as plain user content with no wrapper; see the injected-content-envelope Agent Note. 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 Agent Note.
  • Cache hints and assistant prefill remain absent until a shipping adapter can honor them; see the producer-less variants and inert request knobs Agent Notes.
  • 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.