refactor(agent-loop): separate injected context from turns
This commit is contained in:
@@ -42,7 +42,7 @@ The live registry pipeline has three transformable waterfalls, then the definiti
|
||||
- `ToolExecutionToken` — a fresh branded `Symbol` assigned by the registry. It supports equality correlation only and never crosses a model, log, or worker boundary.
|
||||
- `ToolExecution` — the readonly pipeline view: immutable `{ token, callId, name, arguments, signal, agent?, parent? }`; the registry separately retains and re-fuses the original caller signal. `ToolDispatchExecution` is the `tools/execute`-only view whose required signal is mutable, so a wrapper may replace and restore it but cannot delete it. A nested call's `parent` is a `ToolExecutionToken`, not an execution object.
|
||||
- `ToolRunContext` — the execution passed to a tool body, extending `ToolExecution` with `deferContext(context)`. Composite tools use it to ferry context produced by nested dispatches to the outer result even when the tool later throws or cancellation wins; it never injects immediately.
|
||||
- `ToolExecutionResult` — discriminated execution-local outcome. Success is `{ isError:false, value:JsonValue, content, meta?, additionalContexts? }`; failure is `{ isError:true, error:{ message, info? }, content, meta?, additionalContexts? }` and has no value. Call identity stays on the immutable `ToolExecution`. The registry snapshots, validates, and freezes the canonical value before rendering, then materializes the durable presentation fields before final observation. `ToolFailure.info` carries an internal `{ name, code }` for a `HarnessError`; `additionalContexts` preserves every deferred or post-execute `HookContext` for the loop's post-result FIFO.
|
||||
- `ToolExecutionResult` — discriminated execution-local outcome. Success is `{ isError:false, value:JsonValue, content, meta?, additionalContexts? }`; failure is `{ isError:true, error:{ message, info? }, content, meta?, additionalContexts? }` and has no value. Call identity stays on the immutable `ToolExecution`. The registry snapshots, validates, and freezes the canonical value before rendering, then materializes the durable presentation fields before final observation. `ToolFailure.info` carries an internal `{ name, code }` for a `HarnessError`; `additionalContexts` preserves every deferred or post-execute `AdditionalContext` for the loop's post-result FIFO.
|
||||
- `PreToolDecision` — `{kind:'allow'}` | `{kind:'deny', reason}` | `{kind:'ask', reason?}`. Input rewrite is deliberately not offered; `ask` is serviced by [`ctx.approval`](../../ui/user-approval/README.md) when mounted and otherwise degrades to deny.
|
||||
- `PostToolDecision` — accept may replace `content` or `value`, never both, and may attach `additionalContexts`; block turns feedback into a valueless failure. Content replacement preserves the canonical value and metadata. Value replacement is revalidated and rerenders content/metadata. Accept preserves tool-deferred contexts before decision contexts; block discards tool-deferred contexts and exposes only contexts explicitly supplied by the blocking decision.
|
||||
- `ToolGuard` — `(execution) => string | undefined`; the returned string is a final monotonic denial reason evaluated after the reorderable pre-execute waterfall and before dispatch.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeTarget } fr
|
||||
import type { ScopeKey, ScopeLayer, Scoped } from '@deepseek-ai/dsh-scope'
|
||||
import type { CallId, ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm'
|
||||
import { assertNever, deepFreeze, HarnessError } from '@deepseek-ai/dsh-llm'
|
||||
import type { Agent, HookContext } from '@deepseek-ai/dsh-agent'
|
||||
import type { AdditionalContext, Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
|
||||
import type { JsonValue } from '@deepseek-ai/dsh-session'
|
||||
import type { ToolProviderResult } from '@deepseek-ai/dsh-system-prompt'
|
||||
@@ -306,7 +306,7 @@ export interface ToolRunContext extends ToolExecution {
|
||||
* the agent loop. Contexts retain their individual source and metadata and
|
||||
* are emitted in call order.
|
||||
*/
|
||||
deferContext(context: HookContext): void
|
||||
deferContext(context: AdditionalContext): void
|
||||
/** Mark a successful final result as terminal for the current agent turn. */
|
||||
concludeTurn(): void
|
||||
}
|
||||
@@ -440,7 +440,7 @@ export interface ToolExecutionSuccess {
|
||||
readonly content: ContentBlock[]
|
||||
readonly error?: never
|
||||
readonly meta?: JsonValue
|
||||
readonly additionalContexts?: HookContext[]
|
||||
readonly additionalContexts?: AdditionalContext[]
|
||||
/** The agent loop stops after committing this successful result batch. */
|
||||
readonly concludesTurn?: true
|
||||
}
|
||||
@@ -452,7 +452,7 @@ export interface ToolExecutionFailure {
|
||||
readonly value?: never
|
||||
readonly content: ContentBlock[]
|
||||
readonly meta?: JsonValue
|
||||
readonly additionalContexts?: HookContext[]
|
||||
readonly additionalContexts?: AdditionalContext[]
|
||||
readonly concludesTurn?: never
|
||||
}
|
||||
|
||||
@@ -475,9 +475,9 @@ export type PreToolDecision =
|
||||
* next request, or block by turning corrective feedback into an error result.
|
||||
*/
|
||||
export type PostToolDecision =
|
||||
| { kind: 'accept'; content?: ContentBlock[]; value?: never; additionalContexts?: HookContext[] }
|
||||
| { kind: 'accept'; value: JsonValue; content?: never; additionalContexts?: HookContext[] }
|
||||
| { kind: 'block'; feedback: ContentBlock[]; additionalContexts?: HookContext[] }
|
||||
| { kind: 'accept'; content?: ContentBlock[]; value?: never; additionalContexts?: AdditionalContext[] }
|
||||
| { kind: 'accept'; value: JsonValue; content?: never; additionalContexts?: AdditionalContext[] }
|
||||
| { kind: 'block'; feedback: ContentBlock[]; additionalContexts?: AdditionalContext[] }
|
||||
|
||||
/**
|
||||
* Best-effort human-readable message from an arbitrary thrown value: Error
|
||||
@@ -652,7 +652,7 @@ export class ToolRegistry extends Service {
|
||||
}
|
||||
|
||||
/** Context deferred by a running tool body, keyed by its scheduler-owned execution. */
|
||||
private deferredContexts = new WeakMap<ToolRunContext, HookContext[]>()
|
||||
private deferredContexts = new WeakMap<ToolRunContext, AdditionalContext[]>()
|
||||
/** Successful executions whose tool body declared the current turn complete. */
|
||||
private concludingExecutions = new WeakSet<ToolExecution>()
|
||||
/** Enclosing transport tokens marked terminal by a successful nested call. */
|
||||
@@ -969,7 +969,7 @@ export class ToolRegistry extends Service {
|
||||
}
|
||||
|
||||
private createExecution(exec: ToolExecutionInput): ScheduledToolPreparation | { kind: 'ready'; exec: MutableToolRunContext } {
|
||||
const deferredContexts: HookContext[] = []
|
||||
const deferredContexts: AdditionalContext[] = []
|
||||
const token = createExecutionToken()
|
||||
const callId = exec.callId
|
||||
const name = exec.name
|
||||
@@ -987,7 +987,7 @@ export class ToolRegistry extends Service {
|
||||
signal,
|
||||
...agent !== undefined ? { agent } : {},
|
||||
...parent !== undefined ? { parent } : {},
|
||||
deferContext(context: HookContext): void {
|
||||
deferContext(context: AdditionalContext): void {
|
||||
deferredContexts.push(context)
|
||||
},
|
||||
concludeTurn(): void {
|
||||
|
||||
Reference in New Issue
Block a user