RFC: AgentFn<Opt> — Typed Agent Boundary + createAgentAdapter Bridge #252

Closed
opened 2026-05-14 07:57:39 +00:00 by xiaoju · 0 comments
Owner

背景

当前 adapter 层的核心抽象:

// types.ts (workflow-protocol)
type AdapterFn = <T>(prompt: string, schema: z.ZodType<T>) => RoleFn<T>;
type RoleFn<T> = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise<RoleResult<T>>;

没有 "Agent" 的正式类型定义。Agent 的行为散落在各个 adapter 实现里(createTextAdapterTextProducerFnworkflowAdapter 的内联逻辑)。

问题:不同 agent 有不同的结构化配置需求(Cursor 需要 workspace,LLM 只需要 prompt),但没有类型层面的表达。Cursor adapter 被迫用 runtime.extract 在运行时从 context 里"猜" workspace。

核心概念澄清

概念 职责 签名
AgentFn Agent 的本质:接收 context,产出 text。Options 是 agent 特有的结构化配置 <Opt>(ctx: ThreadContext, options: Opt) => Promise<string>
AdapterFn Role 的泛化模板:给定 prompt + meta schema,返回可执行的 RoleFn <T>(prompt: string, schema: ZodType<T>) => RoleFn<T>
createAgentAdapter 桥接函数:Agent → Adapter。extract 函数负责从 (ctx, prompt, runtime) 中提取 Options <Opt>(agent: AgentFn<Opt>, extract: ExtractOptionsFn<Opt>) => AdapterFn

设计

1. AgentFn 类型定义

workflow-protocol/src/types.ts 新增:

/**
 * Core agent function. Input is always ThreadContext, output is always string.
 * `Opt` captures agent-specific structured options.
 * Agents with no extra options use `AgentFn` (Opt defaults to void).
 */
export type AgentFn<Opt = void> = Opt extends void
  ? (ctx: ThreadContext) => Promise<string>
  : (ctx: ThreadContext, options: Opt) => Promise<string>;

workflow-runtimetypes.tsindex.ts 中 re-export AgentFn

2. createAgentAdapter 桥接函数

workflow-util-agent/src/create-agent-adapter.ts 新增:

export type ExtractOptionsFn<Opt> = (
  ctx: ThreadContext,
  prompt: string,
  runtime: WorkflowRuntime,
) => Promise<Opt>;

/**
 * Bridges AgentFn<Opt> to AdapterFn.
 *
 * 1. extract(ctx, prompt, runtime) → Opt
 * 2. agent(ctx, options) → raw string
 * 3. Store raw string in CAS
 * 4. runtime.extract(schema, contentHash) → typed meta T
 */
export function createAgentAdapter<Opt>(
  agent: AgentFn<Opt>,
  extract: ExtractOptionsFn<Opt>,
): AdapterFn {
  return <T>(prompt: string, schema: z.ZodType<T>) => {
    return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
      const options = await extract(ctx, prompt, runtime);
      const raw = await agent(ctx, options);
      const contentHash = await putContentNodeWithRefs(runtime.cas, raw, []);
      const extracted = await runtime.extract(
        schema as z.ZodType<Record<string, unknown>>,
        contentHash,
      );
      return { meta: extracted.meta as T, childThread: null };
    };
  };
}

对于 AgentFn<void> 提供便利重载:

export function createSimpleAgentAdapter(agent: AgentFn<void>): AdapterFn {
  return createAgentAdapter(agent, async () => undefined as unknown as void);
}

workflow-util-agent/src/index.ts 中 export createAgentAdaptercreateSimpleAgentAdapterExtractOptionsFn

3. 不动的部分

  • AdapterFn 签名不变
  • AdapterBinding 不变
  • TextProducerFn / createTextAdapter 不变 — 它们是 AdapterFn 的一个便利工厂实现,现在 createAgentAdapter 是另一条路径
  • 现有 adapter 实现(LLM、Cursor、Hermes、workflowAdapter)本次不迁移,Phase 2 再做

4. tsconfig 修复(顺带)

  • workflow-cas/tsconfig.json"composite": true(被 workflow-agent-cursor 和 workflow-util-agent 引用,需要 composite)
  • workflow-agent-cursor/tsconfig.json 的 references 加 { "path": "../workflow-cas" }(extract-workspace.ts 引用了 @uncaged/workflow-cas 但 tsconfig 没���明)

Phase 拆分

Phase 1: AgentFn + createAgentAdapter(本 issue)

  • workflow-protocolAgentFn<Opt> 类型
  • workflow-runtime re-export
  • workflow-util-agentcreateAgentAdapter + createSimpleAgentAdapter
  • tsconfig 修复
  • 全量构建通过,现有测试不受影响

Phase 2: 现有 adapter 迁移(后续 issue)

  • LLM adapter → createAgentAdapter
  • Cursor adapter → createAgentAdapter<{ workspace: string }> + 去掉 ad-hoc extract
  • Hermes adapter → createAgentAdapter

完成标准

  • Phase 1 构建通过 (bun run build)
  • 现有测试通过 (bun run test)
  • biome check 对改动文件无新增 error
