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
This commit is contained in:
Turtle
2026-07-20 14:37:04 +08:00
parent b25b78fd02
commit f593ef5ab5
47 changed files with 230 additions and 266 deletions

View File

@@ -11,9 +11,9 @@ import { isAbsolute } from 'node:path'
import { deepFreeze } from '@deepseek-ai/dsh-llm'
import { scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { ContentBlock, Message, MessageSource } from '@deepseek-ai/dsh-llm'
import type { Message } from '@deepseek-ai/dsh-llm'
import { SESSION_FORMAT_VERSION, SessionId } from './types.ts'
import type { ContextEnvelope, CreateSessionOptions, EpochHeader, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SurfaceIntent, SurfaceEventType } from './types.ts'
import type { CreateSessionOptions, EpochHeader, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SurfaceIntent, SurfaceEventType } from './types.ts'
import { snapshotJsonValue } from './json.ts'
import { SurfaceManager } from './surface.ts'
import type { SessionSurface } from './surface.ts'
@@ -80,19 +80,6 @@ declare module 'cordis' {
}
}
/**
* Render injected context as tagged synthetic user-role content, keeping the
* canonical session vocabulary provider-neutral. Adapter-specific exceptions
* belong in the adapter.
*/
function renderContextEnvelope(content: ContentBlock[], source: MessageSource): ContentBlock[] {
return [
{ type: 'text', text: `<context source=${JSON.stringify(source.kind)}>` },
...content,
{ type: 'text', text: '</context>' },
]
}
/** Detach, validate, and freeze the creation metadata published by a session. */
function snapshotSessionHeader(id: SessionId, source?: SessionHeader): SessionHeader {
const input: unknown = source === undefined
@@ -226,22 +213,6 @@ interface SessionEntry {
/** Store attachment for the append path; module-private to keep Session store-agnostic publicly. */
const attachments = new WeakMap<Session, SessionEntry>()
/**
* Render one context contribution exactly as it will appear in model history.
* @param content - content blocks supplied by the context producer.
* @param source - attribution used by the canonical context envelope.
* @param envelope - canonical tagged framing or caller-owned raw framing.
* @returns a detached block list ready for the derived model transcript.
*/
export function renderContextContent(
content: ContentBlock[],
source: MessageSource,
envelope: ContextEnvelope = 'context',
): ContentBlock[] {
const cloned = structuredClone(content)
return envelope === 'raw' ? cloned : renderContextEnvelope(cloned, source)
}
/**
* An event-sourced session: an append-only log of {@link SessionEvent}s.
*
@@ -507,7 +478,18 @@ export class Session {
// trace/replay data.
switch (event.type) {
case 'user/message': {
// Injected context and mid-turn steering project identically to a user
// prompt: content verbatim, in user role. context's `source`/`meta` and
// steering's `turn` are log-only and do not reach the model. Do NOT
// re-add per-type framing (e.g. `<context>`/`<steering>`) here: framing is
// caller-owned — a producer bakes it into `content`, as workspace-context
// does with `<system-reminder>` — or, if reintroduced, must be driven by
// the event `meta` map and a dedicated renderer, keeping this projection a
// verbatim pass-through. See the deferred design note in
// ../../../../.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md
case 'user/message':
case 'context/message':
case 'steering/message': {
return { role: 'user', content: event.data.content }
}
case 'assistant/message': {
@@ -524,13 +506,6 @@ export class Session {
content: [{ type: 'tool-result', toolCallId: callId, content, isError }],
}
}
case 'context/message': {
const { content, source, envelope } = event.data
return { role: 'user', content: renderContextContent(content, source, envelope) }
}
case 'steering/message': {
return { role: 'user', content: event.data.content }
}
default:
// A non-surface event (boundary, chunk, log-only record) projects to
// no message. Merge-extensible union: no assertNever here.

View File

@@ -2,9 +2,6 @@ import type { Branded } from '@deepseek-ai/dsh-brand'
import type { AssistantProvenance, CallId, ContentBlock, LlmCallConfig, Message, MessageSource, StreamChunk, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm'
import type { JsonValue } from './json.ts'
/** Canonical context-tag framing, or caller-owned framing rendered verbatim. */
export type ContextEnvelope = 'context' | 'raw'
/** Identifies one session in the store (and its persistence artifacts). */
export type SessionId = Branded<'SessionId'>
@@ -205,14 +202,17 @@ export interface SessionEventMap {
/**
* In-session context injection (file-change notices, subdir AGENTS.md,
* skill content, cron notifications, …). Rendered into the derived history
* as synthetic context — NOT a user prompt. `envelope: 'raw'` lets a caller
* own the complete model-facing frame; `meta` is durable JSON state omitted
* from the model projection.
* as a synthetic user-role message carrying `content` verbatim — NOT a
* user prompt. `meta` is durable JSON state omitted from the model
* projection; it is also the intended channel for any future framing
* directive (a producer declares the frame, a dedicated renderer applies it —
* see the deferred note in
* ../../../../.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md),
* so the surface keeps projecting `content` verbatim rather than wrapping it.
*/
'context/message': {
content: ContentBlock[]
source: MessageSource
envelope?: ContextEnvelope
meta?: JsonValue
}
/** Raw stream chunk — token-level replay fidelity. */