DeepSeek Harness 源码实战 第4章:Session 会话日志——单一事实源
DeepSeek Harness æºç 宿 第4ç« ï¼Session ä¼è¯æ¥å¿ââåä¸äºå®æº
ææµªç« 2026-08-19 0 é 读13åéç³»åï¼DeepSeek Harness æºç 宿 ï½ è¿åº¦ 4/16
åæä»åºï¼github.com/deepseek-aiâ¦
ä½ ä»¥ä¸ºçè天记å½ï¼å ¶å®åªæ¯æ¥å¿çä¸ä¸ªæå½±ã
åè¿ agent 项ç®ç人é½ç¥éï¼ä¸ä¸æç®¡çæå¤çã对è¯å岿ä¹åï¼å·¥å
·è°ç¨çä¸é´ç»ææ¾åªï¼æµå¼è¾åºç token åçè¦ä¸è¦ä¿çï¼fork ä¸ä¸ªä¼è¯æ¶åªäºç¶æè¦å¤å¶ï¼è¿äºé®é¢å¨ dsh éæä¸ä¸ªç»ä¸ççæ¡ï¼Session äºä»¶æ¥å¿ã
ææ¯ææµªç«ï¼è¿ä¸ç« æä»¬æå¼ dsh çæ°æ®æ ¸å¿ââSessionã宿¯æ´ä¸ªç³»ç»çãåä¸äºå®æºãï¼Single Source of Truthï¼ï¼ææå ¶ä»è§å¾é½ä»å®æ´¾çã
4.1 为ä»ä¹æ¯ append-only äºä»¶æ¥å¿è䏿¯æ¶æ¯æ°ç»
å æ¥ç两ç§è®¾è®¡æè·¯çå·®å¼ã
æ¹æ¡ Aï¼æ¶æ¯æ°ç»ï¼å¤§å¤æ°ç®å agent 项ç®çåæ³ï¼
messages = [
{ role: 'user', content: '帮æè¯»ä¸ä¸ package.json' },
{ role: 'assistant', content: '...', tool_calls: [...] },
{ role: 'tool', content: '...', tool_call_id: '...' },
{ role: 'assistant', content: 'è¿æ¯æä»¶å
容' },
]
ç®åç´æ¥ï¼ä½æé®é¢ï¼
- æµå¼è¾åºç token åç丢äºââåªææç»æ¶æ¯ï¼æ æ³ç²¾ç¡®åæ¾
- turn/step çè¾¹çä¿¡æ¯ä¸¢äºââä¸ç¥éåªæ¡æ¶æ¯å±äºåªæ¬¡è¯·æ±
- 请æ±é ç½®ï¼providerãmodelãåæ°ï¼ä¸¢äºââä¸ç¥éç¨äºä»ä¹æ¨¡å
- fork æ¶éè¦æ·±æ·è´æ´ä¸ªæ°ç»ï¼ä¸æ æ³è¡¨è¾¾ãfork èªæä¸ªä½ç½®ã
- å·¥å ·è°ç¨çæ§è¡ç»èï¼è¶ æ¶ãéè¯ã审æ¹ï¼ä¸¢äº
æ¹æ¡ Bï¼append-only äºä»¶æ¥å¿ï¼dsh çåæ³ï¼
events = [
{ seq: 1, type: 'turn/start', data: { turn: 1 } },
{ seq: 2, type: 'step/start', data: { turn: 1, step: 1 } },
{ seq: 3, type: 'user/message', data: { ... } },
{ seq: 4, type: 'request/header', data: { header: ... } },
{ seq: 5, type: 'assistant/chunk', data: { chunk: ... } },
{ seq: 6, type: 'assistant/chunk', data: { chunk: ... } },
{ seq: 7, type: 'assistant/message', data: { message: ..., usage: ... } },
{ seq: 8, type: 'tool/call', data: { callId: ..., name: ..., arguments: ... } },
{ seq: 9, type: 'tool/result', data: { message: ... } },
{ seq: 10, type: 'step/end', data: { turn: 1, step: 1 } },
{ seq: 11, type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } },
]
æ¯ä¸æ¡é½æ¯ä¸ä¸ªä¸å¯åçäºä»¶ï¼æä¸ä¸ªåè°éå¢ç seqï¼åºåå·ï¼ãææä¿¡æ¯é½ä¿çï¼æµå¼åçãturn/step è¾¹çã请æ±é ç½®ãå·¥å ·æ§è¡ç»èã
æ¥ç dsh æºç ä¸å¯¹ Session çå®ä¹ï¼æ¥èª packages/core/session/src/types.tsï¼
// packages/core/session/src/types.tsï¼èéï¼
/**
* The merge-extensible, append-only source of truth for an agent interaction.
* Message history is derived from this log. Every event is lossless JSON and
* sequence numbers stay contiguous, including raw chunks, so persistence can
* store the canonical log verbatim.
*/
export interface SessionEventMap {
// ... äºä»¶ç±»åå®ä¹
}
注ééä¸ä¸ªå ³é®è¯ï¼append-onlyï¼ä» 追å ï¼ãlossless JSONï¼æ æ JSONï¼ãderivedï¼æ´¾ççï¼ãæ¶æ¯å岿¯ä»æ¥å¿æ´¾ççï¼ä¸æ¯æ¥å¿æ¬èº«ã
对æ¯è¡¨ï¼
| 维度 | æ¶æ¯æ°ç» | äºä»¶æ¥å¿ |
|---|---|---|
| å卿¹å¼ | å¯åæ°ç»ï¼å¯ä¿®æ¹ | ä» è¿½å ï¼ä¸å¯å |
| æµå¼åç | 丢失 | ä¿çï¼assistant/chunkï¼ |
| turn/step è¾¹ç | æ | æ¾å¼è®°å½ |
| 请æ±é ç½® | æ | request/header å¿«ç § |
| fork | æ·±æ·è´æ´ä¸ªæ°ç» | ä»æä¸ª seq å¼å§å¼ç¨ |
| åæ¾ | ä¸å¯è½ | å®å ¨å¯åæ¾ |
| æä¹ å | èªå®ä¹æ ¼å¼ | åæ ·åå¨ JSONL/SQLite |
éå¥ï¼æ¶æ¯æ°ç»æ¯ç»æï¼äºä»¶æ¥å¿æ¯è¿ç¨ã丢äºè¿ç¨ï¼å°±ä¸¢äºåæ¾å审计çè½åã
4.2 SessionEventMap å ¨è§£æ
SessionEventMap æ¯ Session æ¥å¿çãè¯æ±è¡¨ãââå®ä¹äºææåæ³çäºä»¶ç±»åãæ¥ççå®ä»£ç ï¼
// packages/core/session/src/types.tsï¼èéï¼
export interface SessionEventMap {
/** Opens turn `turn` before the loop claims queued input or runs pre-step. */
'turn/start': { turn: number }
/** Closes turn `turn` with the TurnEndReason that ended it. */
'turn/end': { turn: number; reason: TurnEndReason }
/** Opens step `step` of turn `turn` â one model call plus the tool executions it requested. */
'step/start': { turn: number; step: number }
/** Closes step `step` of turn `turn`. */
'step/end': { turn: number; step: number }
/**
* A user-role message on the model-visible surface: a direct human prompt,
* a synthetic agent.inject() context, or an entered goal continuation round.
*/
'user/message': UserMessage
/** Raw stream chunk â token-level replay fidelity. */
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
/** Assembled assistant message for one step (derived history uses this). */
'assistant/message': {
turn: number; step: number; message: AssistantMessage; usage?: TokenUsage
}
/** The model requested one tool invocation: `name` with the raw `arguments` JSON string. */
'tool/call': {
turn: number; step: number; callId: CallId; name: string; arguments: string
}
/** A completed tool call's model-facing result. */
'tool/result': {
turn: number; step: number; message: ToolResultMessage
error?: { name: string; code: string }
meta?: JsonValue
}
/** Whole-list snapshot; latest write wins on replay. Log-only UI state. */
'todo/write': { todos: TodoItem[] }
/** Full header for the next request, appended inside its step before dispatch. */
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
/** Route metadata for the next request, logged only when the route or capacity changes. */
'request/context': RequestContext
/** Marks the end of a constructor seed. Events before it came from seed (resume, fork, replay). */
'session/end-seed': Record<string, never>
}
æ¯ä¸ªäºä»¶ç±»åçè®¾è®¡é½æè®²ç©¶ãéä¸çå ³é®çå 个ï¼
turn/start å turn/endï¼æ è®°ä¸ä¸ªè½®æ¬¡çå¼å§åç»æãturn å¯è½å å«é¶ä¸ªæå¤ä¸ª stepï¼æ¥éª¤ï¼ãrejectionï¼æç»ï¼ã空è¾å ¥ãåæ¶ã失败é½å¯è½å ³éä¸ä¸ª turn èä¸äº§ç stepââæ¥å¿ä¼è®°å½è¿æ¬¡å°è¯ã
step/start å step/endï¼æ è®°ä¸æ¬¡æ¨¡å请æ±åå ¶å·¥å ·è°ç¨çè¾¹çãä¸ä¸ª step = 䏿¬¡æ¨¡åè°ç¨ + è¿æ¬¡è°ç¨è¯·æ±çå·¥å ·æ§è¡ã
user/messageï¼ç¨æ·è§è²çæ¶æ¯ã注æå®æä¸ç§æ¥æºï¼ç´æ¥ç人类è¾å
¥ãåæç agent.inject() ä¸ä¸æï¼æä»¶åæ´éç¥ãåç®å½ AGENTS.mdãskill å
容çï¼ãgoal continuation roundï¼ç®æ ç»è·è½®æ¬¡ï¼ãsource åæ®µåºåå®ä»¬ã
assistant/chunk å assistant/messageï¼æµå¼åçåç»è£
åçæ¶æ¯ãchunk æ¯ token 级å«çåå§åçï¼ä¿è¯åæ¾ä¿çãmessage æ¯ä¸ä¸ª step ç宿´ç»è£
ç»æãusage åæ®µï¼token ç¨éï¼æå¨ message ä¸ï¼æ¨¡åè¾åºåè®¡è´¹æ°æ®ä¸èµ·æ
è¡ã
tool/call å tool/resultï¼å·¥å
·è°ç¨åç»æãcallId é
对ãarguments æ¯åå§ JSON åç¬¦ä¸²ï¼æªè§£æï¼ï¼ä¸æ¨¡å产ççä¸è´ãresult ç meta åæ®µæ¯å·¥å
·ç§æçå±ç¤ºè½½è·ï¼å¯¹ core ä¸éæã
request/headerï¼è¯·æ±é ç½®å¿«ç §ãå å« providerãmodelãsystem promptãtool schemasãææ°çå¿«ç §ç¨äºé建ä¸ä¸æ¬¡è¯·æ±ãreason åæ®µè®°å½ä¸ºä»ä¹åäºä¸ªæ° headerï¼initialï¼æ°ä¼è¯ï¼ãresumeï¼è¿ç¨éå¯åç馿¬¡è¯·æ±ï¼ãchangeï¼æ¢äºæ¨¡åæé ç½®ï¼ã
session/end-seedï¼æ è®°ç§ååå²çç»æãæé 彿°ä» resumeãfork æ replay 产ççäºä»¶é½å¨è¿ä¸ªæ è®°ä¹åï¼å½åçå½å¨æäº§ççäºä»¶å¨ä¹åãè¿ä¸ªäºä»¶æ¯ log-only çï¼payload 为空ï¼ä½ç½®åæ¶é´æ³å°±æ¯å®çå«ä¹ã
éæ¥è¡¨ï¼
| äºä»¶ç±»å | ç±»å« | è½½è·è¦ç¹ |
|---|---|---|
| turn/start | è¾¹ç | turn åºå· |
| turn/end | è¾¹ç | turn åºå· + ç»æåå |
| step/start | è¾¹ç | turn + step åºå· |
| step/end | è¾¹ç | turn + step åºå· |
| user/message | Surface | ç¨æ·æ¶æ¯ï¼ä¸ç§æ¥æºï¼ |
| assistant/chunk | æµå¼ | åå§ token åç |
| assistant/message | Surface | ç»è£ åç宿´æ¶æ¯ + usage |
| tool/call | Surface | callId + name + åå§ arguments |
| tool/result | Surface | callId + ç»ææ¶æ¯ + error + meta |
| todo/write | Log-only | æ´åè¡¨å¿«ç § |
| request/header | Log-only | 请æ±é ç½®å¿«ç § + åå |
| request/context | Log-only | è·¯ç±å æ°æ® |
| session/end-seed | Log-only | 空 payloadï¼æ è®°ç§åè¾¹ç |
4.3 æ ¸å¿ä¸åéï¼æ¨¡åå¯è§å³å·²è®°å½
dsh ææ¡£éæä¸å¥å ç²çè¯ï¼
模åå¯è§å³å·²è®°å½ã æµè¾¾æ¨¡å请æ±çä¸åé½å¿ é¡»è½ä»æ¥å¿é建ï¼å¹¶ç±ä¸é¡¹è¿è¡æ¶ä¸åéæè¨è¿ä¸ç¹ã
è¿å¥è¯æ¯ä»ä¹ææï¼å®æ¯ä¸ä¸ªè¿è¡æ¶æè¨ï¼å¦ææä¸ªä¸è¥¿å°äºæ¨¡åé£éï¼åºç°å¨æ¨¡å请æ±ç messages éï¼ï¼é£å®ä¸å®å¨æ¥å¿éæå¯¹åºçäºä»¶ãåä¹ï¼å¦ææ¥å¿é没æï¼å®å°±ä¸å¯è½åºç°å¨æ¨¡å请æ±éã
è¿ä¸ªä¸åéçä»·å¼å¨äºå¯å®¡è®¡æ§ãä½ å¯ä»¥å¨ä»»ä½æ¶åæ£æ¥æ¥å¿ï¼ç¥é模å卿¯ä¸æ¥çå°äºä»ä¹ã没æãéå½¢ä¸ä¸æãââä¸å卿¥å¿ä¹å¤ã模åå´è½çå°çä¿¡æ¯ã
å¦æä½ æ³æ°å¢ä¸ç§æ¨¡åå¯è§çè¾å ¥ï¼æ¯å¦ä¸ç§æ°çä¸ä¸ææ³¨å ¥ç±»åï¼ï¼ä½ ä¸è½åªæ¯æææ¬å¡è¿ messages æ°ç»ãä½ å¿ é¡»ï¼
- æ©å±
SessionEventMapï¼æ°å¢ä¸ä¸ªäºä»¶ç±»å - ç¡®ä¿è¿ä¸ªäºä»¶è¢«è¿½å å°æ¥å¿
- 仿¥å¿æ¸²ææ¶ï¼æå®çº³å ¥ messages æ´¾ç
è¿æ ·ï¼æ°å¢çè¾å ¥èªå¨æ»¡è¶³ãå¯è§å³å·²è®°å½ãçä¸åéã
æ¥ç SessionEvent çç±»åå®ä¹ï¼çè§£äºä»¶çç»æï¼
// packages/core/session/src/types.tsï¼èéï¼
export type SessionEvent<T extends SessionEventType = SessionEventType> = {
[K in SessionEventType]: {
type: K
/** Monotonic sequence number within the session. */
seq: number
/** Unix epoch milliseconds. */
time: number
data: SessionEventMap[K]
/**
* Marks an event a reader may safely skip when it does not recognize `type`.
* Absent means required: a reader meeting an unrecognized type without this
* marker MUST refuse to reconstruct the session instead of silently dropping
* the event.
*/
ignorable?: true
// Surface äºä»¶é¢å¤æºå¸¦çå
æ°æ®ï¼ä»
SurfaceEventTypeï¼
}
}[SessionEventType]
注æ ignorable åæ®µç设计ï¼é»è®¤æ
åµä¸ï¼éå°ä¸è®¤è¯çäºä»¶ç±»åï¼reader å¿
é¡»æç»é建ä¼è¯ï¼è䏿¯éé»ä¸¢å¼ãåªææ è®°äº ignorable: true çäºä»¶æå
许跳è¿ãè¿æ¯ä¸ä¸ªãå®å
¨å¤±è´¥ãç设计ââéå¿æ 记导è´è¿åº¦æç»ï¼ä¸æ¹ä¾¿ï¼ï¼è䏿¯é黿¥åä¸ä¸ªæ®ç¼ºçä¼è¯ï¼å±é©ï¼ã
éå¥ï¼ä¸åé䏿¯ææ¡£éçå£å·ï¼æ¯ä»£ç éçè¿è¡æ¶æè¨ãå¯å®¡è®¡æ¯è®¾è®¡åºæ¥çï¼ä¸æ¯äºåè¡¥çã
4.4 Surface æºå¶ï¼append / replaceãsourceEventSeqs 溯æº
äºä»¶æ¥å¿éæä¸ç±»äºä»¶ä¼äº§åºæ¨¡åå¯è§çæ¶æ¯ï¼user/messageãassistant/messageãtool/resultãå®ä»¬è¢«ç§°ä¸º Surface Eventï¼è¡¨é¢äºä»¶ï¼ã
Surfaceï¼è¡¨é¢ï¼æ¯äºä»¶æ¥å¿ä¹ä¸çä¸ä¸ªæåºè§å¾ââæ¨¡åçå°ç对è¯åºåã䏿¯ææäºä»¶é½å¨è¡¨é¢ä¸ï¼åªæ Surface Event å¨ãèä¸ Surface Event æä¸¤ç§è¿å ¥æ¹å¼ï¼
appendï¼è¿½å ï¼ï¼æ·»å å°è¡¨é¢å°¾é¨ãæ£å¸¸çç¨æ·æ¶æ¯ãå©ææ¶æ¯ãå·¥å ·ç»æé½æ¯ appendã
replaceï¼æ¿æ¢ï¼ï¼æ¿æ¢è¡¨é¢ä¸çä¸ä¸ªåºé´ãç¨äº compactionï¼å缩ï¼ââå½åå²å¤ªé¿éè¦å缩æ¶ï¼compaction æä»¶äº§çä¸ä¸ªæ¿æ¢äºä»¶ï¼ç¨ä¸ä¸ªæè¦æ¶æ¯æ¿æ¢æä¹åçä¸å¤§æ®µå¯¹è¯ã
æ¥çç±»åå®ä¹ï¼
// packages/core/session/src/types.tsï¼èéï¼
export type SurfaceOp =
| 'append'
| { op: 'replace'; start: number; end: number }
export interface SurfaceIntent {
surfaceOp: SurfaceOp
/**
* Complete set of known source-event seqs. assistant/message may use a
* present empty array for a known empty provider stream.
*/
sourceEventSeqs?: number[]
}
SurfaceOp ç两个åä½ï¼'append' å { op: 'replace', start, end }ãreplace ç start å end æ¯è¡¨é¢èç¹çä½ç½®ï¼inclusiveï¼ï¼è¢«æ¿æ¢çèå´å¿
须已åå¨ã
sourceEventSeqs æ¯æº¯æºä¿¡æ¯ââè®°å½è¿ä¸ªè¡¨é¢äºä»¶æ¯ç±åªäºæºäºä»¶äº§ççãæ¯å¦ assistant/message ç sourceEventSeqs å¯è½å
å«äº§çå®çææ assistant/chunk äºä»¶ç seqãè¿æ ·ä½ å¯ä»¥ä»ä¸æ¡ç»è£
åçæ¶æ¯è¿½æº¯å°å®ç±åªäº token åçç»æã
Surface çå®ç°ä»£ç å¨ packages/core/session/src/surface.tsï¼
// packages/core/session/src/surface.tsï¼èéï¼
const SURFACE_EVENT_TYPES = new Set<string>([
'user/message',
'assistant/message',
'tool/result',
])
export function isSurfaceEligibleType(type: string): boolean {
return SURFACE_EVENT_TYPES.has(type)
}
export function isSurfaceEvent(event: SessionEvent): event is SurfaceEvent {
if (!SURFACE_EVENT_TYPES.has(event.type)) return false
return (event as SessionEvent<SurfaceEventType>).surfaceOp !== undefined
}
export function isAppendSurfaceEvent(
event: SessionEvent,
): event is SurfaceEvent & { surfaceOp: 'append' } {
return isSurfaceEvent(event) && event.surfaceOp === 'append'
}
ä¸ä¸ªå½æ°åå«å¤æï¼äºä»¶ç±»åæ¯å¦ eligibleï¼æèµæ ¼ä¸è¡¨é¢ï¼ãäºä»¶æ¯å¦ççæ¯ Surface Eventï¼ç±»å坹䏿 surfaceOp æ è®°ï¼ãäºä»¶æ¯å¦æ¯ append æ¥æºç Surface Eventã
ä»£ç æ³¨ééæä¸æ®µéè¦ç设计说æï¼
The model-visible surface deliberately shadows replaced ranges, so it is the wrong source for a human transcript â a landed replacement would erase conversation the user already saw. Append-origin events are that transcriptâs durable source material; replacement copies stay model-only.
ç¿»è¯ï¼æ¨¡åå¯è§ç表é¢ä¼æ æé®è½è¢«æ¿æ¢çèå´ï¼compaction åæ§æ¶æ¯å¨è¡¨é¢ä¸æ¶å¤±ï¼ï¼æä»¥è¡¨é¢ä¸éåå人类å¯è¯»ç对è¯è®°å½ãappend æ¥æºçäºä»¶ææ¯äººç±»è®°å½çæä¹ æºææï¼replace ç坿¬åªå¯¹æ¨¡åå¯è§ã
è¿æå³ç dsh åºåäºä¸¤ç§è§å¾ï¼
| è§å¾ | æ°æ®æ¥æº | ç¨é |
|---|---|---|
| 模åè¡¨é¢ | Surface Eventï¼å« replaceï¼ | æ´¾çæ¨¡å请æ±ç messages |
| äººç±»è®°å½ | ææ append æ¥æºç Surface Event | UI å±ç¤ºãtranscript å¯¼åº |
éå¥ï¼æ¨¡åçå°çåç¨æ·çå°çå¯ä»¥ä¸ä¸æ ·ãè¿ä¸æ¯ bugï¼æ¯ compaction çå¿ ç¶ç»æã
4.5 仿¥å¿æ´¾çä¸åï¼æ¶æ¯åå²ãåæ¾ãforkãæå½±ãæä¹ å
Session æ¥å¿æ¯åä¸äºå®æºï¼ææå ¶ä»è§å¾é½ä»å®æ´¾çã
æ¶æ¯å岿´¾ç
session.deriveMessages() 仿¥å¿æå½±åºæ¨¡åå¯è§ç messages æ°ç»ãå®éå Surface Eventï¼æ surfaceOp æå»ºï¼append 追å å°å°¾é¨ï¼replace æ¿æ¢å¯¹åºåºé´ãæç»ç»æå°±æ¯æ¨¡åä¸ä¸æ¬¡è¯·æ±æ¶çå°ç对è¯åå²ã
åæ¾
å 为 assistant/chunk äºä»¶ä¿çäº token 级å«çåå§åçï¼ä½ å¯ä»¥ç²¾ç¡®åæ¾ä¸æ¬¡æµå¼è¾åºãUI å¯ä»¥ç¨ chunk äºä»¶åæåæºææï¼åæ¾æ¶ä¹è½é token éç°ã
fork
ctx.sessions.fork(source, boundary?, childSessionId?) ä»ä¸ä¸ªæºä¼è¯ fork åºæ°ä¼è¯ãæºä¼è¯çäºä»¶æä¸ºæ°ä¼è¯çç§åï¼seedï¼ï¼ä»¥ session/end-seed äºä»¶æ è®°è¾¹çãæ°ä¼è¯ä»ç§åä¹åå¼å§è¿½å èªå·±çäºä»¶ãè¿ç§æ¹å¼ä¸éè¦æ·±æ·è´æ´ä¸ªä¼è¯ââç§åäºä»¶æ¯å
±äº«çï¼æ°äºä»¶æ¯ç¬ç«çã
æå½±
dsh æä¸ç»æå½±å ï¼ä»æ¥å¿æ´¾çä¸å维度çä¿¡æ¯ï¼
| å | æå½±å 容 |
|---|---|
| session-projection | æ¶æ¯åå²ï¼deriveMessagesï¼ |
| session-projection-cache | æå½±ç¼åï¼é¿å éå¤è®¡ç® |
| session-stats | ä¼è¯ç»è®¡ï¼turn æ°ãstep æ°ãtoken ç¨éçï¼ |
| session-query | SQL æ¥è¯¢æ¥å£ï¼åºäº SQLiteï¼ |
| session-log-export | æ¥å¿å¯¼åºï¼transcript æ ¼å¼ï¼ |
æä¹ å
dsh æ¯æä¸¤ç§æä¹ åå端ï¼
| å端 | å | æ ¼å¼ | éç¨åºæ¯ |
|---|---|---|---|
| JSONL | session-persistence-jsonl | æ¯è¡ä¸ä¸ª JSON äºä»¶ | ç®ååºæ¯ãè°è¯ |
| SQLite | session-persistence-sqlite | å ³ç³»åæ°æ®åº | ç产ç¯å¢ã大æ¥å¿ |
两è é½åæ ·åå¨äºä»¶æ¥å¿ââå 为äºä»¶å·²ç»æ¯ lossless JSONï¼æä¹ åä¸éè¦é¢å¤è½¬æ¢ãæ¢å¤æ¶ç´æ¥è¯»åäºä»¶æµï¼é建å åä¸ç Session 对象ã
session-checkpoint-policy å
è´è´£æä¹
åæ¶æºãå®ä¸å¨æ¯ä¸ªäºä»¶è¿½å æ¶é½åçï¼å¤ªæ
¢ï¼ï¼èæ¯å¨ç¹å®çæ£æ¥ç¹ï¼æ¯å¦è¯·æ±å®æåï¼æ¹éåå
¥ã
䏿¬¡å®æ´äºä»¶åºåçæ¥å¿åæ¾
æ¥çä¸ä¸ªå®æ´ç turn 卿¥å¿éé¿ä»ä¹æ ·ï¼
seq type data æè¦
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
1 turn/start { turn: 1 }
2 step/start { turn: 1, step: 1 }
3 user/message { source: 'human', content: '帮æè¯» package.json' }
4 request/header { reason: 'initial', header: { provider: 'deepseek', model: '...' } }
5 assistant/chunk { chunk: { type: 'text', text: '好ç' } }
6 assistant/chunk { chunk: { type: 'text', text: 'ï¼ææ¥' } }
7 assistant/chunk { chunk: { type: 'text', text: '读å' } }
8 assistant/chunk { chunk: { type: 'tool-call', ... } }
9 assistant/message { message: { content: [text, tool-call] }, usage: { ... } }
10 tool/call { callId: 'c1', name: 'fs_read', arguments: '{"path":"package.json"}' }
11 tool/result { callId: 'c1', message: { content: [text] } }
12 step/end { turn: 1, step: 1 }
13 step/start { turn: 1, step: 2 }
14 user/message { source: 'tool-result', content: ... }
15 request/header { reason: 'change', header: { ... } }
16 assistant/chunk { chunk: { type: 'text', text: 'è¿æ¯' } }
17 assistant/chunk { chunk: { type: 'text', text: 'æä»¶å
容' } }
18 assistant/message { message: { content: [text] }, usage: { ... } }
19 step/end { turn: 1, step: 2 }
20 turn/end { turn: 1, reason: { kind: 'completed' } }
è¿ä¸ªæ¥å¿å¯ä»¥å®æ´åæ¾ï¼ä» seq 5-8 ç chunk å¯ä»¥é建æµå¼è¾åºä½éªï¼ä» seq 4 å 15 ç header å¯ä»¥ç¥éæ¯æ¥ç¨äºä»ä¹æ¨¡åï¼ä» seq 10-11 å¯ä»¥çå°å·¥å ·è°ç¨ç宿´è¿ç¨ãfork æ¶ï¼æ°ä¼è¯ä»¥ seq 20 ä¹å为起ç¹ï¼åé¢ç 20 æ¡äºä»¶æä¸ºç§åã
éå¥ï¼æ¥å¿ä¸æ¯è天记å½çå¤ä»½ï¼èå¤©è®°å½æ¯æ¥å¿çæå½±ã
æ¬ç« å°ç»
| è¦ç¹ | 说æ |
|---|---|
| è®¾è®¡éæ© | append-only äºä»¶æ¥å¿ï¼èéå¯åæ¶æ¯æ°ç» |
| SessionEventMap | å®ä¹ææåæ³äºä»¶ç±»åçæ¥å£ï¼å¯æ©å± |
| æ ¸å¿äºä»¶ | turn/start/endãstep/start/endãuser/messageãassistant/chunkãassistant/messageãtool/callãtool/result |
| 模åå¯è§å³å·²è®°å½ | è¿è¡æ¶ä¸åéï¼æ¨¡åçå°çä¸åé½è½ä»æ¥å¿é建 |
| ignorable åæ®µ | é»è®¤æç»ä¸è®¤è¯çäºä»¶ï¼å®å ¨å¤±è´¥ |
| Surface Event | user/messageãassistant/messageãtool/result ä¸ç±» |
| SurfaceOp | appendï¼è¿½å å°å°¾é¨ï¼æ replaceï¼æ¿æ¢åºé´ï¼ç¨äº compactionï¼ |
| sourceEventSeqs | 溯æºä¿¡æ¯ï¼ä»è¡¨é¢äºä»¶è¿½æº¯å°æºäºä»¶ |
| 两ç§è§å¾ | 模å表é¢ï¼å« replaceï¼vs 人类记å½ï¼ä» appendï¼ |
| æ´¾çè½å | æ¶æ¯åå²ãåæ¾ãforkãæå½±ãæä¹ å |
| æä¹ åå端 | JSONLï¼ç®åï¼å SQLiteï¼çäº§ï¼ |
| ç§åæºå¶ | fork çäºä»¶ä»¥ session/end-seed æ è®°è¾¹ç |
ä¸ç« é¢å
æ¥å¿æäºï¼äºä»¶å®ä¹å¥½äºãä¸ä¸ç« æä»¬æ¥ç驱å¨è¿äºäºä»¶ç弿ââagent-loopã䏿¬¡æ¨¡å请æ±éå°åºèçå¤å°ä¸ªäºä»¶ï¼turn å step æ¯æä¹åä½çï¼å·¥å ·å¹¶åè°åº¦æ¯æä¹åçï¼
ææ¯ææµªç«ï¼å¦æä½ 被 append-only æ¥å¿ç设计åç²äºï¼ç¹ä¸ªæ¶èãä½ ç agent 项ç®éåå²è®°å½åå¨åªï¼è¯è®ºåºèèã
ç³»åè¿åº¦ï¼4/8 ï½ ä¸ä¸ç« ï¼Turn / Step 循ç¯ââagent-loop æºç æè§£
Aitishiku.com