← 返回博客

AIOS 的目录结构

2026-05-13 · 产品记录

想快速理解一个项目,看目录比读 README 更直接。这是 AIOS 顶层的样子,以及为什么这么切。

想快速理解一个项目,看目录结构比读 README 更直接。README 告诉你它想成为什么,目录告诉你它实际上是什么。AIOS 顶层就这么几个目录,但每一层的边界划得相当明确。

顶层

把 AIOS 仓库 clone 下来,顶层会看到这些目录(node_modules、dist 之类的运行态产物不算):

  • server/ —— 后端,分主服务和应用服务两块
  • gui/ —— 前端 Vue 3 单页应用
  • apps/ —— 应用的 APP.md 文档(不放实现代码)
  • language/ —— i18n 烘焙源,中英文文案在这里
  • skills/ —— Agent skill 系统
  • scripts/ —— 启动 / 构建 / 修复脚本
  • database/ —— SQLite 数据,运行态,默认不入 git
  • files/ —— 用户上传和生成的文件
  • install-*.sh / install-*.ps1 —— 一键安装入口

顶层目录少,是有意为之。新手 clone 下来,30 秒就能拼出整个项目的轮廓。

server/ —— 后端是两个进程

AIOS 的后端不是一个进程,是两个,跑在不同端口:

  • server/main/ —— 主服务,端口 9502。HTTP / WebSocket 入口,系统级能力(Chat、Task、Auth、LLM、Settings 等)
  • server/apps/ —— 应用服务,端口 9503。每个用户态应用挂在这里

为什么要拆?因为系统级能力和用户态应用的重启代价不一样。改一个应用,只需要重启 apps 进程;系统内核相对稳定,不需要每次都跟着重启。

server/main —— 内核

主服务内部进一步分层:

  • api/ —— 路由入口,只做 path/method 分发
  • service/ —— 业务编排(auth, chat, task, prompt, settings, runtime)
  • ai/ —— Agent 执行循环、工具调用
  • llm/ —— Provider 抽象、输入归一、输出解析、流式处理
  • repository/ —— SQLite 数据访问

这是经典的 api → service → repository 三段式,但多了一个 ai/ 和 llm/。前者负责「AI 怎么用工具」,后者负责「怎么和模型说话」。这两块在 chat 和 task 里都会用到,所以单独拆出来。

server/apps —— 用户态应用进程

应用服务的入口 server/apps/index.js 只有 30 行,核心逻辑就是:启动时从 registry 里 import 每个应用,运行时按 URL 前缀找对应应用,把请求转给它。整个 dispatcher 不参与业务。

每个应用是一个模块,默认导出一个对象,包含这五个字段:

  • name —— 应用名
  • match(path) —— 判断这条 URL 是不是属于自己
  • initDb() —— 第一次启动时建表(可选)
  • initRuntime() —— 启动后台循环,比如订阅箱、炒币机(可选)
  • handleApi(req, res, path) —— 处理请求

模块内部按主服务的同样三段式组织:api/ + service/ + repository/。所以读完一个应用,基本能读懂全部应用。

apps/ —— 应用的「自报家门」

这个目录容易让人误解。它不放代码,只放每个应用的 APP.md 文档。每份 APP.md 都会写清楚四件事:应用名、一句话描述、后端在哪、用了哪张表。

