mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(agent-loop): project runtime context before steps
This commit is contained in:
@@ -13,7 +13,7 @@ import { scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
import type { Scoped } from '@deepseek-ai/dsh-scope'
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import { SESSION_FORMAT_VERSION, SessionId } from './types.ts'
|
||||
import type { CreateSessionOptions, EpochHeader, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SurfaceIntent, SurfaceEventType } from './types.ts'
|
||||
import type { CreateSessionOptions, EpochHeader, RequestContext, 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'
|
||||
@@ -392,7 +392,7 @@ export class Session {
|
||||
readonly firstLiveSeq: number
|
||||
|
||||
constructor(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader) {
|
||||
if (seed) {
|
||||
if (seed !== undefined) {
|
||||
// Validate the seed to the SAME invariants `append` enforces, so a
|
||||
// replay/fork (`ctx.sessions.create(id, { seed })`) cannot construct a
|
||||
// live log that no persistence backend could store: each event's `data`
|
||||
@@ -429,7 +429,7 @@ export class Session {
|
||||
// captures the creation seed: no load-time write. Re-marking is skipped
|
||||
// because a cold session is resumed on first touch, so repeatedly opening
|
||||
// one must not grow its log per open.
|
||||
if (this.firstLiveSeq > 0 && this.log.at(-1)?.type !== 'session/end-seed') {
|
||||
if (seed !== undefined && this.log.at(-1)?.type !== 'session/end-seed') {
|
||||
this.append('session/end-seed', {})
|
||||
}
|
||||
}
|
||||
@@ -566,6 +566,25 @@ export class Session {
|
||||
return this.headerFold
|
||||
}
|
||||
|
||||
/** Cached fold of `request/context` events. */
|
||||
private contextFold: RequestContext | undefined
|
||||
private contextFoldSeq = 0
|
||||
|
||||
/**
|
||||
* Return the latest resolved route metadata, or `undefined` before the first
|
||||
* `request/context` event. Each event is folded once.
|
||||
* @returns the latest immutable route metadata.
|
||||
*/
|
||||
requestContext(): RequestContext | undefined {
|
||||
if (this.contextFoldSeq < this.log.length) {
|
||||
for (const event of this.log.slice(this.contextFoldSeq)) {
|
||||
if (event.type === 'request/context') this.contextFold = deepFreeze({ ...event.data })
|
||||
}
|
||||
this.contextFoldSeq = this.log.length
|
||||
}
|
||||
return this.contextFold
|
||||
}
|
||||
|
||||
/** The derived-message cache: frozen projections, extended per unseen node. */
|
||||
private derived: Message[] = []
|
||||
/** Surface position (nodes projected) the cache has reached. */
|
||||
|
||||
@@ -149,7 +149,8 @@ function validateEvent(
|
||||
break
|
||||
case 'steering/message':
|
||||
case 'todo/write':
|
||||
case 'request/header': {
|
||||
case 'request/header':
|
||||
case 'request/context': {
|
||||
if (trace.openTurn === null) {
|
||||
fail(`${event.type} appended outside any open turn (core execution events must be turn-enclosed)`)
|
||||
}
|
||||
|
||||
@@ -151,6 +151,16 @@ export interface EpochHeader {
|
||||
tools?: ToolSchema[]
|
||||
}
|
||||
|
||||
/** Registration-bound metadata for one resolved model route. */
|
||||
export interface RequestContext {
|
||||
/** Registered provider route the metadata belongs to. */
|
||||
provider: string
|
||||
/** Provider-owned model id the metadata belongs to. */
|
||||
model: string
|
||||
/** Maximum combined request and response context in tokens, when advertised. */
|
||||
contextWindow?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a `request/header` snapshot was appended: `'initial'` — the log's first
|
||||
* header (a new conversation); `'resume'` — a loop instance's first request
|
||||
@@ -233,6 +243,11 @@ export interface SessionEventMap {
|
||||
* It is log-only; the latest snapshot reconstructs the request header.
|
||||
*/
|
||||
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
|
||||
/**
|
||||
* Route metadata for the next request, logged only when the route or capacity
|
||||
* changes. It does not participate in request reconstruction or header equality.
|
||||
*/
|
||||
'request/context': RequestContext
|
||||
/**
|
||||
* Marks the end of a constructor seed. Events before it have smaller seq
|
||||
* values and came from the seed (resume, fork, or replay); this lifecycle
|
||||
|
||||
Reference in New Issue
Block a user