1. SecurityClaw 学习笔记/

01 项目总览与架构概览

结论先说:一个能长期运行的 Agent 框架,至少要把五层分开 #

如果你第一次看 SecurityClaw,很容易被它的技能目录、RAG 引擎、LangGraph 图和一堆 provider 抽象绕晕。但把所有文件先压成架构层,你会发现这个项目真正重要的不是“用了多少 AI 组件”,而是它把一个可运行 Agent 系统拆成了五层:

  1. 入口层:用户如何进入系统
  2. 编排层:任务如何被组织、分发和推进
  3. 能力层:系统到底会哪些技能
  4. 知识层:系统如何持有和检索证据
  5. 基础设施层:外部依赖如何被隔离

这篇的任务就是把总图建立起来。后面 02-09 篇,都是从这张总图里拆出的局部。


一、先看总图:不要先陷进某个 4000 行文件 #

flowchart TD
    User[用户 / 定时任务 / Web 请求] --> Entry[入口层 main.py / service]
    Entry --> Runner[编排层 runner.py / chat_router]
    Runner --> Skills[能力层 skills/*]
    Runner --> RAG[知识层 rag_engine.py / memory]
    Runner --> Graph[状态机 LangGraph / loop]
    Skills --> DB[基础设施层 db_connector.py]
    Skills --> LLM[基础设施层 llm_provider.py]
    RAG --> DB
    RAG --> LLM
    Graph --> LLM

如果你记住这张图,再去看代码,就不会有“所有东西混在一起”的感觉。


二、入口层:系统怎么被唤起 #

入口层解决的问题非常简单:谁发起了一次 Agent 运行?

在 SecurityClaw 里,入口主要是 CLI 和服务接口。

# 伪代码:入口层只负责收请求,不负责理解业务
@click.group()
def cli():
    pass

cli.add_command(chat)        # 人工交互
cli.add_command(run)         # 后台循环
cli.add_command(dispatch)    # 手动触发某个技能
cli.add_command(service)     # Web 服务
cli.add_command(list_skills) # 可观测入口
cli.add_command(status)      # 运行状态入口

入口层不该做什么 #

入口层不应该:

  • 直接执行业务逻辑
  • 直接调数据库
  • 直接决定用哪个技能
  • 直接持有权限策略

它只负责把用户意图交给编排层。

这是一个非常容易被写坏的边界。很多小项目会在 CLI 入口里直接 if question contains X: call skill Y,这样一旦入口变多(CLI / API / cron),逻辑会立刻复制粘贴。


三、编排层:Agent 为什么不是“一次 LLM 调用” #

编排层是整个系统的核心。它回答的是:

这次任务应该怎么推进?当前状态是什么?下一步由谁负责?什么时候该停?

编排层通常由两个部分组成:

  1. runner.py 这类系统调度器
  2. chat_router/logic.py 这类具体执行循环
# 伪代码:编排层的三件套
class Runner:
    def setup(self):
        skills = loader.discover()
        scheduler.register(skills)

    def build_context(self):
        return {"db": db, "llm": llm, "memory": memory, "config": cfg}

    def dispatch(self, question: str):
        state = create_initial_state(question)
        return graph.invoke(state)

编排层的职责边界 #

  • 管状态
  • 管循环
  • 管调度
  • 管恢复
  • 管停止

但它不应该亲自变成技能实现。编排层要“安排能力”,不能自己变成能力。


四、能力层:为什么 skills/ 不只是插件目录 #

很多人看 skills/ 会把它理解成“插件系统”。这个理解只对了一半。

真正准确的理解是:

skills/ 是系统的能力表面,它把可调用能力拆成一组带契约的最小单元。

一个技能目录至少会带来三种信息:

  1. 这项能力做什么
  2. 它什么时候跑
  3. 它允许怎么跑
skills/geoip_lookup/
├── manifest.yaml
├── instruction.md
├── logic.py
└── hooks.py

为什么这层必须独立出来 #

因为 Agent 的可扩展性,本质上不是“prompt 还能写多长”,而是:

  • 能不能低成本加新能力
  • 加了新能力后会不会污染旧逻辑
  • 一个坏技能会不会拖垮系统

把能力层做成目录化、声明化、可跳过的结构,就是在解决这几个问题。


五、知识层:RAG 和记忆为什么不是同一回事 #

Agent 系统里最容易混淆的两样东西是:

  • 运行中的状态
  • 可长期检索的知识

SecurityClaw 把它们拆开,是一个非常正确的决定。

5.1 运行状态 #

属于短期、结构化、为下一步服务

5.2 工作记忆 / checkpoint #

属于会话级,可恢复

5.3 RAG #

属于长期知识层,用来提供证据,不是直接拿来当状态机字段

State        -> 当前这轮在干什么
Checkpoint   -> 下次从哪里恢复
RAG          -> 我长期知道什么事实

如果把三者混在一起:

  • prompt 会膨胀
  • 恢复会变慢
  • 失败会变得不可解释

六、基础设施层:为什么要抽象 DB 和 LLM #

基础设施层存在的根本原因不是“设计优雅”,而是:

业务逻辑不该知道自己到底连的是 OpenSearch、MockDB 还是 SQLite;也不该知道自己到底调的是 Ollama、OpenAI 还是 Fake LLM。

class BaseDBConnector(ABC):
    def search(self, query: str): ...
    def index_document(self, doc: dict): ...
    def knn_search(self, embedding, k: int): ...

class BaseLLMProvider(ABC):
    def chat(self, messages): ...
    def embed(self, text: str): ...

这一层解决了什么 #

  1. 测试可跑:Mock provider 替掉真实依赖
  2. 部署可切换:本地、线上、离线模式都能共存
  3. 架构可扩展:不需要把每个技能重写一遍

如果这层不抽象,Agent 项目会非常快地从“框架”退化成“绑定某一个模型和某一个数据库的脚本集合”。


七、为什么这五层必须同时成立 #

只要缺一层,系统就会迅速变脆。

缺入口层边界 #

新增 API / cron 时逻辑复制

缺编排层 #

系统退化成“prompt + tool” 的拼装

缺能力层 #

扩展能力必须改核心逻辑

缺知识层 #

每次任务都得从零开始理解

缺基础设施抽象 #

测试和部署都会被真实依赖绑死

也就是说,一个可运行的 Agent 框架,不能只看“模型能不能规划”,要看这五层是否各自独立。


八、这一篇真正要你带走的框架 #

如果你以后再看任何 Agent 项目,先问它这五个问题:

  1. 入口在哪里?
  2. 编排层在哪里?
  3. 能力是怎么注册和执行的?
  4. 知识层怎么分 state / memory / RAG?
  5. 外部依赖有没有被抽象?

这五个问题能比“它用了哪个模型”更快判断项目是否靠谱。


九、验证命令 #

python main.py list-skills
python main.py status
pytest tests/ -x --tb=short