refactor(agent-loop): rename private input boundary
This commit is contained in:
@@ -52,7 +52,7 @@ Configured agents start automatically. A model call requires both `provider` and
|
||||
|
||||
The concrete `ReactLoopAgent` adapter, its `Inbox`, `runLoop`, and instance-bound publication/start controls are package-internal. The package root exports only the plugin/service/config contract, and the package exports map exposes no `./src/*` escape hatch; lifecycle owners create agents through `ctx.agents` rather than naming, constructing, or starting driver internals. One prepared session can be claimed by only one concrete driver, and everything observable happens through session events and the `agent/*` event taxonomy.
|
||||
|
||||
`ReactLoopAgent` maps the public `send()`/`queue()`/`steer()`/`inject()` intents onto native-private `#acceptDelivery`. Each public method resolves every optional field before the private mechanism receives mandatory content, source, contexts, metadata, target, and wakeup facts; no configurable delivery primitive crosses the package seam. `send()` and `queue()` join the ordinary FIFO, respectively waking or leaving an idle driver parked. If claimed, an ordinary item is the sole message in its turn, and its contexts are the prompt waterfall's default additional contexts that materialize only after admission. Absent or `separate` placement appends an independent injected `user/message`; `prompt-prefix` placement bakes the context, the stable `## My request:` delimiter, and effective request into one `user/message`, whose model-hidden envelope retains display content and context descriptors. The waterfall's returned allow is authoritative, so a listener wrapping `next()` preserves downstream `content` and `additionalContexts` unless it intentionally replaces them. A successor waits for the preceding ordinary turn's checkpoint to settle, while cancellation, disposal, a prompt block, or a pre-start failure may drop its contexts with the message. Running `steer()` enters the same record shape in the steering FIFO without dispatching `agent/prompt-submit`; its next checkpoint applies the same separate-or-prefix placement to `steering/message`, while policy can still stop before another step. Steering left after turn close and its checkpoint becomes later queued input with contexts intact unless terminal turn policy, cancellation, or disposal discards it. `inject()` accepts no attached contexts, bypasses both FIFOs, and appends durable context directly: an open-turn injection defers in a FIFO while the current step executes assistant tool calls (successful batches place it after all results, interrupted batches drain it before turn close), and an idle injection wraps a one-shot `injection` turn. Every FIFO enqueue publishes `agent/inbox/enqueue`; the driver's claims publish `agent/inbox/dequeue`, and `cancel()` without `keepInbox` publishes `agent/inbox/discard`. Malformed data throws before enqueue or append.
|
||||
`ReactLoopAgent` maps the public `send()`/`queue()`/`steer()`/`inject()` intents onto native-private `#acceptInput`. Each public method resolves every optional field before the private mechanism receives mandatory content, source, contexts, metadata, target, and wakeup facts; no configurable delivery primitive crosses the package seam. `send()` and `queue()` join the ordinary FIFO, respectively waking or leaving an idle driver parked. If claimed, an ordinary item is the sole message in its turn, and its contexts are the prompt waterfall's default additional contexts that materialize only after admission. Absent or `separate` placement appends an independent injected `user/message`; `prompt-prefix` placement bakes the context, the stable `## My request:` delimiter, and effective request into one `user/message`, whose model-hidden envelope retains display content and context descriptors. The waterfall's returned allow is authoritative, so a listener wrapping `next()` preserves downstream `content` and `additionalContexts` unless it intentionally replaces them. A successor waits for the preceding ordinary turn's checkpoint to settle, while cancellation, disposal, a prompt block, or a pre-start failure may drop its contexts with the message. Running `steer()` enters the same record shape in the steering FIFO without dispatching `agent/prompt-submit`; its next checkpoint applies the same separate-or-prefix placement to `steering/message`, while policy can still stop before another step. Steering left after turn close and its checkpoint becomes later queued input with contexts intact unless terminal turn policy, cancellation, or disposal discards it. `inject()` accepts no attached contexts, bypasses both FIFOs, and appends durable context directly: an open-turn injection defers in a FIFO while the current step executes assistant tool calls (successful batches place it after all results, interrupted batches drain it before turn close), and an idle injection wraps a one-shot `injection` turn. Every FIFO enqueue publishes `agent/inbox/enqueue`; the driver's claims publish `agent/inbox/dequeue`, and `cancel()` without `keepInbox` publishes `agent/inbox/discard`. Malformed data throws before enqueue or append.
|
||||
|
||||
### Loop lifecycle (`loop.ts`)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const bindContext = Symbol('dsh.agent-loop.bind-context')
|
||||
const publishAgent = Symbol('dsh.agent-loop.publish-agent')
|
||||
|
||||
/** Fully resolved input accepted only by the concrete driver's private delivery mechanism. */
|
||||
type ResolvedDelivery = {
|
||||
type ResolvedAgentInput = {
|
||||
content: ContentBlock[]
|
||||
source: MessageSource
|
||||
meta: JsonValue | undefined
|
||||
@@ -215,8 +215,8 @@ export class ReactLoopAgent implements Agent {
|
||||
* materialization reads every nested field once; deep freeze prevents later
|
||||
* caller mutation before an inbox or deferred-injection queue drains it.
|
||||
*/
|
||||
private snapshotMessage(id: AgentMessageId, delivery: ResolvedDelivery): InboxMessage {
|
||||
const { content, source, contexts, wakeup, meta } = delivery
|
||||
private snapshotMessage(id: AgentMessageId, input: ResolvedAgentInput): InboxMessage {
|
||||
const { content, source, contexts, wakeup, meta } = input
|
||||
const accepted = snapshotJsonValue({
|
||||
id, content, source, contexts, wakeup,
|
||||
...meta !== undefined ? { meta } : {},
|
||||
@@ -241,17 +241,17 @@ export class ReactLoopAgent implements Agent {
|
||||
if (this._status === 'disposed') throw new Error(`agent "${this.id}" is disposed`)
|
||||
}
|
||||
|
||||
/** Accept one fully resolved intent through the concrete driver's private routing matrix. */
|
||||
#acceptDelivery(delivery: ResolvedDelivery): AgentMessageId {
|
||||
/** Accept one fully resolved agent input through the concrete driver's private routing matrix. */
|
||||
#acceptInput(input: ResolvedAgentInput): AgentMessageId {
|
||||
this.assertNotDisposed()
|
||||
const id = AgentMessageId(randomUUID())
|
||||
const { target, wakeup } = delivery
|
||||
const { target, wakeup } = input
|
||||
// next-step/no-wakeup is injection: durable context without running the model.
|
||||
if (target === 'next-step' && !wakeup) { this.injectContext(delivery); return id }
|
||||
if (target === 'next-step' && !wakeup) { this.injectContext(input); return id }
|
||||
// next-step/wakeup is steering into the running turn; idle falls back to a
|
||||
// waking ordinary turn (there is no active turn to attach to).
|
||||
const steering = target === 'next-step' && this._status === 'running'
|
||||
const accepted = this.snapshotMessage(id, delivery)
|
||||
const accepted = this.snapshotMessage(id, input)
|
||||
if (steering) {
|
||||
this.#inbox.steer(accepted)
|
||||
} else {
|
||||
@@ -262,7 +262,7 @@ export class ReactLoopAgent implements Agent {
|
||||
}
|
||||
|
||||
send(content: ContentBlock[], options?: SendOptions): AgentMessageId {
|
||||
return this.#acceptDelivery({
|
||||
return this.#acceptInput({
|
||||
content,
|
||||
target: 'next-turn',
|
||||
wakeup: true,
|
||||
@@ -273,7 +273,7 @@ export class ReactLoopAgent implements Agent {
|
||||
}
|
||||
|
||||
queue(content: ContentBlock[], options?: SendOptions): AgentMessageId {
|
||||
return this.#acceptDelivery({
|
||||
return this.#acceptInput({
|
||||
content,
|
||||
target: 'next-turn',
|
||||
wakeup: false,
|
||||
@@ -284,7 +284,7 @@ export class ReactLoopAgent implements Agent {
|
||||
}
|
||||
|
||||
steer(content: ContentBlock[], options?: SendOptions): AgentMessageId {
|
||||
return this.#acceptDelivery({
|
||||
return this.#acceptInput({
|
||||
content,
|
||||
target: 'next-step',
|
||||
wakeup: true,
|
||||
@@ -295,7 +295,7 @@ export class ReactLoopAgent implements Agent {
|
||||
}
|
||||
|
||||
inject(content: ContentBlock[], options?: InjectOptions): AgentMessageId {
|
||||
return this.#acceptDelivery({
|
||||
return this.#acceptInput({
|
||||
content,
|
||||
target: 'next-step',
|
||||
wakeup: false,
|
||||
@@ -306,8 +306,8 @@ export class ReactLoopAgent implements Agent {
|
||||
}
|
||||
|
||||
/** The `next-step`/no-wakeup injection path: durable context, no FIFO, no run. */
|
||||
private injectContext(delivery: Extract<ResolvedDelivery, { target: 'next-step'; wakeup: false }>): void {
|
||||
const { content, source, meta } = delivery
|
||||
private injectContext(input: Extract<ResolvedAgentInput, { target: 'next-step'; wakeup: false }>): void {
|
||||
const { content, source, meta } = input
|
||||
const context = {
|
||||
content,
|
||||
source,
|
||||
|
||||
Reference in New Issue
Block a user