review: drop the structured-output nudge; FIXME the context-global registry constraint

Two human review directives:

- No re-prompt. A structured child that finishes a turn cleanly without
  calling structured_output settles error to the parent immediately —
  readResult already carried that mapping; the nudge loop only delayed it.
  Deletes the loop, its cancellation-window guard, STRUCTURED_OUTPUT_NUDGE,
  and the structuredNudgeRetries Config on both backends.

- FIXME in the structured module doc: per-agent/per-session tool registry and
  prompt assembly would dissolve the final-assembly enforcement dance (the
  placeholder tool, the swap, the strip, the global-registration lifetime).
This commit is contained in:
Tianyi Cui
2026-07-07 09:14:48 +08:00
parent a520965f09
commit b907c20213
13 changed files with 50 additions and 135 deletions
@@ -22,7 +22,6 @@ import { assertSupportedOutputSchema } from '@deepseek-ai/dsh-tools'
import type { SubagentResult, SubagentRun, SubagentStartRequest, SubagentStopReason } from '@deepseek-ai/dsh-subagent'
import {
acquireStructuredRuntime,
STRUCTURED_OUTPUT_NUDGE,
type StructuredAcquisition,
} from './structured.ts'
@@ -30,7 +29,6 @@ export {
acquireStructuredRuntime,
STRUCTURED_OUTPUT_TOOL,
STRUCTURED_OUTPUT_INSTRUCTION,
STRUCTURED_OUTPUT_NUDGE,
type StructuredAcquisition,
} from './structured.ts'
@@ -90,13 +88,6 @@ export interface InProcessRunOptions {
* parent's log (FORK), or `undefined` for a fresh child (SPAWN).
*/
readonly seed?: SessionEvent[]
/**
* How many times a structured run re-prompts a child that finished a turn
* cleanly WITHOUT calling `structured_output` (see the structured module).
* REQUIRED, resolved from the backend's validated Config — per the explicit-
* defaulting rule, the driver never fills it with a hidden fallback.
*/
readonly structuredNudgeRetries: number
}
/**
@@ -178,7 +169,7 @@ export function startInProcessRun(
let cancelled = false
// An accessor, not an inline read: `cancelled` mutates from closures (the
// abort listener, run.cancel), which control-flow narrowing cannot see — an
// inline `!cancelled` in the nudge condition reads as always-true.
// inline read at the result mapping would narrow to the initializer.
const isCancelled = (): boolean => cancelled
const requestCancel = (reason: string): void => {
cancelled = true
@@ -196,28 +187,9 @@ export function startInProcessRun(
if (request.signal?.aborted) return { output: [], stopReason: 'aborted' }
child.send(request.prompt)
await child.whenIdle()
if (structured) {
// Nudge loop: a child that finished a turn CLEANLY without calling
// structured_output gets re-prompted, up to the backend-configured
// retry count. An errored/aborted turn is not nudged — its failure is
// the honest result (a cancelled turn ends `aborted`, and a pre-turn
// cancel leaves no `turn/end` at all, so neither reads `completed`).
// `!cancelled` closes the remaining window: a cancel landing AFTER a
// clean turn end clears nothing — `child.cancel()` only kills
// queued/running work — so without it the next `send` would spend a
// fresh post-cancellation turn; the condition re-evaluates after
// every `whenIdle()`, so a mid-nudge cancel stops the loop at the
// next boundary too.
let nudges = options.structuredNudgeRetries
while (
!isCancelled() && structured.captured(child) === undefined && nudges > 0
&& lastOwnTurnEnd(child, seedLength)?.data.reason.kind === 'completed'
) {
nudges -= 1
child.send([{ type: 'text', text: STRUCTURED_OUTPUT_NUDGE }])
await child.whenIdle()
}
}
// Deliberately NO re-prompt when a structured child finishes cleanly
// without calling structured_output: readResult maps that to `error` —
// the shortfall goes to the parent instead of buying extra model turns.
return readResult(child, seedLength, isCancelled(), structured ? { captured: structured.captured(child) } : undefined)
} finally {
request.signal?.removeEventListener('abort', onAbort)
@@ -241,12 +213,6 @@ export function startInProcessRun(
}
}
/** The child's OWN last `turn/end` event (events at or after `seedLength`), if any. */
function lastOwnTurnEnd(child: Agent, seedLength: number): SessionEvent<'turn/end'> | undefined {
return child.session.events.slice(seedLength)
.findLast((e): e is SessionEvent<'turn/end'> => e.type === 'turn/end')
}
/**
* Read a settled child's terminal result from its session log, scoped to the
* child's OWN events (everything at or after `seedLength` — fork seeds the