这份文档不是写给人读的,真正的读者是 AI 自己。AIOS 在组装系统提示词时,会扫描 apps/*/APP.md,把「应用目录」完整地喂给 LLM。AI 因此一眼就知道:这个 OS 上有哪些应用、它们后端在哪、用了哪张表。

把元信息和实现分开,既让 AI 的认知接口足够薄,也让 README、代码、文档三者不会互相错位。

gui/ —— 前端

前端是一个 Vite + Vue 3 单页应用,源码全部在 gui/src/ 下:

  • views/AppShell.vue —— 整个 OS 的壳,根据路由动态加载应用组件
  • apps/ —— 每个应用的前端实现,目录名和 server/apps/ 一一对应
  • apps.js —— 应用注册表(id / 名称 / 图标 / 颜色 / 是否有内部侧栏 / 懒加载 import)
  • components/ —— 全站共用组件(QuickChat、AppsPopup、ToastHost、ReloadDialog 等)
  • stores/ —— 全站状态(auth、appContext、settings 等)
  • system/ —— 系统级工具(WebSocket、locale 等)

前端没有引入 Pinia 之外的状态管理,也没有运行时 i18n 库。多语言通过构建时烘焙完成,见下一节。

language/ —— 烘焙源

AIOS 不在运行时做 i18n,而是构建时把 __T_XXX__ token 一次性替换成目标语言。源码可以直接写自然语言,也可以写 token。语言资源放在 language/<locale>/ 下:

  • language/zh/server/*.json —— 后端文案
  • language/zh/gui/*.json —— 前端文案
  • language/zh/system/*.md —— 系统永远告诉 AI 的事,比如应用开发约定

language/zh/system/app-creation-guide.md 是 AIOS 给 AI 的「内部宪法」:新建应用必须把后端放在哪、前端放在哪、APP.md 写哪些字段、改完代码怎么 reload。这份文档在每次对话开始时都会被注入 system prompt。

i18n 烘焙最初是个权宜之计,后来发现它真正的价值是给 AI 减负:AI 写新应用时,不必同步维护翻译 JSON、不必关心 t() 调用——这是会复利的认知税。

skills/ —— Agent 技能

类似 Claude Skills 的设计:每个 skill 是一个独立目录,内含 SKILL.md 描述能力和触发条件,具体实现可以是脚本或子工作流。当前内置 skill 不多,主要作为可扩展点保留。

scripts/ —— 工具链

scripts/ 下放启动、构建、修复脚本。其中两个值得展开:

  • start.mjs —— 启动入口,会做语言烘焙。它有意在源码仓里做防烘焙保护,只在用户安装目录的运行副本里做语言替换,确保源码树不被 token 替换后的具体语言污染
  • fix-node-pty.js —— postinstall 阶段修 node-pty 的本机编译失败,跨 macOS / Linux / Windows 自动适配

database/ 和 files/ —— 运行态

这两个目录是用户真实数据的归宿:

  • database/aios.db —— 整个 OS 的状态,一个 SQLite 文件装下对话、设置、笔记、记账、应用数据
  • files/ —— 用户上传的附件、AI 生成的文件,按用途归类成子目录

都不入 git。备份 OS = 拷走这两个目录;重置 OS = 删掉这两个目录。

三个端口

运行时同时起多个进程,端口固定为约定值:

  • 9502 —— 主服务
  • 9503 —— 应用服务
  • 5173 —— Vite dev server(只在 npm run dev 时存在)

生产模式下没有 5173,主服务直接 serve gui/dist/。

为什么这样切

把目录划法和 AIOS 想做的事对照看,几条逻辑就清楚了。

一、AI 是一等公民。apps/ 只放 APP.md、language/zh/system/ 强制注入到提示词、ai/ 和 llm/ 单独拆出来——都是为了让 AI 拥有比传统用户更高优先级的认知通道。

二、应用是一等公民。server/apps/ 和 gui/src/apps/ 一一对应,每个应用自带 api+service+repository+前端组件,registry 注册即可上线。新增或删除应用不会污染主服务。

三、运行态和源码态严格分离。database / files / node_modules / gui/dist / .aios 都不入 git。源码树永远是「AI 写应用时该读到的样子」,不被语言烘焙、数据库内容、用户上传污染。

四、约定多于配置。后端三段式、前端 apps.js 注册、APP.md 自报家门、reload 三档(restartServer / restartApps / build)——这些都是约定,不是机制。AI 学会一套就能复用到所有应用。

目录结构本身就是一份契约。它告诉所有合作者——人类的、AI 的——这个项目的边界在哪,什么属于谁。

AIOS 是一个开源项目,完整源码在 GitHub:

github.com/realuckyang/AIOS