技能优先架构

Themis 将每个 AI 智能体功能结构化为三个层次:技能(领域知识)、输出工具(结构化契约)和工作流(生命周期编排)。技能是基础 —— 它们承载了使智能体高效运作的判断力、推理能力和专业知识。

三层架构

+--------------------------------------------------+
|  第一层:技能                                      |
|  职责:角色、流程、判断、领域知识                      |
|  来源:基于文件 (.claude/skills/, lib/skills/)      |
|         数据库存储 (Skill 模型, 3 种范围)            |
|         提示 (app/prompts/)                        |
+--------------------------------------------------+
                       |
                 智能体调用工具
                       |
                       v
+--------------------------------------------------+
|  第二层:输出工具                                   |
|  职责:结构化数据契约、副作用                         |
|  位置:app/services/*_tool_builder.rb              |
|  构建方式:ClaudeAgentSDK.create_tool()            |
+--------------------------------------------------+
                       |
                   返回结果
                       |
                       v
+--------------------------------------------------+
|  第三层:工作流                                     |
|  职责:仅生命周期编排                                |
|  位置:app/services/workflows/                     |
|  目标:50 行以内                                    |
+--------------------------------------------------+

第一层:技能

技能拥有所有判断力。它们告诉智能体_做什么_、如何推理_以及_何时使用工具。Themis 支持三种技能来源,协同工作为智能体提供全面的领域知识。

基于文件的技能(代码库)

以 Markdown 文件形式提交到代码库中的技能,带有 SKILL.md 清单。两个目录服务于不同目的:

  • .claude/skills/ —— Themis 内部技能。架构指南、编码规范、审查方法论、集成辅助。这些保留在 Themis 仓库中,由 Claude Agent SDK 自动发现。
  • lib/skills/ —— 可移植技能。在代码生成期间复制到目标项目工作树中,使智能体能够携带跨项目标准(例如 code-quality/ 包含 Rails 规范和安全检查清单)。

每个技能是一个包含 SKILL.md(YAML 前置信息 + Markdown)和可选补充文件的目录:

.claude/skills/understanding-themis/
  SKILL.md          # 包含名称、描述、内容的清单
  HOTWIRE.md        # 补充参考资料(可选)

数据库存储技能(Skill 模型)

用户创建的技能存储在数据库中,带有 Active Storage 文件附件。通过 Web UI 或在对话中通过智能体工具管理。三种范围控制可见性:

范围归属可见范围用例
系统管理员所有用户、所有空间组织级标准
空间空间所有空间成员团队特定知识
个人用户仅所有者个人偏好和工作流

数据库技能在每次智能体运行前由 SkillExtractor 提取到磁盘,按范围缓存并进行原子目录替换。智能体通过相同的 .claude/skills/ 目录约定发现它们。

SkillExtractor.prepare_for_agent(space:, user:)
  → Queries Skill.available_for(user, space)
  → Extracts to cache dirs (system / space / personal)
  → Returns add_dirs array for SDK options

智能体还可以在对话中通过 SkillToolBuilder 工具(create_skillupdate_skilllist_my_skills)创建和更新自己的个人技能。

智能体提示

提供工作流特定指令的静态和动态提示文件:

  • 静态提示.md)—— 技能不需要运行时上下文时使用。示例:pr_review.md 包含审查流程、质量标准和裁决准则。
  • 动态提示.md.erb)—— 注入运行时数据的 ERB 模板。示例:base_agent.md.erb 渲染每个 space 的上下文(如 agent 身份和可用渠道)。

提示位于 app/prompts/。通过 PromptLoader.load("name")(静态)或 PromptLoader.render("name", locals)(动态)加载。

提示定义流程和判断,但描述输出格式 —— 该职责属于输出工具的 schema。

技能如何到达智能体

ChatJob / ChannelMentionJob
  │
  ├─ System prompt ← PromptLoader (app/prompts/)
  │
  └─ add_dirs ← SkillExtractor
       ├─ File-based skills (.claude/skills/)
       └─ DB skills (extracted to cache)
              ├─ system/
              ├─ {space_id}/space/
              └─ {space_id}/personal/{user_id}/

Claude Agent SDK 扫描 add_dirs 中的 SKILL.md 文件并自动使其对智能体可用。技能可通过 feature_skills 设置按空间开关。

第二层:输出工具

输出工具定义智能体与系统之间的结构化契约。我们不要求智能体生成可解析的文本(这很脆弱),而是给它一个带有类型化参数的工具来调用。

将工具接入调用方是定义工具之后的下一步。每个智能体调用方(完整智能体、Web/API 对话、消息)通过工具目录选择性加入一组工具 —— 将工具添加到新调用方是一次声明式更改,而不是跨四个文件的编辑。

工具构建器位于 app/services/*_tool_builder.rb,使用 ClaudeAgentSDK.create_tool()

class PRReviewToolBuilder
  def self.build_submit_review_tool(review:, space:)
    ClaudeAgentSDK.create_tool(
      "submit_review",
      "Submit your completed code review.",
      {
        type: "object",
        properties: {
          verdict: { type: "string", enum: %w[APPROVE REQUEST_CHANGES COMMENT] },
          summary: { type: "string", description: "Markdown review summary" },
          comments: {
            type: "array",
            items: {
              type: "object",
              properties: {
                path: { type: "string" },
                line: { type: "integer" },
                body: { type: "string" }
              },
              required: %w[path line body]
            }
          }
        },
        required: %w[verdict summary]
      }
    ) do |args|
      # Side effects: submit to GitHub, update review record
    end
  end
end

Schema 就是格式规范。智能体在其工具列表中看到它,就确切知道要产生什么。不会在输出格式指令上浪费提示预算。

当前工具构建器

按用途分组。所有工具都通过工具目录接入智能体调用方。

工作流输出契约

构建器关键工具用途
PRReviewToolBuilderget_pr_info, get_pr_diff, get_pr_comments, get_ci_status, submit_reviewPR 审查工作流输出
CodeGenerationResultToolBuildersubmit_code_generation_result代码生成的 PR 元数据
AutomationToolBuilderskip_message自动化跳过决策

触发器(工厂接线)

构建器关键工具用途
PRReviewTriggerToolBuildertrigger_pr_review从对话/提及中入队 PR 审查
CodeGenerationToolBuildertrigger_code_generation从对话/提及中入队代码生成

数据访问

构建器关键工具用途
GithubToolBuilderget_pr_info, get_pr_diff, get_pr_comments, get_ci_status, list_pull_requests, post_pr_comment对话/提及上下文中的 GitHub 直接 API 访问
ChatHistoryToolBuildersearch_conversations, recall_conversation按需对话历史
RepoSearchToolBuilderresolve_repo_path浏览本地 git 工作树
ThemisQueryToolBuilderquery_themis_dataThemis 数据库查询(editable_by? 门禁)
GoogleDriveProxyToolBuilder代理的 Google Drive 读取工具按用户的 OAuth 范围 Drive 访问

副作用

构建器关键工具用途
SentryToolBuilderupdate_sentry_issueSentry 状态 + 分配
MemoryToolBuildersave_memory, delete_memory按用户的内存存储

对话 UX

构建器关键工具用途
AskUserQuestionHook (PreToolUse)AskUserQuestion (原生内置)结构化的澄清问题 — 不作为 MCP 工具构建,通过钩子拦截
FileToolBuildercreate_file智能体生成的文件下载
ShowWidgetToolBuildershow_widget沙箱化 HTML 小组件(D3、Mermaid、SVG)
ShowChartToolBuildershow_chart结构化 Chart.js 渲染
ImageGenerationToolBuildergenerate_imageGemini 图像生成

资源管理

构建器关键工具用途
SkillToolBuilder13 个工具 —— CRUD、文件操作、签出/签入智能体驱动的技能管理
AutomationChatToolBuildercreate_automation, update_automation, list_my_automations, delete_automation智能体驱动的自动化管理

第三层:工作流

工作流是薄的胶水层。它创建记录、启动智能体、处理错误并更新状态。它应该包含零判断零解析

所有工作流继承自 Workflows::BaseWorkflow 并实现 #execute。基类提供 #run_agent(prompt:, system_prompt:, model:, max_turns:)

module Workflows
  class FeatureWorkflow < BaseWorkflow
    def execute(input:)
      record = FeatureRecord.create!(input: input, status: "running")

      begin
        result = run_agent(
          prompt: build_prompt(input),
          system_prompt: PromptLoader.load("feature_name")
        )
        record.complete!(result)
      rescue => e
        record.fail!(e.message)
        raise
      end
    end
  end
end

如果您的工作流正在进行正则解析、JSON 提取或业务逻辑 —— 说明某些内容放错了层。

决策框架

问题答案
是否涉及判断、推理或领域知识?将其移到技能中技能
是否定义结构化数据交换或产生副作用?将其做成输出工具输出工具
是否管理记录生命周期、错误恢复或编排?保留在工作流中工作流
正在从 LLM 自由文本中解析结构化数据?做法有误重构为输出工具
正在编写关于输出格式的提示指令?工具 schema 应该处理这个重构为输出工具
工作流超过 50 行?某些内容放错了层审计并重新分配

添加新功能

步骤 1:编写技能

根据用途决定技能的存放位置:

  • 智能体提示app/prompts/)—— 工作流特定的指令。静态使用 .md,动态上下文使用 .md.erb
  • 代码库技能.claude/skills/)—— 跨工作流共享的可复用领域知识。
  • 数据库技能 —— 用户可配置的知识,通过 UI 管理。

关注:角色、流程、判断准则、领域知识。不要描述输出格式。

步骤 2:将输出契约定义为 SDK 工具

创建 app/services/feature_tool_builder.rb。工具 schema 定义智能体产出什么。处理器执行副作用。

class FeatureToolBuilder
  def self.build_submit_tool(record:, space:)
    ClaudeAgentSDK.create_tool(
      "submit_result",
      "Submit your analysis results.",
      { type: "object", properties: { ... }, required: %w[...] }
    ) do |args|
      # Execute side effects, return confirmation
    end
  end
end

步骤 3:将工作流编写为薄胶水层

创建 app/services/workflows/feature_workflow.rb。它只应创建记录、构建提示、调用 run_agent、处理错误和更新状态。目标 50 行以内。

步骤 4:接入任务

创建 app/jobs/feature_job.rb。任务构建选项(模型、MCP 服务器、工具、技能目录),实例化工作流并调用 execute。使用工具目录选择性加入工具组,而不是手动组装 mcp_servers / allowed_tools 列表。

class FeatureJob < ApplicationJob
  def perform(record_id)
    record = FeatureRecord.find(record_id)
    options = build_options(record)
    workflow = Workflows::FeatureWorkflow.new(options: options)
    workflow.execute(record: record)
  end
end

反模式

反模式为什么是错的修复方法
从智能体自由文本解析 JSON脆弱:在 Markdown 围栏、额外文本、格式变化时会崩溃定义带类型化参数的输出工具
提示指令描述输出格式浪费 Token 预算在智能体可能忽略的规则上;重复契约工具 schema 就是格式
工作流中的业务逻辑将编排与领域逻辑耦合;使工作流臃肿将判断移到技能中,数据契约移到工具中
没有 ERB 的大型提示无法注入运行时上下文(用户偏好、项目设置)对动态部分使用 .md.erb 和 locals
每个工作流多个输出工具使智能体困惑于该调用哪个工具每个工作流一个主要输出工具
工具处理器中的复杂业务逻辑难以测试,与基础设施紧密耦合保持处理器精简:验证、副作用、确认

工作流成熟度

成熟度跟踪每个工作流将技能、输出工具和编排分离的清晰程度。无论成熟度级别如何,所有工作流的工具接线现在都通过工具目录统一进行。

工作流成熟度输出契约
PRReviewsubmit_review SDK 工具处理 GitHub 提交
Mention智能体直接使用 MCP 工具(GitHub、Linear 评论)
Automationskip_message 工具用于跳过决策;通过 AutomationMessageDeliveryService 投递
CodeGenerationsubmit_code_generation_result 工具已存在;PR 元数据部分通过解析获取

成熟度级别:

  • —— 提示拥有判断力,工具拥有契约,工作流是薄的生命周期胶水。
  • —— 部分遵循模式。仍有一些解析或格式指令。
  • —— 判断、解析和编排混杂在工作流中。