@downcity/agent
Agent、AgentSessions、Session 与执行内核。
@downcity/agent 提供 Agent、AgentSessions、Session 与执行内核。City 组合根和 RemoteAgent 由 @downcity/city 提供。
Agent 不持有 Plugin;City 负责 Plugin 唯一实例、配置投影与生命周期,Plugin 作者协议位于
@downcity/city/plugin。Workspace 资源由 @downcity/city 提供。
const city = new City({ workspaces: [workspace] });
const agent = new Agent({ id: "assistant" });
city.agents.add(agent);
const session = await agent.sessions.create({ workspace });
const turn = await session.prompt({ query: "你好" });
const result = await turn.finished;使用 Plugin 前应先通过 CityOptions.plugins 或 city.plugins.add() 向 City 添加实例;
city.agents.add() 只登记 Agent,City 的全部 Plugin 会自动对它可用。
读取每轮文件改动
绑定 Workspace 的 Session 会把当前 Turn 通过内置结构化 write、edit 工具成功提交的文件编辑
写入最后一条 Assistant Message。Diff 直接来自文件工具的原子修改事实,不扫描 Turn 首尾工作树,
因此 Shell、脚本、外部编辑器和其他 Session 的修改不会进入该结果;非 Git Workspace 同样支持。
文件编辑使用 data-session-turn-file-diff data part 持久化,包含逐文件状态、增删行数和 unified diff。
可以使用公开解析函数安全读取:
import { read_session_turn_file_diff_data } from "@downcity/agent/session";
const page = await session.messages();
for (const message of page.items) {
if (message.type !== "assistant") continue;
for (const part of message.parts) {
if (part.type !== "data") continue;
const file_diff = read_session_turn_file_diff_data(part);
if (file_diff) console.log(file_diff.files, file_diff.additions, file_diff.deletions);
}
}Group 与 GroupSession
Group 是具有集体主体性的协作主体,拥有自己的 model 和 GroupSession 集合。Group.model 专门用于理解群聊意图并选择投递成员;默认使用 AI 调度。调度由 GroupSession 自身拥有,负责根据 Group 目标、当前对话和成员的 id/name/description 计算有限阶段计划,并统一处理调度 Turn 排队、持久化和停止。Agent 的 description 是供展示和语义选择使用的能力简介,不会替代决定 Agent 行为的 instruction。AI 调度在同一 Turn 内通过强制的 dispatch_group tool call 提交计划;如果模型先返回普通文本或无效参数,调度策略会把协议错误交还模型纠正,仍未获得有效调用时才记录明确失败。系统不会自动切换到另一套调度规则;如需自定义行为,应显式提供 dispatch_strategy,并让异步策略响应输入中的 abort_signal。成员执行仍使用成员 Agent 的 AgentSession,不会把私有上下文、Plugin
和工具调用复制进群聊历史。GroupSession 的 prompt() 只负责立即写入用户消息并启动异步调度;多个调用可以同时进入。首条用户消息落盘后,GroupSession 会使用 Group.model 异步生成短标题并写入自身 meta.json,不会阻塞调度。标题是独立的 canonical metadata,preview_text 仍只表示最后一条消息摘要;后续消息不会改写标题。GroupSession 只运行一个全局 auto dispatch,在当前成员执行完成后统一判断下一步;消息、标题和 Group/成员运行态统一通过
subscribe() 订阅,其中标题事件为 { type: "title", title }。
dispatch_group 提交 reason、stages 和 next。stages 按顺序执行,同一阶段的 assignments 并行执行;每个 assignment 都包含成员 ID 和该成员要直接完成的具体任务。例如:
{
"reason": "先形成方案,再基于方案做质量审查",
"stages": [
{
"assignments": [
{ "member_id": "architect", "instruction": "设计实现方案并说明关键取舍" }
]
},
{
"assignments": [
{ "member_id": "reviewer", "instruction": "基于上一阶段方案识别风险并给出结论" }
]
}
],
"next": "stop"
}同一个成员可以出现在不同阶段,但同一阶段内不能重复。已知的连续步骤应一次放入多个 stages;next: "continue" 只用于下一步成员选择必须依赖本轮真实输出的场景。当前对话已经满足用户意图时使用空 stages 和 next: "stop";所列 stages 执行后即可完成时也使用 stop。调度次数上限与重复路径检测只是异常熔断,不是正常停止条件。
调度器是“谁执行什么任务、何时停止”的唯一决策边界。成员一旦收到 assignment,就直接完成该任务,不会在自己的 AgentSession 中再次判断是否需要发言。被选中的成员未生成有效文本时,GroupSession 会把它记录为执行失败,并停止后续阶段。
自定义 DispatchStrategy 接收 group、trigger、current_message、history、pending_messages、成员画像和 abort_signal。其中 history 已排除本批新增消息,成员画像只包含 agent_id/name/description,不会暴露完整 Agent:
const dispatch_strategy: DispatchStrategy = {
decide_dispatch({ current_message, members }) {
return {
reason: `由 ${members[0].name} 处理当前请求`,
stages: [{
stage_id: `reply-${current_message.id}`,
assignments: [{
member_id: members[0].agent_id,
instruction: "直接完成用户当前请求",
}],
}],
terminal: true,
};
},
};const lead = new Agent({ id: "lead", name: "负责人", description: "负责拆解目标并形成实现方案", model });
const reviewer = new Agent({ id: "reviewer", name: "审查员", description: "负责审查质量与风险", model });
city.agents.add(lead);
city.agents.add(reviewer);
const group = new Group({
id: "delivery-team",
model,
members: [lead, reviewer],
});
city.groups.add(group);
const group_session = await group.sessions.create({ workspace });
const result = await group_session.prompt({ query: "请完成实现并让 reviewer 审查改动" });
console.log(result.turn_id, result.success);
const sessions = await group.sessions.list();
const restored = await group.sessions.get(sessions[0].id, { workspace });
await restored.rename("交付评审");Workspace 仍然是 City 提供的资源。Workspace 只注入具体的 GroupSession,再由 GroupSession 注入成员 AgentSession;它不属于 Agent、
Group,也不决定成员如何协作。加入带持久化 Storage 的 City 后,GroupSession 会保存自己的 Workspace ID、消息摘要、成员 AgentSession ID 映射、传播检查点,以及 dispatch/turns.jsonl 中的调度 Turn 日志;
进程中断后会根据已落盘阶段重新执行尚未完成的 user dispatch,或继续尚未完成的 auto dispatch。调用 group_session.stop() 会同时中断当前调度模型调用、取消排队调度并停止成员 AgentSession;主动停止不会写入调度失败消息;
sessions.list() 的摘要直接返回持久化 title 与 preview_text;sessions.get(id) 才恢复完整消息和成员会话引用。调用 group_session.rename(title) 会持久化手动标题并停止后台自动标题覆盖它。