RFC: Generic AgentFn — Schema-Driven Input Specialization #327

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

背景

当前 AgentFn 的签名是:

type AgentFn = (ctx: ThreadContext, systemPrompt: string) => Promise<string>;

这假设了所有 agent 的工作输入都是 string(prompt),但现实中不同 agent 有结构化输入需求:

  • Cursor Agent: { cwd, mode, model }
  • LLM Agent: string(prompt)
  • API Agent: { endpoint, payload }

从类型论看,函数参数的泛化是函数类型的特化(逆变)。AgentFn 强制 I = string 丢失了表达力。现在 adapter-cursor_ctx 被直接忽略、cwd 硬编码 process.cwd(),就是这个 mismatch 的直接症状。

核心设计

1. AgentFn 泛型化

type AgentFn<I> = (ctx: ThreadContext, input: I) => Promise<string>;
  • ctx 保留:thread history、abort signal 等运行时上下文,所有 agent 都需要
  • input 泛型化:每个 agent 声明自己需要的结构化输入

2. Schema 驱动的 Adapt 层

Adapter 不需要泛型 — 给定 schema,extract 逻辑是通用的:

// schema 定义了 agent 需要什么
// extractFromContext 是通用转换器,根据 schema 从上游数据提取/映射
function adaptAgent<I>(schema: ZodType<I>, agent: AgentFn<I>): AgentFn<string> {
  return (ctx, raw) => agent(ctx, extractFromContext(raw, schema));
}

3. 具体 Agent 只声明 schema

// Cursor
const cursorInputSchema = z.object({
  cwd: z.string(),
  mode: z.enum(['plan', 'ask', 'default']),
  model: z.string(),
});

// LLM — input 就是 string,最简形式
const llmInputSchema = z.string();

Phase 拆分

Phase 1: 类型层改造

  • AgentFn<I> 泛型化
  • adaptAgent 函数 + extractFromContext 通用实现
  • 现有 AgentFn 消费者迁移为 AgentFn<string>(向后兼容默认值)
  • Testing issue: 待创建

Phase 2: Adapter 迁移

  • adapter-cursor 用 schema 重写,去掉 _ctx hack
  • adapter-hermes 迁移
  • createRole (workflow-utils) 适配新签名
  • Testing issue: 待创建

Phase 3: 端到端验证

  • 现有 workflow (solve-issue, extract-knowledge) 通过构建和测试
  • 新写一个非 string 输入的 agent adapter 验证泛型路径
  • Testing issue: 待创建

完成标准

  • 所有 Phase 的 testing issue 已 close
  • CI 构建通过
  • 现有 workflow 不受影响
## 背景 当前 `AgentFn` 的签名是: ```typescript type AgentFn = (ctx: ThreadContext, systemPrompt: string) => Promise<string>; ``` 这假设了所有 agent 的工作输入都是 `string`(prompt),但现实中不同 agent 有结构化输入需求: - **Cursor Agent**: `{ cwd, mode, model }` - **LLM Agent**: `string`(prompt) - **API Agent**: `{ endpoint, payload }` 从类型论看,函数参数的泛化是函数类型的特化(逆变)。`AgentFn` 强制 `I = string` 丢失了表达力。现在 `adapter-cursor` 里 `_ctx` 被直接忽略、`cwd` 硬编码 `process.cwd()`,就是这个 mismatch 的直接症状。 ## 核心设计 ### 1. AgentFn 泛型化 ```typescript type AgentFn<I> = (ctx: ThreadContext, input: I) => Promise<string>; ``` - `ctx` 保留:thread history、abort signal 等运行时上下文,所有 agent 都需要 - `input` 泛型化:每个 agent 声明自己需要的结构化输入 ### 2. Schema 驱动的 Adapt 层 Adapter 不需要泛型 — 给定 schema,extract 逻辑是通用的: ```typescript // schema 定义了 agent 需要什么 // extractFromContext 是通用转换器,根据 schema 从上游数据提取/映射 function adaptAgent<I>(schema: ZodType<I>, agent: AgentFn<I>): AgentFn<string> { return (ctx, raw) => agent(ctx, extractFromContext(raw, schema)); } ``` ### 3. 具体 Agent 只声明 schema ```typescript // Cursor const cursorInputSchema = z.object({ cwd: z.string(), mode: z.enum(['plan', 'ask', 'default']), model: z.string(), }); // LLM — input 就是 string,最简形式 const llmInputSchema = z.string(); ``` ## Phase 拆分 ### Phase 1: 类型层改造 - `AgentFn<I>` 泛型化 - `adaptAgent` 函数 + `extractFromContext` 通用实现 - 现有 `AgentFn` 消费者迁移为 `AgentFn<string>`(向后兼容默认值) - Testing issue: 待创建 ### Phase 2: Adapter 迁移 - `adapter-cursor` 用 schema 重写,去掉 `_ctx` hack - `adapter-hermes` 迁移 - `createRole` (workflow-utils) 适配新签名 - Testing issue: 待创建 ### Phase 3: 端到端验证 - 现有 workflow (solve-issue, extract-knowledge) 通过构建和测试 - 新写一个非 string 输入的 agent adapter 验证泛型路径 - Testing issue: 待创建 ## 完成标准 - [ ] 所有 Phase 的 testing issue 已 close - [ ] CI 构建通过 - [ ] 现有 workflow 不受影响
This repo is archived. You cannot comment on issues.
No Label
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#327