
当我们谈论一个 Agent 系统的设计,三个角度几乎绕不开:
主流 Agent 框架在这三个维度上常见的做法是:把 Loop 写成固定的 ReAct 模板,把 Harness 做成 CLI 或 Server 包装,把 Hook 做成 LangGraph Edge 或 Tool Pre/Post Callback。这套范式在做"通用对话+工具调用"时很顺,但当任务本身具有强结构性(比如"从一句话生成一个完整的部门管理模块"),就会暴露三个问题:
Ooder 在最近一轮重构中,把原本的 NlpPipeline + NlpDesignOrchestrator + NlpProjectIntegrator 三轨硬编码流程,重新组织为基于 SceneGroup 的场景编排核。这篇文章会从 harness、loop、hook 三个角度逐层拆解这套设计。
Ooder 的 Harness 不是单一的进程外壳,而是一个由三层结构组成的承载体系:

图 1 · Ooder Agent Harness 的三层结构:宿主层、编排核、能力面
承担"接住外部请求、流式回写"的职责。Ooder 的宿主层有三种典型形态:
宿主层的核心动作只有一个:把异构入参翻译成 ScenarioContext,然后调用编排核。
// NlpGenerateController#generate
ScenarioContext context = new ScenarioContext(query, null, null);
context.putAccumulated("projectName", projectName);
context.putAccumulated("autoSave", autoSave);
context.putAccumulated("channelCode", channelCode);
ScenarioOrchestrationResult result = scenarioOrchestrator.orchestrate(context);编排核 ScenarioOrchestrator 是整个 Harness 的"大脑",由四个协作组件构成:
组件 | 职责 | 关键方法 |
|---|---|---|
Pattern Resolver | 把意图(systemType + buildLevel)映射到 6 种编排模式之一 | resolvePattern()· OrchestrationPattern.fromIntent() |
Group Activator | 根据模式决定哪些 SceneGroup 激活/跳过 | pattern.isGroupActive(groupId) |
Step Loop | 组内串行/并行执行 ScenarioStep,处理失败策略 | AbstractScenarioGroup.execute() |
Confirmation Hook | 当组结果标记 needConfirm 时暂停等待用户决策 | ConfirmationCallback.confirm() |
能力面是 Ooder 编排核的精髓所在:将4 类 88+ 个异构步骤统一适配为同一个 ScenarioStep 接口:
每个 Adapter 都不是简单的方法转发,而是承担两个关键职责:
public interface ScenarioStep {
String getStepId(); // 全局唯一标识
String getGroupId(); // 所属场景组(SG-UNDERSTAND…)
int getOrder(); // 组内执行顺序
boolean isEnabled(ScenarioContext ctx); // 动态启用判断
StepResult execute(ScenarioContext ctx, StepResult previousResult);
FailureStrategy getFailureStrategy(); // FATAL / RECOVERABLE / SKIPABLE
List<String> getRequiredCapabilities();
List<String> getProducedOutputs();
List<String> getRequiredInputs();
}当能力面被压平为同一个接口,编排核才有可能用 同一套 Loop + Hook 去驱动数十种异构能力 —— 这就是 SceneGroup 设计能跨越 NLP / DSL / 代码生成 / 编译四个维度的根本原因。
大多数 Agent 框架的 Loop 长这样:Plan → Tool → Observe → Plan' → …。Ooder 的 Loop 长这样:Resolve Pattern → Activate Groups → Run Group Sequence → Quality Loop → Integrate。

