agent24 架构基线
阅读快照:阅读快照(main @ bc923205f4a773836d0f872ced63c98f0cb653be,2026-11-12)。下文路径相对 agent24 仓库根。链接固定到该 SHA。
这份基线回答一个问题:当前架构能不能朝某个方向演进。判断时先看「扩展点」,再看「它明确还没接上的缝」。库里已有的类型,不等于产品路径已经调用它。
1. 目的与产品定位
上游 README 把 Agent24 写成 shell-agnostic 框架:Rust 内核加 daemon 提供个人 AI 助手的承载,外壳只经 HTTP/WS 连接。自带 Electron 参考壳,也允许 Tauri 等其他壳挂上。应用方从本框架 fork,再装场景模块。见 README.md 第 1-17 行。
根 package.json 的描述是另一句产品话:「24/7 personal & community agent platform (Electron shell + daemon + protocol)」,版本 0.6.0。见 package.json 第 2-5 行。
researcher 的 T004 记录作者口述:Agent24 是基础办公套装,Sin90 / Cos72 是可加载 OS。那是 2026-10 的产品说明,源码依据更早。本基线不改那份说明。代码侧能对上的是:桌面壳、Open Design sidecar、Documents 进程外模块、Knowledge 宿主接口、微信入站桥,都已经长在同一个 daemon 上。评估「要不要借鉴某项能力」时,要写明假设的是框架内核,还是这套办公壳的用户体验。两者的改动落点不同。
远程消费仍是目标态。daemon 绑定 127.0.0.1,没有公开 bind。见 serve_steps.rs 第 965-985 行。README 自己也写了跨设备要自备隧道和认证。
2. 技术栈
| 层 | 现状 | 证据 |
|---|---|---|
| 内核语言 | Rust,workspace edition 2024,Apache-2.0 |
rust/Cargo.toml 第 78-81 行 |
| 安全约束 | unsafe_code = forbid;非测试代码 unwrap_used / expect_used = deny |
rust/Cargo.toml 第 88-97 行 |
| daemon | agent24d 0.6.0,axum 路由,tokio 运行时,reqwest 调模型 |
agent24d/Cargo.toml 与路由表 |
| 持久化 | sqlx + SQLite,WAL,外键,busy_timeout 5 秒 |
agent24-store/src/lib.rs 第 274-280、356-363 行 |
| 桌面 | Electron + React 18 + Vite,Node >=22,pnpm 9.12 |
根 package.json 与 apps/desktop/package.json |
| 协议 | 手写 protocol/openapi.yaml(本次约 8166 行)+ Rust 导出的 WS 事件 schema,生成 packages/api-client 的 TS 类型 |
README 架构图;CI 的 pnpm gen:api 约定见 AGENTS.md |
| Python | ML worker 只有 Rust 侧契约。agent24-worker 不在 default-members,普通 cargo build 会跳过 |
rust/Cargo.toml 第 39-42 行 |
packages/node-daemon 仍是 v1 协议的 mock / 参考实现,承载 TS CapabilityModule。它不是桌面默认后端。桌面默认拉起 Rust daemon。见 README 第 45-55 行,以及下文第 9 节的拉起方式。
3. 模块与包布局
Rust workspace 成员见 rust/Cargo.toml 第 1-37 行。按职责分成四块:
- 应用:
apps/agent24d(组合根)、apps/agent24-cli、apps/agent24-documents、apps/agent24-sidecar-host。 - 内核服务 crate:
agent24-core、agent24-agent、agent24-models、agent24-tools、agent24-policy、agent24-memory、agent24-store、agent24-scheduler、agent24-mcp、agent24-workspace。 - 扩展与宿主:
agent24-domain、agent24-os-proto、agent24-os-sdk、agent24-os-packages、agent24-os-fd、agent24-attach,以及agent24-knowledge、agent24-documents-client、agent24-comm、agent24-components、agent24-idoris、agent24-decide、agent24-hw。 - 实验:
agent24-worker。
仓库自带分层图把依赖方向定成 L0 契约、L1 纯领域、L2 存储、L3 能力服务、L4 agent loop、L5 agent24d、L6 进程外模块、L7 外壳。该图生成于 f818b0bf,见 docs/ARCHITECTURE-LAYERS.md 第 17-72 行。本次没有重跑 cargo metadata。新增 crate 时以那份脚本和当前 Cargo.toml 为准,不要只改叙述。
TypeScript 侧:apps/desktop,以及 packages/ 下的 api-client、node-daemon、wechat-bridge、data-dir、contract-tests、documents-accept。pnpm workspace 只包含 apps/* 和 packages/*。
vendor/ 有两个 submodule(xiaoheishu、gemma-chrome-translate)。CI 明确 submodules: false。见 .github/workflows/ci.yml 第 33 行。
4. 核心 agent loop 与编排
产品路径上的一次运行由 RunManager 持有。它拼在一起的是 store、ModelRouter、ToolRegistry、可选的 WorkspaceService 与 SessionMemory、事件 sink,以及一棵取消令牌树。见 agent24-agent/src/lib.rs 第 580-609 行。
start_run 的语义是先落库再后台执行:
- 生成
run_<ulid>,状态Queued。取消令牌先于行可见写入内存表,避免客户端列出 run 时还没有令牌。见第 1545-1571 行。 - 带
workspace_id的 run 走 workspace admission,和租约一起写入。见第 706-725 行。 - 入站 run 把 session、run、
inbound_runs放进同一事务,重复事件不会再开一条。见start_run_inbound第 1497-1509 行。 execute放在独立 task 里。panic 会把 run 打成失败并清掉令牌,避免永远停在非终态。见第 1612-1638 行。
execute(第 2295 行起)把 run 转成 Running,发出 RunStarted,再做断言召回。召回失败只记警告,不阻断这次模型调用。随后进入 run_loop。
run_loop(第 2923-3159 行)是共享的 completion 与工具回合,上限 MAX_ITERATIONS = 10(第 62 行):
- 每轮先检查已持久化消息的来源标签。来源无法证明则失败,不发给模型。
- 按 plan mode 组装工具清单。
propose_plan是循环内部构造,用来把只读工具切到全量工具,且要人审批。常量在第 230 行。 ModelRouter::complete与取消令牌赛跑。Unavailable、Provider、iDoris headroom、supervisor busy 都映射成用户能看的失败码,不再笼统写成 internal。- 没有 tool call 时,先把会话记忆写完,再把 run 标成
Completed。记忆写入与取消赛跑,避免长时间停在不可取消状态。预算常量MEMORY_WRITE_BUDGET为 30 秒(第 252 行),但注释写明 SQLite 确认追加可以超过这个预算。 - 有 tool call 时,先持久化 assistant 回合,再逐个执行。每回合最多执行
MAX_TOOL_CALLS_PER_TURN = 16(第 101 行),多出来的调用只回一条 skipped,不执行。
调度器是另一条进入 loop 的路。agent24-scheduler 用 cron / every / at 触发 run。due 的 next_run_at 在触发前就推进并落库,宕机期间错过的 tick 只补一次,不回放。见 agent24-scheduler/src/lib.rs 第 1-20 行。agent 还可以用 SelfWakeTool 给自己建一次性 schedule。见 serve_steps.rs 第 596-600 行。
探索子代理是结构上的只读:它拿到的 registry 只有 fs_read,没有写、shell、网络,也没有它自己。见 agent24-tools/src/lib.rs 第 1859-1879 行 与注册处第 585-594 行。
agent24-decide 没有接到这条 loop。agent24d 的依赖注释写明:目前只提供 GET /api/v1/decide/profile,决策级联仍是未接线的 D1。见 agent24d/Cargo.toml 第 28-29 行。
5. 工具与插件
内核工具是 Tool trait:JSON Schema 参数、风险等级、超时、出口授权、审批绑定。默认超时 30 秒。见 agent24-tools/src/lib.rs 第 626-687 行。
默认内置集只有四个:http_fetch、fs_read、fs_write、shell_exec,工作区目录作读写白名单和 shell cwd。见第 1850-1857 行。用户可以把第三方工具的风险调松,但不能把 builtin 或 module 工具调松。见 effective_risk 第 1887-1918 行。
启动时还会挂上:已安装领域 OS 声明的模块工具、Knowledge 工具(仅当 run 带了 binding)、~/.agent24/mcp.json 里的 MCP 工具、explorer、self-wake。见 serve_steps.rs 第 503-619 行。
MCP 工具适配成同一个 Tool。名字带服务器前缀。风险类固定 External,调用前走 authorize_egress_if_restricted。服务器自己不能把风险降下来。见 agent24-mcp/src/lib.rs 第 253-343 行。daemon 同时暴露一个 MCP server,把 run 能力向外提供。这是「内核当 MCP 客户端」和「内核当 MCP 服务端」两条不同的边。
审批在 ApprovalBroker。默认没有 Guardian,gated 调用等人决定。Guardian 只自动放行它判成低风险的调用,模型不可用或高风险仍落到人。会话级 grant 有上限 1024,溢出时清空,下次再问。见 agent24-policy/src/lib.rs 第 242-257 行。
TS CapabilityModule 仍只活在 node-daemon 内部,没有对外 SDK。和 Rust Tool 不是同一条加载链。README 第 148-176 行写了这个边界。新能力若要进桌面默认路径,应走 Rust Tool、模块工具声明,或进程外领域 OS,而不是再写一个只在 mock daemon 里可见的 TS 模块。
技能契约是另一件事。ADR-040 已接受为设计约束,并写明仍是零代码:没有技能加载器、版本表、评测跑器。见 docs/adr/ADR-040-skill-contract-v1.md 第 4-14 行。仓里的 skills/knowledge-entry/ 是一份技能内容,不是运行时。
6. 记忆与状态
有三层,不要混成「已经有一套分层记忆产品」:
- 权威业务状态在
agent24-store的 SQLite(agent24.db):session、run、消息、审批、schedule、审计。Store自己写明它只校验和记账,不建立调用方授权。见 lib.rs 第 221-238 行。审计日志带prev_hash/hash链。 - 会话工作记忆是 canonical session。verbatim 超过 40 条就折叠成滚动摘要,保留最近 20 条,摘要硬顶 4000 字符。见 agent24-memory/src/session.rs 第 1-48 行。
RunManager上的memory可以是None,那时 run 只从当前 prompt 开始。加载失败时,普通错误退化为空上下文;导入不完整则失败,避免假装这段历史不存在。见 lib.rs 第 904-939 行。 - 跨会话个人记忆是断言账本。产品路径的
recall走KvStore::retriever(),而这个方法返回的是FtsRetriever。见 session_memory.rs 第 153-172 行 与 memory/src/lib.rs 第 576-578 行。暂停开关关掉后,召回直接返回空。REST 在/api/v1/memory/assertions与/api/v1/memory/settings,owner 不是请求参数。
向量检索是库原语。VectorRetriever 注释写明真正的 OmlxEmbedder 还在等 ML worker,这一片交付的是 trait、存储、余弦和 FTS 回退,用的是测试 embedder。见 vector.rs 第 1-25 行。本次在 agent24d 产品路径上没有搜到 VectorRetriever 的调用。
事件日志、巩固器、condenser、artifact 都在 agent24-memory 里导出。它们是可调用的库,不是「loop 每轮都会跑」的证明。README 写明 L3 ATIF 轨迹、SkillBank、自进化框架没有对应符号。本次在 agent24-agent 里搜索 SkillBank / atif 也没有命中。
领域 OS 的记忆隔离靠 ScopedMemory 句柄和 (org, space) 键前缀。README 同时写明:进程外模块与内核同 UID、无沙箱,句柄保证挡不住直接打开 sqlite 文件。这是当前安全模型的一部分,不是文档笔误。
7. 模型与 provider 抽象
最小契约是 ModelProvider:name、outbound_destination、complete、models。没有路由,也没有通用重试。见 agent24-models/src/lib.rs 第 225-246 行。
实现体是 OpenAiCompatProvider,用来接 oMLX、Ollama、LM Studio 和远程 OpenAI 兼容端点。标成 Local 的实例必须 loopback_only():不读代理环境变量,不跟随重定向。见第 332-354 行。思考开关按实例打开,不按 tier 推断。
ModelRouter 在 trait 之上做档位选择。TaskProfile 只有 Privacy 和 Complexity。LocalOnly 永不产生 Remote。未知 tier 字符串解析成 Remote。见 router.rs 第 30-98 行。Unavailable 进入指数冷却,上限默认 60 秒,成功则清零。
daemon 启动调用的是 ModelRouter::from_env()。见 serve_steps.rs 第 322 行。from_env 的实际分支:
- 若设置了
IDORIS_URL,provider 列表为空,改为装入IdorisGateway::from_env()。见 router.rs 第 358-364 行。 - 否则构造 oMLX(默认
http://127.0.0.1:8088,默认模型Qwen3-8B-4bit)和 Ollama(默认http://127.0.0.1:11434)。非 loopback URL 标成Remote。见第 365-409 行。
README 仍写「生产启动只构造 oMLX 与 Ollama,iDoris 在 P4 门后」。代码已经有 IDORIS_URL 这条旁路,以及 /api/v1/idoris/* 的安装、连接、启停路由。见 server.rs 第 966-988 行。评估模型相关方案时以 from_env 为准,并注明 README 这句话落后于这次提交。
agent loop 默认把 TaskProfile::default() 交给路由,再用 run 的来源策略把它收成 LocalOnly。见 lib.rs 第 691-704 行与 2966-2968 行。没有独立的「按任务自动生成复杂度」步骤。Lora tier 在枚举里存在,from_env 没有注册 LoRA provider。
8. 接口与 API
外壳只应走 v1 REST + WebSocket。路由表在 build_router_with_modules。领域 OS 路由先折进 kernel,再套 auth layer。注释写明顺序不能反,否则模块路由会变成无认证空洞。见 server.rs 第 947-954 行。
这次读到的主要面:
- 对话与运行:
/api/v1/chat、/sessions、/runs、/runs/{id}/cancel、/events(WS)。 - 模型与用量:
/models、/usage、/timings。 - 工具与同意:
/tools、/tool-overrides、/standing-grants、/module-consents、/approvals、/module-approvals。 - 记忆:
/memory/assertions、/memory/settings。 - 调度:
/schedules及 run_now / suspend / resume。 - 入站:
/inbound/allowlist、/inbound/policy、/inbound/events。 - 扩展:
/os、/attached、/os/{name}/commands/{command}、/components、/host/knowledge/bindings/...。 - 宿主:
/capabilities/creative、/presence/challenges、/shutdown、/peer/runs。
认证有两种。桌面在非 Windows 上拉起 Rust daemon 时用 capability 模式:serve --port 0 --auth-mode capabilities --host-bootstrap-stdio。Windows 和 node mock 仍是 legacy 单 token。见 backend-manager.ts 第 141-145 行与第 420 行。分层文档说路由级资源授权还没做。本次没有逐条路由复核授权中间件,所以这里只记录桌面启动参数,不把「每个路由都已按资源授权」写成事实。
微信桥是开发用传输。它把完成的用户文本 POST 到 /api/v1/inbound/events,回复固定「已记录」。准入、run、审批留在 daemon 和桌面。见 packages/wechat-bridge/package.json 第 5 行 与 bridge.ts 第 1-4 行。这次 main 的最新合并就是这条桥(PR #1819)。
packages/api-client 只有生成的类型,不是一个带重试的 HTTP SDK。外壳自己发请求。
9. 扩展点
后续评估「架构是否撑得住」时,优先对到下面这些缝。每一条都写了它今天能做什么,以及它还不能代替什么。
| 扩展点 | 今天能做的 | 还不能代替的 | 证据 |
|---|---|---|---|
ModelProvider + ModelRouter |
加一个 OpenAI 兼容端点,并放进 Local / Remote / Lora | 运行时设置页、按任务自动画像、独立 Claude 协议、LoRA 训练 | from_env 与 TaskProfile |
Tool + ToolRegistry |
内置工具、MCP、模块工具、只读子代理 | 不受审批的任意插件,或在 node-daemon 里写完就出现在桌面 | serve_steps.rs 注册顺序 |
| 进程外领域 OS | agent24 os install,独立进程,自己的库,/api/v1/<name>/* |
进程内 DomainModule 的生产实现。README 写明生产级 in-process 实现已经没有,Sin90 已迁出 |
README 第 117-119 行 |
| 附着模块 | 用户自己启动的进程,经 agent24 os attach 注册,反向命令走同一条连接 |
附着模块自己的入站 REST 面 | server.rs 第 1125-1170 行 |
| sidecar | 桌面 SidecarManager 托管 Open Design(TS,POSIX) |
Rust agent24-sidecar-host。分层文档写它未接入 Desktop,本次未看到 desktop 调用它 |
docs/ARCHITECTURE-LAYERS.md 第 374 行,需随代码再核 |
| 调度器 | cron 触发 run,模块回调投递,self-wake | 把任意外部事件都变成受监督的后台作业 | scheduler 文件头 |
| 入站事件 | allowlist、policy、微信桥写入 /inbound/events,再 start_run_inbound |
统一的多入口准入。分层文档把 Nostr、语音、手机遥控列成未完成的 Q12 | 微信桥与分层文档第 390 行 |
| 记忆库 | FTS 召回、摘要压缩、断言账本、向量原语 | 产品路径上的语义召回、技能库、自进化 | retriever() 与 vector.rs |
| 组件锁 | iDoris / Hyphae 的组件安装与状态 | 技能加载器。ADR-040 仍是零代码 | ADR-040 |
| 协议 | 手写 OpenAPI + 事件 schema 生成 TS | 由 Rust 类型生成 OpenAPI。分层文档列为计划项 B4 | 分层文档第 151 行 |
两条扩展不要并成一条。领域 OS 决定「这台 agent 是什么产品」,能力模块 / 工具决定「多会一件事」。README 第 79-110 行把权限词表仍是两套这件事记成未还的债。
10. 与性能相关的设计
这些是代码里已经写成约束的点,不是压测结果。本次没有跑基准。
- 循环有硬上限:10 轮模型调用,每轮最多执行 16 个工具。超出部分不执行。
- 工具默认 30 秒,MCP 工具 60 秒。模型聊天超时 120 秒,廉价请求 5 秒(
OpenAiCompatProvider::new第 301-304 行)。连接超时 2 秒在base_client_builder第 282 行。 - provider 失败有冷却,避免一次故障打满下游。
LocalOnly在本地全挂时直接失败,不会为了「有响应」改走远程。 - SQLite 使用 WAL。关库时做
wal_checkpoint(TRUNCATE),checkpoint busy 则返回冲突,不自行删除 WAL 文件。见 storeclose第 241-270 行。连接busy_timeout5 秒,文件池max_connections(5)。 - 记忆写入与取消赛跑,摘要压缩计入 30 秒预算。追加确认可以更长。会话锁被别的 run 持有时,后来的 run 会停在
session_context。这是正确性约束,也是并发吞吐的上限。 - 调度 tick 用注入时钟,不在引擎里 sleep。错过的触发 skip,不回放风暴。
- 桌面与 daemon 分进程。daemon 只绑 loopback,动态端口。外壳通过 ready 行拿 token。跨进程是隔离和启动成本的来源,也是壳可替换的前提。
agent24-agent/src/lib.rs本次 10559 行,server.rs7717 行。热点逻辑集中,改一轮 loop 的审查面很大。这是演进成本,不是运行时性能数字。
11. 测试与部署
CI(.github/workflows/ci.yml)包括:Hyphae lock 校验、pnpm typecheck / lint / test、Open Design 打包脚本测试、Rust fmt + clippy + test(超时 30 分钟)。另有桌面 release、Windows check、sidecar Windows workflow。提交约定在 AGENTS.md:Rust 门禁是 cargo fmt --all --check、cargo clippy --workspace --all-targets -- -D warnings、cargo test --workspace。改了 apps/ 或 packages/ 才跑 pnpm。改了 protocol/ 要 lint OpenAPI 并确认生成物无 diff。
发布流程把集成分支 ab/<方向> 上的 task PR 和进入 main 的 release PR 分开。release 要求按用户路径做 CLI 用例。纯界面除外。Open Design 下载、presence、Comm 私钥、Knowledge 凭据只在桌面验收。见 AGENTS.md 第 12-27 行。
桌面打包脚本会编 agent24d 与 agent24-cli。macOS 额外编 documents 模块。见 apps/desktop/package.json 的 build:mac / build:win / build:linux。版本号 0.6.0。
本次没有执行这些门禁,也不把 CI 绿写成这次阅读的结论。
12. 已知限制与技术债
下面每条都在本次源码或该 SHA 的文档里有直接句子。未复核的分层文档条目标了「文档声称」。
- daemon 只绑
127.0.0.1。多端远程消费需要外加隧道和认证。 ModelRouter::from_env的默认槽是 oMLX 与 Ollama。IDORIS_URL会换成另一条网关路径。没有通用的远程 provider 注册表,也没有 LoRA 槽的生产注册。- 主 loop 的
TaskProfile默认简单档,只会被来源策略收紧为LocalOnly。复杂度不会自动升到Complex。 - 产品召回是 FTS。向量检索的真实 embedder 未接上,daemon 也未调用
VectorRetriever。 - 技能运行时不存在。ADR-040 自己写「零代码」。
migration_coordinator::ENABLED为false。main.rs第 115 行用编译期断言锁住,生产启动不调用run。见 migration_coordinator.rs 第 19-20 行。knowledge-storefeature 默认关闭。注释写knowledge.db没有生产调用方。见 agent24d/Cargo.toml 第 14-16 行。Knowledge 工具只出现在带 binding 的 run 上。- 进程外模块无沙箱,和内核同 UID。记忆句柄不是安全边界。
- 领域 OS 与能力模块的权限词表仍是两套。
- Windows 桌面仍走 legacy 单 token。workspace-bound run 的完整路径文档标为 Unix。本次只复核了
selectedAuthMode。 agent24-worker无产品调用方。Python ML 服务未实现。语义召回、转写、LoRA 都卡在这条边上。- loop 与 HTTP 路由集中在两个超大文件。协议文件也超过 8000 行。小改动的冲突和审查成本高。
- README 的「今天生产只走 oMLX/Ollama、iDoris 在 P4」与
from_env的IDORIS_URL分支不一致。引用 README 能力表之前先对代码。 - 文档声称、本次未逐行复核:capability 的路由级资源授权、OpenAPI 改由 Rust 生成、领域 OS 包签名与文件哈希、sidecar-host 未接入桌面、Web3 身份未接线。来源是 ARCHITECTURE-LAYERS.md 第 366-392 行,对应更早的
f818b0bf。要用这些条做结论时,先在当前 SHA 上重读对应文件。
13. 评估新方向时怎么用
收到一条外部材料时,用这份基线做三步,详细分析写进该条目的 analysis.md,结论写进 recommendation.md。
- 它落在哪条扩展点上。对得上表中某一行,就引用那一行的文件。对不上,写明要新开的缝,不要把它说成「插件一下就行」。
- 产品路径有没有调用。crate 里有类型,只说明库原语在。要看到
agent24d启动或run_loop调用,才能说当前架构已经支持。 - 它会不会削弱已经守住的约束:
LocalOnly不外泄、工具审批、来源标签、loopback 绑定、取消在非终态仍然有效、WAL 与审计链。削弱任何一条都要在建议里写成风险,而不是实现细节。
基线过期的信号:main 的 SHA 与 阅读快照 不同,或本文件引用的函数在新提交里改了签名。那时先更新快照和受影响的小节,再评估新条目。不要用 2026-10 的 54cec44 结论覆盖这次基线,也不要让这份基线回写 T004 至 T008。