## 背景 当前 adapter 层的核心抽象: ```typescript // types.ts (workflow-protocol) type AdapterFn = <T>(prompt: string, schema: z.ZodType<T>) => RoleFn<T>; type RoleFn<T> = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise<RoleResult<T>>; ``` 没有 "Agent" 的正式类型定义。Agent 的行为散落在各个 adapter 实现里(`createTextAdapter` 的 `TextProducerFn`、`workflowAdapter` 的内联逻辑)。 问题:不同 agent 有不同的结构化配置需求(Cursor 需要 workspace,LLM 只需要 prompt),但没有类型层面的表达。Cursor adapter 被迫用 `runtime.extract` 在运行时从 context 里"猜" workspace。 ## 核心概念澄清 | 概念 | 职责 | 签名 | |------|------|------| | **AgentFn** | Agent 的本质:接收 context,产出 text。Options 是 agent 特有的结构化配置 | `<Opt>(ctx: ThreadContext, options: Opt) => Promise<string>` | | **AdapterFn** | Role 的泛化模板:给定 prompt + meta schema,返回可执行的 RoleFn | `<T>(prompt: string, schema: ZodType<T>) => RoleFn<T>` | | **createAgentAdapter** | 桥接函数:Agent → Adapter。extract 函数负责从 (ctx, prompt, runtime) 中提取 Options | `<Opt>(agent: AgentFn<Opt>, extract: ExtractOptionsFn<Opt>) => AdapterFn` | ## 设计 ### 1. AgentFn 类型定义 在 `workflow-protocol/src/types.ts` 新增: ```typescript /** * Core agent function. Input is always ThreadContext, output is always string. * `Opt` captures agent-specific structured options. * Agents with no extra options use `AgentFn` (Opt defaults to void). */ export type AgentFn<Opt = void> = Opt extends void ? (ctx: ThreadContext) => Promise<string> : (ctx: ThreadContext, options: Opt) => Promise<string>; ``` 在 `workflow-runtime` 的 `types.ts` 和 `index.ts` 中 re-export `AgentFn`。 ### 2. createAgentAdapter 桥接函数 在 `workflow-util-agent/src/create-agent-adapter.ts` 新增: ```typescript export type ExtractOptionsFn<Opt> = ( ctx: ThreadContext, prompt: string, runtime: WorkflowRuntime, ) => Promise<Opt>; /** * Bridges AgentFn<Opt> to AdapterFn. * * 1. extract(ctx, prompt, runtime) → Opt * 2. agent(ctx, options) → raw string * 3. Store raw string in CAS * 4. runtime.extract(schema, contentHash) → typed meta T */ export function createAgentAdapter<Opt>( agent: AgentFn<Opt>, extract: ExtractOptionsFn<Opt>, ): AdapterFn { return <T>(prompt: string, schema: z.ZodType<T>) => { return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise<RoleResult<T>> => { const options = await extract(ctx, prompt, runtime); const raw = await agent(ctx, options); const contentHash = await putContentNodeWithRefs(runtime.cas, raw, []); const extracted = await runtime.extract( schema as z.ZodType<Record<string, unknown>>, contentHash, ); return { meta: extracted.meta as T, childThread: null }; }; }; } ``` 对于 `AgentFn<void>` 提供便利重载: ```typescript export function createSimpleAgentAdapter(agent: AgentFn<void>): AdapterFn { return createAgentAdapter(agent, async () => undefined as unknown as void); } ``` 在 `workflow-util-agent/src/index.ts` 中 export `createAgentAdapter`、`createSimpleAgentAdapter`、`ExtractOptionsFn`。 ### 3. 不动的部分 - `AdapterFn` 签名不变 - `AdapterBinding` 不变 - `TextProducerFn` / `createTextAdapter` 不变 — 它们是 `AdapterFn` 的一个便利工厂实现,现在 `createAgentAdapter` 是另一条路径 - 现有 adapter 实现(LLM、Cursor、Hermes、workflowAdapter)本次不迁移,Phase 2 再做 ### 4. tsconfig 修复(顺带) - `workflow-cas/tsconfig.json` 加 `"composite": true`(被 workflow-agent-cursor 和 workflow-util-agent 引用,需要 composite) - `workflow-agent-cursor/tsconfig.json` 的 references 加 `{ "path": "../workflow-cas" }`(extract-workspace.ts 引用了 `@uncaged/workflow-cas` 但 tsconfig 没���明) ## Phase 拆分 ### Phase 1: AgentFn + createAgentAdapter(本 issue) - `workflow-protocol` 加 `AgentFn<Opt>` 类型 - `workflow-runtime` re-export - `workflow-util-agent` 加 `createAgentAdapter` + `createSimpleAgentAdapter` - tsconfig 修复 - 全量构建通过,现有测试不受影响 ### Phase 2: 现有 adapter 迁移(后续 issue) - LLM adapter → `createAgentAdapter` - Cursor adapter → `createAgentAdapter<{ workspace: string }>` + 去掉 ad-hoc extract - Hermes adapter → `createAgentAdapter` ## 完成标准 - [ ] Phase 1 构建通过 (`bun run build`) - [ ] 现有测试通过 (`bun run test`) - [ ] biome check 对改动文件无新增 error
xiaoju changed title from RFC: Generic TextProducerFn — Schema-Driven Input Specialization to RFC: AgentFn<Opt> — Typed Agent Boundary + createAgentAdapter Bridge 2026-05-14 08:34:42 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#252