图 2 · 5 大 SceneGroup 编排序列、质量回路与编排模式激活矩阵
场景组 | 语义 | 包含步骤 | 关键 Order |
|---|---|---|---|
SG-UNDERSTAND | 理解用户意图与实体 | system_design / intent_classification / entity_extraction / entity_resolution | 5 / 10 ‖ 20 / 22 |
SG-DESIGN | 架构设计与四分离 | module_decomposition / four_separation / design_assembly | 25 / 4 / 5 |
SG-GENERATE | 配置生成与组件构建 | config_generation / A2uiSkill × 30 / Builder × 34 / design_bridge | 30 / 10‖20 / 40 |
SG-QUALITY | 四分离审计与场景校验 | llm_fallback / cls_audit / cls_validation | 35 / 45 / 48 |
SG-INTEGRATE | 项目集成与构建 | project_integration / module_integration / 6 步 BuildSPI | 50 / 55 / 61~66 |
同样是"创建一个部门管理",根据 LLM 推断出的 systemType 与 buildLevel,Loop 的形状会发生显著变化。这是 OrchestrationPattern.fromIntent() 的真正威力:
public static OrchestrationPattern fromIntent(String systemType, String buildLevel) {
if (systemType == null) return STANDARD;
switch (systemType) {
case "NAV_BASED": return NAV_BASED; // 全场景组激活
case "CHART_BASED": return CHART_BASED; // 跳过 DESIGN
case "SINGLE_MODULE": return SVG_BASED; // 跳过 DESIGN + QUALITY
default: // CRUD_BASED
if ("SIMPLE".equals(buildLevel)) return SIMPLE;
if ("STANDARD".equals(buildLevel)) return STANDARD;
return FULL;
}
}对照图 2 下半部分的激活矩阵:
这是 SceneGroup 与传统 Step DAG 最关键的差异:Loop 的形状不是预定义的 DAG,而是由意图推导出来的"模式"映射到固定的场景组序列。这种设计使得新增一种系统类型只需要新增一个 OrchestrationPattern 枚举值,而不是重画 DAG。
真正能体现 SceneGroup 是"Agent Loop"而不是"管道"的地方,是 QualityGroup 的内部回路:
// QualityGroup#execute
do {
result = super.execute(context);
loopCount++;
StepResult validation = context.getStepResult("cls_validation");
if (validation == null || validation.isSuccess()) break;
// D2 验证未通过 → 重置 D1/D2/D3 → 回到 GENERATE
context.clearStepResults("llm_fallback", "cls_audit", "cls_validation");
} while (loopCount < MAX_VALIDATION_LOOPS); // 最多 3 轮关键设计点: ScenarioContext.clearStepResults() 在清理 stepResults 的同时,同步清理 accumulatedData 中由该步骤写入的 key。这保证了重试时不会读到上一轮的脏数据 —— 这是审计阶段发现并修复的一个隐蔽 bug。
这个质量回路的语义和 ReAct 中的"Observe → Replan"是一致的:当观察结果不达标时,回退到生成阶段重做。但 Ooder 把它固化在 Group 内部,而不是暴露给 LLM 自由决策,从而让回路是有界、可验证、可观测的。
Hook 是 Ooder 编排核的"关节",决定了系统在 Loop 跑动时能被多深入地观察、多精细地控制。整套设计有四类 Hook,分布在 Harness 的不同层次。

