Browse Source

refactor(agent-loop): rename private input boundary

Tianyi Cui 2 months ago
parent
commit
1595f4c851

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-24-intent-named-agent-delivery.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-2026-07-24-intent-named-agent-delivery.md: df58435e5a59dbc5e3c2ba17c624728533ed3b96
-2026-07-24-intent-named-agent-delivery.zh.md: 78cf25870d3f220373692caf943870b920fb151e
+2026-07-24-intent-named-agent-delivery.md: 62a05ed7601ca3dd2f05fae609341ab3fa37671a
+2026-07-24-intent-named-agent-delivery.zh.md: e559b106ca54f901a18f2936142770d6306ef757

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-24-intent-named-agent-delivery.md

@@ -21,7 +21,7 @@ Sharing helper implementations through an abstract `Agent` class also makes the
 
 `send`, `queue`, and `steer` accept `SendOptions`; `inject` accepts `InjectOptions`, which omits attached contexts because injection has no inbox item to own them. `followup` is absent: ordinary `send` already names the established common operation, and “follow-up” is false for a session's first message.
 
-`ReactLoopAgent` resolves each public call into one module-private `ResolvedDelivery` and passes it to native-private `#acceptDelivery`. Every internal field is mandatory: content, source, contexts, metadata (possibly `undefined`), target, and wakeup. Injection resolves contexts to the empty tuple. The private name says what the synchronous boundary guarantees: acceptance can still lead to later dequeue, discard, or durable injection rather than eventual delivery.
+`ReactLoopAgent` resolves each public call into one module-private `ResolvedAgentInput` and passes it to native-private `#acceptInput`. Every internal field is mandatory: content, source, contexts, metadata (possibly `undefined`), target, and wakeup. Injection resolves contexts to the empty tuple. The private name says what the synchronous boundary guarantees: acceptance can still lead to later dequeue, discard, or durable injection rather than eventual delivery.
 
 The target/wakeup matrix remains an implementation mechanism in `dsh-agent-loop`. It is not exported, protected, or represented by a base class. With one concrete adapter, a subclass delivery seam would be hypothetical; callers and tests use the same public `Agent` interface.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-24-intent-named-agent-delivery.zh.md

@@ -21,7 +21,7 @@ Status: implemented
 
 `send`、`queue` 和 `steer` 接收 `SendOptions`;`inject` 接收 `InjectOptions`,后者不包含附加上下文,因为注入没有 inbox 项来拥有它们。接口不提供 `followup`:普通 `send` 已经为既有的常见操作命名,而「follow-up」不适用于会话的第一条消息。
 
-`ReactLoopAgent` 把每次公开调用解析为一个模块私有的 `ResolvedDelivery`,再将其传给原生私有的 `#acceptDelivery`。每个内部字段都必须提供:内容、来源、上下文、元数据(可以是 `undefined`)、目标和唤醒标志。注入会把上下文解析为空元组。这个私有名称说明同步边界所保证的事实:接受之后,工作仍可能在稍后出队、被丢弃或持久注入,而不是最终必然送达。
+`ReactLoopAgent` 把每次公开调用解析为一个模块私有的 `ResolvedAgentInput`,再将其传给原生私有的 `#acceptInput`。每个内部字段都必须提供:内容、来源、上下文、元数据(可以是 `undefined`)、目标和唤醒标志。注入会把上下文解析为空元组。这个私有名称说明同步边界所保证的事实:接受之后,工作仍可能在稍后出队、被丢弃或持久注入,而不是最终必然送达。
 
 target/wakeup 矩阵仍是 `dsh-agent-loop` 中的实现机制。它不会导出,不是 protected 成员,也不由基类表示。只有一个具体适配器时,子类投递 seam 只是假想的;调用方和测试使用同一个公开 `Agent` 接口。
 

File diff suppressed because it is too large
+ 0 - 0
packages/core/agent-loop/README.md


+ 14 - 14
packages/core/agent-loop/src/agent.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,

Some files were not shown because too many files changed in this diff