图 3 · 四类 Hook 在编排核中的位置:宿主切面、内部观测、人机回路、步骤容错
这是最直白的一类 Hook,任何观察者都可以注册到编排器,接收 7 种生命周期事件:
public interface ScenarioOrchestrationListener {
void onOrchestrationStarted(ScenarioContext ctx, OrchestrationPattern pattern);
void onGroupStarted(String groupId, ScenarioContext ctx);
void onGroupCompleted(String groupId, ScenarioGroupResult result);
void onStepStarted(String stepId, String groupId, ScenarioContext ctx);
void onStepCompleted(String stepId, String groupId, StepResult result);
void onOrchestrationCompleted(ScenarioOrchestrationResult result);
void onOrchestrationFailed(String error, ScenarioContext ctx);
}典型用途包括:
这是 Ooder 区别于纯自动化 Agent 的关键设计:当某个 SceneGroup 的结果标记为 needConfirm=true(典型场景:设计阶段完成、需要用户确认架构图),编排器会暂停 Loop,调用 ConfirmationCallback,等待 4 种决策之一:
Decision | 语义 | 行为 |
|---|---|---|
CONFIRM | 用户确认通过 | 继续执行下一组 |
MODIFY | 用户修改部分内容 | 修改写回 ScenarioContext,继续下一组 |
REJECT | 用户拒绝 | 立即中止整个编排 |
RERUN | 用户希望重做 | 重新执行当前组 |
// ScenarioOrchestratorImpl
if (groupResult.isNeedConfirm() && confirmationCallback != null) {
Decision decision = confirmationCallback.confirm(group.getGroupId(), context, groupResult);
switch (decision) {
case REJECT: return ScenarioOrchestrationResult.failure(...);
case RERUN: groupResult = group.execute(context); break;
case MODIFY:
case CONFIRM:
default: // 继续
}
}这种设计把"人类介入"提升为编排核的一等公民,而不是上层应用通过 polling 或 WebSocket 临时拼出来的能力。当一个 Agent 系统需要可信交付时(特别是会写文件、改数据库的场景),这种在 Loop 内置的确认 Hook 是必须的。
每个 ScenarioStep 都声明自己的失败策略,编排器据此决策"出错时往哪走":
策略 | 行为 | 典型用例 |
|---|---|---|
FATAL | 立即中止本组并向上抛失败 | system_design、llm_fallback、project_integration、stepGenSPI |
RECOVERABLE | 记录失败但继续执行后续步骤 | intent_classification、config_generation、cls_validation |
SKIPABLE | 跳过本步骤,不影响下游 | entity_resolution、design_bridge、cls_audit |
失败策略是每个步骤自己声明的(在 Adapter 中通过 mapFailureStrategy() 静态映射),编排器只是按照声明执行,这让容错策略和业务逻辑解耦。
位于 Harness 的最外层、Spring 应用之外的环境(如 Trae IDE)通过 .trae/hooks/*.json 注册四类钩子:
Trae Hooks 不直接进入编排核,而是在 Harness 之外把 Agent 的输入/输出引导回闭环测试管线(参见 .trae/skills/ooder-nlp-harness/SKILL.md)。这是把"编排核 + Skill 闭环测试"做成一个可持续演进系统的关键。
从硬编码双轨到 SceneGroup 编排:演进对比

图 4 · 重构前后的架构对比
以"创建一个部门管理,包含部门名称、部门编码、负责人、上级部门、创建时间字段"为例,看完整 Loop 是怎么跑的。
POST /api/nlp/generate
{ "input": "创建一个部门管理,包含...", "projectName": "Demo", "autoSave": true,
"channel": "channel1_module_independent" }关键词"管理" → 系统级嫌疑 → 但只描述了单个模块 → 命中 STANDARD 模式:激活 UNDERSTAND + DESIGN + GENERATE + INTEGRATE,跳过 QUALITY。
这一步如果有 ConfirmationCallback,编排器会暂停等待用户在 Designer 上点确认 → 用户点 CONFIRM → Loop 继续。
编排器返回 ScenarioOrchestrationResult,宿主层将其转换为 NlpDesignResult(含 className、modulePath、saved 等字段),HTTP 端点流式返回给前端。
注意: 整个 Loop 中,宿主层从未感知"4 类 88+ 异构步骤"的存在 —— 它只看到一个 ScenarioOrchestrator + 5 个 SceneGroup。这就是分层抽象的红利。
读到这里,一个自然的问题是:为什么不用 LangGraph 那种 Step DAG,或者 Temporal 那种 Workflow Activity?
当步骤数到达 80+ 时,画 DAG 反而是负担:节点和边的拓扑爆炸,重构代价高昂。SceneGroup 把节点按语义而不是依赖分组(理解、设计、生成、质量、集成),让每一组内部维护自己的 DAG(PARALLEL_GROUPS),组与组之间是顺序的。这种"宏观线性、微观并行"的结构对人类工程师非常友好。
让 LLM 自由决策"下一步用哪个工具"是 ReAct 的精髓,但在有强结构约束的工程任务中(如代码生成),完全自由的 LLM 调度会带来不可预期的执行路径。Ooder 的折中是:意图由 LLM 推断,但 Loop 结构由模式映射。这既保留了灵活性,又保证了可观测、可重放、可审计。
真正交付到客户手上的 Agent 系统几乎都需要人机协作(确认设计、修改字段、回滚错误)。SceneGroup 把 ConfirmationCallback 内置到 Loop 关节点,让"用户介入"成为编排核的一等公民,而不是上层 UI 通过轮询拼出来的。
Ooder 已经有 13 个 PipelineStep + 5 个 DesignStage + 6 步 BuildSPI 在生产环境跑了一年多。任何新架构必须能包住而不是替换这些既有资产。SceneGroup 通过 5 个 Adapter 实现"包"而不是"重写",老路径标 @Deprecated 但仍然可用,新路径通过 Spring Boot 自动配置无侵入注册。这种迁移策略让重构能在不停机不破坏既有调用方的情况下落地。
SceneGroup 当前的形态解决了 Ooder 自身的编排问题,但它的边界值得继续推:
从这个角度看,SceneGroup 编排核更像一个面向结构化任务的 Agent OS 内核:
当我们用"操作系统"的视角看 Agent 框架,会发现现在大多数框架都还停留在"shell 脚本"阶段 —— 每个任务都重新写一遍 if/else 调度。SceneGroup 这种把语义边界、意图驱动、人机回路、容错策略一次性烧进内核的设计,可能是面向"工程级 Agent 系统"必须迈出的一步。
这篇文章是从 harness、loop、hook 三个视角拆解 SceneGroup 的尝试。如果你正在设计自己的 Agent 编排核,希望这套结构能给你一点不同于"Step DAG / ReAct" 主流叙事的视角。
Ooder A2UI · 编排核架构系列 · agent-sdk-core 2.5.0
关键词:SceneGroup · ScenarioOrchestrator · OrchestrationPattern · Agent Loop · Hook
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。