# agent **Repository Path**: ariacompute/agent ## Basic Information - **Project Name**: agent - **Description**: aria agent - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-14 - **Last Updated**: 2026-11-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# agent **面向 AI Agent 的分层 agent 平台** 基于 OpenAI [`codex`](https://github.com/openai/codex) harness 构建,向上提供 (a)
**Rust + 云端 API**(对应 **OpenAI beta Agents** 资源面)、(b) **JS / Python
SDK**(镜像 OpenAI Agents SDK),以及 (c) **Swift / Kotlin 原生 SDK** —— 它们共享
同一套 `recall → model → memorize` 运行时与可插拔 sandbox。 [English](./README.md) · **简体中文** · [文档](./docs/adr/0001-submodule-strategy.md) [![][release-shield]][release-link] [![][language-shield]][language-link] [![][license-shield]][license-link] [![][lastcommit-shield]][lastcommit-link]
[![][sdks-shield]][sdks-link] [![][tests-shield]][tests-link] [![][docs-shield]][docs-link] [![][codex-shield]][codex-link]
目录 #### 目录 **上手** - [✨ 功能](#-功能) - [🧩 模块](#-模块) - [🚀 快速开始](#-快速开始) **命令行与部署** - [🧰 命令行](#-命令行) - [🛳 部署](#-部署) **架构与 SDK** - [🔗 codex 子模块](#-codex-子模块) - [🌐 浏览器 sandbox](#-浏览器-sandbox) - [🧠 记忆图与 memo 工具族](#-记忆图与-memo-工具族) - [🔌 OpenAI Agents API 兼容性](#-openai-agents-api-兼容性) **SDK** - [📦 SDK 示例](#-sdk-示例) **信任与治理** - [🔏 信任库安全](#-信任库安全) - [🔨 开发](#-开发) - [📚 文档](#-文档) - [📝 许可证](#-许可证) - [🤝 工程约定](#-工程约定)
## ✨ 功能 `agent` 让每个 SDK 都遵循同样的三步形态——定义 `Agent`、运行一次,然后复用会话 继续对话并流式输出——而运行时自行决定记忆存于何处、工具在何处执行: - 🧩 **分层 agent 运行时** —— `aria-agent-core` 运行 `recall → model → memorize`;工具在可插拔的 `Sandbox`(Docker / Kata / Cube / Browser)中执行。 - 🔗 **深度 codex 集成** —— OpenAI 的 `codex` harness 以 git 子模块方式引入(含 sandboxing + memories),并通过一个小补丁提升为仓库根 Cargo 工作区。 - ☁️ **云端 API** —— `aria-agent-cloud` 是一个 axum 服务,讲 OpenAI **beta Agents** 资源面,Postgres + pgvector 同时保存元数据与上下文,并支持 SSE 流式。 - 📱 **端侧存储** —— `aria-agent-memo`(aria memo / SQLite)保存 local-first 的上下文副本,与云端后端通过同一个 `ContextStore` trait 互换。 - 🌐 **五语言 SDK** —— JS / Python 镜像 OpenAI Agents SDK;Rust / Swift / Kotlin 把运行时嵌入进程内,无需服务端、无需网络。 - 🧭 **可插拔 sandbox** —— 每个工具容器都受 `SANDBOX_CPUS` / `SANDBOX_MEMORY` / `SANDBOX_PIDS_LIMIT` 限制,并经由 `DOCKER_HOST` / `ARIA_DOCKER_SOCKET` 访问守护进程。
[![][back-to-top]](#readme-top)
## 🧩 模块 | 模块 | 说明 | |------|------| | **codex submodule** | `openai/codex`,位于 `codex/`(harness、sandboxing、memories)。 | | **aria-agent-memo** | 端侧/嵌入式**上下文存储**(aria memo / SQLite,与 `aria-memo` CLI 同款 `memories` 表结构),实现 `aria-agent-core::context` 契约。 | | **aria-agent-sandbox** | 可插拔 `Sandbox`:Docker(默认)/ Kata / Cube / Browser。 | | **aria-agent-browser** | 浏览器 sandbox:六镜像 `BrowserCatalog` + `BrowserSandbox`(复用 Docker/Kata)+ 浏览器工具族。 | | **aria-agent-core** | 统一的 agent 运行时:`recall → 模型 → memorize`,工具在 sandbox 中执行。 | | **ariacompute-agent** | UniFFI `cdylib`(`libaria-agent_ffi`):`SdkAgent` / `SdkSession` / `create_agent`。 | | **aria-agent-cloud** | axum 服务:Postgres + pgvector(元数据**与**上下文),OpenAI beta Agents API。`/v1/agents`、`/v1/agents/sessions`、SSE `/v1/agents/sessions/{id}/events/stream`。 | | **bindings** | `bindings/swift`(SwiftPM)与 `bindings/kotlin`(Android)。 | | **sdk/js** | npm `@ariacompute/agent` —— 镜像 `@openai/agents`(`Agent`、`run`、`runStreamed`、`tool`、`Session`)。 | | **sdk/python** | pip `ariacompute-agent` —— 镜像 `openai-agents`(`Agent`、`Runner`、`function_tool`、`Session`)。 | > **存储边界:** **云端**把对话 / 长期上下文保存在 Postgres + pgvector >(`context_fragments`,按 principal 分片)—— pgvector 为必需,缺少则启动失败。 > **端侧** SDK 则保留其嵌入式 sled 存储。详见 >`docs/adr/0010-context-storage-pgvector.md`。
[![][back-to-top]](#readme-top)
## 🚀 快速开始 ```bash # 1. codex 子模块 + 根工作区补丁(见上文「codex 子模块」) git submodule update --init --depth 1 codex git -C codex apply ../patch/0001-ARIACOMPUTE-PATCH-make-codex-a-root-cargo-workspace.patch # 2. 构建 cargo build --workspace # 3. 测试 cargo test --workspace # 4. lint cargo clippy --workspace --all-targets -- -D warnings # 5. 生成 Swift/Kotlin 绑定 # = cargo run -p aria-agent-ffigen just ffi # 6. 运行云端服务 export DATABASE_URL=postgres://postgres:postgres@localhost:5432/agent export OPENAI_API_KEY=sk-... cargo run -p aria-agent-cloud ```
[![][back-to-top]](#readme-top)
## 🧰 命令行 `aria-agent` 可执行文件(crate `aria-agent-cloud`)既是云端服务(默认子命令 `serve`),也是可自更新的命令行工具。CLI 配置存放在配套的 `~/.ariacompute/agent-cli.yml`(可用 `ARIA_COMPUTE_HOME` 覆盖主目录)。 ```bash # 交互式选择 Releases 源(默认 github,或 gitee)。 # 选择会作为 `upgrade_url` 写入 ~/.ariacompute/agent-cli.yml。 aria-agent setup # 1) github -> https://github.com/ariacompute # 2) gitee -> https://gitee.com/ariacompute # 查看当前 CLI 配置状态。 aria-agent setup --status # 删除 CLI 配置文件。 aria-agent setup --clear # 从 Releases 自更新本程序与 libaria-agent_ffi。 # 读取 agent-cli.yml 中的 `upgrade_url`;用 --url 可临时覆盖。 aria-agent upgrade # 最新稳定版 aria-agent upgrade 0.7.2 # 指定版本 aria-agent upgrade --url https://github.com/ariacompute # 启动云端 HTTP 服务(默认子命令)。读取 agent-cli.yml,并把 # ARIA_AGENT_FFI_LIB 指向 ~/.ariacompute/lib。 aria-agent serve # --port 监听端口(默认:CLOUD_PORT 环境变量或 3000) aria-agent serve --port 3000 ``` > `serve` 子命令同样会读取 `~/.ariacompute/agent-cli.yml`,并将 > `ARIA_AGENT_FFI_LIB` 指向 `~/.ariacompute/lib`(`upgrade` 把 > `libaria-agent_ffi` 安装在此处),方便原生 SDK 找到该 cdylib。
[![][back-to-top]](#readme-top)
## 🛳 部署 云端服务自带 `Dockerfile` 与 `docker-compose.yml`,可一键部署(Postgres + `aria-agent` axum 服务)。所有配置都通过本地 `.env` 文件提供。 ### 1. 配置 复制模板并修改取值: ```bash cp .env.example .env ``` | 变量 | 默认值 | 说明 | |------|--------|------| | `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | `postgres` / `postgres` / `agent` | Postgres(pgvector)账号与库:元数据 **与** 上下文记忆。 | | `DATABASE_URL` | `postgres://postgres:postgres@postgres:5432/agent` | 连接串。主机名 `postgres` 即 compose 服务名。 | | `OPENAI_API_KEY` | _(空)_ | OpenAI 密钥,用于模型调用;仅 stub/离线可留空。 | | `AGENT_CLOUD_API_KEY` | _(空)_ | 引导/管理员密钥。设置后作为 Admin principal(以 `Authorization: Bearer ` 或 `ApiKey ` 携带);管理员可经 `POST /v1/api-keys` 发放租户密钥。无有效密钥的请求返回 `401`。留空则为开放模式(`default` 租户)。 | | `API_KEY_CACHE_TTL_SEC` | `60` | API key 解析结果的缓存时间(秒)。 | | `RUST_LOG` | `info` | Rust 日志级别:`error` \| `warn` \| `info` \| `debug` \| `trace`。 | | `CLOUD_PORT` | `3000` | 对 `cloud` 服务**直连**暴露的主机端口(容器内始终监听 3000)。 | | `DOCKER_HOST` | `unix:///var/run/docker.sock` | sandbox 使用的 Docker Engine API 端点(DooD)。可改为远程 / DinD 守护进程。 | | `ARIA_DOCKER_SOCKET` | _(未设置)_ | 显式指定 sandbox socket 路径,优先于自动探测(如 macOS 的 `~/.docker/run/docker.sock`)。 | | `DOCKER_GID` | `998` | 宿主 `docker` 组(拥有 `/var/run/docker.sock`)的 GID。运行期镜像会重建该组并把非 root 用户 `aria` 加入,从而无需 `root` 即可访问 socket。 | | `SANDBOX_CPUS` | `1.0` | 每个工具的 sandbox CPU 上限(如 `1.0`、`0.5`)。通过 Engine API 作用于 Docker 与 Kata。 | | `SANDBOX_MEMORY` | `512m` | 每个工具的 sandbox 内存上限,可读单位(`512m`、`1g` …)。作用于 Docker 与 Kata;Cube 为 best-effort。 | | `SANDBOX_PIDS_LIMIT` | `256` | sandbox 内最大进程数(pids cgroup)。作用于 Docker 与 Kata;Cube 为 best-effort。 | | `NGINX_PORT` | `80` | `nginx` 反向代理对外暴露的主机端口(转发到 `cloud:3000`)。 | | `ARIACOMPUTE_BROWSER_IMAGE_` | _(未设置)_ | 浏览器 sandbox 按引擎的镜像覆盖;`` ∈ `SERVO` / `OBSCURA` / `CHROMIUM` / `GOSUB` / `CAMOUFOX` / `LIGHTPANDA`(如 `ARIACOMPUTE_BROWSER_IMAGE_CHROMIUM=my/playwright:latest`)。缺省回退内置默认值。 | > `.env` 已被 git 忽略;`.env.example` 是提交到仓库的模板。Compose 会自动加载 > `.env` 做变量替换,且 `cloud` 服务通过 `env_file` 直接读取它,因此容器进程能 > 拿到每一个变量。 > **客户端密钥一致性。** 每个客户端(例如用 `-PagentCloudApiKey=...` 构建的 cockpit > Android App)会把 `AGENT_CLOUD_API_KEY`(或经 `POST /v1/api-keys` 发放的密钥)原样 > 作为 `Authorization: Bearer ` 发送。该密钥必须是**本服务端有效**的密钥,**或者** > 当本项为**空**(开放模式)时,客户端也**完全不发送**密钥。服务端不认识的密钥(例如 > `ak_live_x` 这类占位值)会返回 `401`。请保证本服务端密钥与所有客户端的密钥 > **保持一致(同一有效密钥)或同时留空。** ### 2. 构建 `cloud` 服务使用 `aria-agent-cloud` 镜像。你可以手动构建,也可以让 compose 帮你 构建——两种方式生成的是同一个镜像。 **方式 A — 手动构建,再 `up`(复用已构建镜像):** ```bash docker build \ --network=host \ --build-arg BUILD_PROXY=http://127.0.0.1:7897 \ --build-arg DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) \ -f Dockerfile -t aria-agent-cloud . docker compose up ``` **方式 B — 一条命令由 compose 构建并运行:** ```bash export BUILD_PROXY=http://127.0.0.1:7897 # 可选;不设置则直连公网 export DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) docker compose up --build ``` 构建参数(均为可选): | 参数 | 默认值 | 说明 | |------|--------|------| | `BUILD_PROXY` | _(空)_ | 构建期内 `apt`/`cargo`/`git` 使用的 HTTP(S) 代理。留空 ⇒ 直连公网。配合 `--network=host`(或 compose 的 `build.network: host`),使构建容器内 `127.0.0.1` 即宿主机侧代理(如 `127.0.0.1:7897` 的 Clash)。 | | `DOCKER_GID` | `998` | 拥有 `/var/run/docker.sock` 的宿主机 `docker` 组 GID;非 root 的 `aria` 用户加入该组后即可访问挂载的 socket。 | | `APT_MIRROR` | `mirrors.aliyun.com` | apt 镜像源 *主机名*(如 `mirrors.tuna.tsinghua.edu.cn`、`mirrors.ustc.edu.cn`)。 | > 构建使用 `network: host` 是 **Linux** 下访问 `127.0.0.1` 代理的要求。在 Docker > Desktop(Mac/Windows)上构建阶段不支持 host 网络——请将代理监听在 `0.0.0.0`, > 或不加 `network: host` 而改用可路由的宿主机地址作为代理。 ### 3. 运行 ```bash docker compose up --build ``` #### API 冒烟测试 服务起来之后,可运行一次性 HTTP 冒烟测试(**不构建、不启动、不停止**,只探测已运行实例)。它加载与 `docker compose up` **同一份** `.env`(缺失时回退到 `.env.example`)以读取 `AGENT_CLOUD_API_KEY`,而 `BASE_URL` 与 `EVENTS_TEST` 通过命令行参数或环境变量传入: ```bash bash scripts/run-api-tests.sh --base-url http://localhost:3000 # 或: BASE_URL=http://localhost:3000 bash scripts/run-api-tests.sh # 可选:同时跑模型驱动的 /events 与 /events/stream 端点 bash scripts/run-api-tests.sh -u http://localhost:3000 --events-test ``` 脚本会对每条路由(agents、sessions、memory、relations、subagents、api-keys,以及已识别但未实现的 501 stub)断言状态码与关键 JSON 字段,打印 `TOTAL / PASS / FAIL / SKIP`,并以失败数作为退出码。`/events` 与 `/events/stream` 默认 **跳过**(不计失败),仅在 `EVENTS_TEST=1`(或 `--events-test`)且已配置模型网关时才执行。 云端服务会等待 Postgres 健康后再启动;表结构(`agents` / `runs`)在启动时由 `ensure_schema` 自动创建,无需初始化脚本。API 可通过**任一**入口访问: - **直连:** `http://localhost:3000`(或 `http://localhost:${CLOUD_PORT}`)——`cloud` 服务端口,便于调试或不需要 nginx 时使用。 - **经 nginx:** `http://localhost:80`(或 `http://localhost:${NGINX_PORT}`)——`nginx` 反向代理,转发到 `cloud:3000`(推荐入口,会附带 `X-Forwarded-*` 头且不缓冲 SSE 流式响应)。 `GET /healthz` 是存活探针:由 `cloud` 服务自身提供(无需 API key),`nginx` 将 `/healthz` 转发到上游,因此结果反映真实后端健康状态。`cloud` 的 compose 健康检查执行 `curl -fsS http://localhost:3000/healthz`,`nginx` 仅在 `cloud` 健康后才启动。 ### 4. 上下文存储(PostgreSQL + pgvector) 对话/长期上下文保存在 Postgres 的 `context_fragments` 表(`vector(256)` + HNSW 余弦索引),按 `principal_id` 分片。compose 使用 `pgvector/pgvector:pg16` 镜像;服务启动时会执行 `CREATE EXTENSION IF NOT EXISTS vector` 并校验,缺少扩展时**直接启动失败** (不做关键词降级)。 端侧 SDK 继续使用自己的嵌入式 sled 存储(`aria-agent-memo`),不受此约束。 ### 5. 通过 Docker socket 的 sandbox 当 agent 调用需要隔离的工具时,云端服务通过 Docker **Engine API**(默认的 `docker` sandbox provider)拉起一个短生命周期容器来执行。该方式对齐 playground: `cloud` 容器挂载**宿主** Docker socket(`/var/run/docker.sock`,DooD),直接访问 宿主守护进程——**镜像内不安装 `docker` CLI**。 端点由 `DOCKER_HOST` 控制(默认 `unix:///var/run/docker.sock`),因此你可以不改 代码地改为指向远程守护进程或 Docker-in-Docker 边车(如 `tcp://dind:2375`)。 工具容器运行在宿主守护进程上,命名为 `aria-sandbox-`,会话结束时会被强制 移除。 连接在**首次 sandbox 调用时惰性建立**,因此守护进程缺失只会让工具执行报错,而不 会导致启动 panic。`DOCKER_HOST` / `ARIA_DOCKER_SOCKET` 未设置时,会按顺序探测常见 socket 路径——包括 `~/.docker/run/docker.sock`(**macOS Docker Desktop** 的默认位置, `/var/run/docker.sock` 只有在开启 Docker Desktop 的「默认 socket」选项后才存在)、 Colima(`~/.colima/default/docker.sock`)以及 rootless Podman 路径。 `cloud` 容器以非 root 用户 `aria` 运行。为访问挂载的 socket,它会加入一个 GID 由 `DOCKER_GID`(默认 `998`)指定的 `docker` 组,该 GID 必须与宿主上拥有 `/var/run/docker.sock` 的组一致(`stat -c '%g' /var/run/docker.sock`,常为 998 或 999)。若不一致,可用 `docker compose build --build-arg DOCKER_GID=` 重新构建, 或在 compose 中回退为 `user: root`。若想彻底关闭 sandbox,只需注释掉 `/var/run/docker.sock` 的挂载卷(`DOCKER_GID` 构建参数随之失效)。 > 挂载宿主 socket 等于把宿主 Docker 的 root 级控制权交给了 cloud 容器,请仅在 > 受信任的单租户部署中使用。若需更强隔离,可将 `DOCKER_HOST` 指向独立守护进程 > (DinD / 远程)。 #### 自动探测 `DOCKER_GID` `.env` 中的 `DOCKER_GID` 必须与宿主上拥有 socket 的组一致。可一次性读出并写回 `.env`: ```bash DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) sed -i "s/^DOCKER_GID=.*/DOCKER_GID=$DOCKER_GID/" .env echo "DOCKER_GID set to $DOCKER_GID" ``` 随后重新构建,使运行期镜像采用新的组: `docker compose build --build-arg DOCKER_GID=$DOCKER_GID`。 #### sandbox 资源限制 每个工具容器都受 `SANDBOX_CPUS`、`SANDBOX_MEMORY`、`SANDBOX_PIDS_LIMIT` 限制(见上表)。**Docker** 与 **Kata** provider 直接经 Engine API 下发 (`HostConfig` 的 `NanoCpus`、`Memory`、`PidsLimit`);**Kata** 还会让容器运行在 `kata` runtime 下。**Cube** provider 则映射为 best-effort 的 `cube` CLI 参数 (`--cpus` / `--memory`),可能需要按你部署的 Cube 运行时调整。取值非法时会回退 默认值并记录告警,因此服务总能正常启动。 ### 6. 数据卷 | 卷 | 对应路径 | 用途 | |----|----------|------| | `pgdata` | Postgres(pgvector) | `agents` / `runs` 元数据 **与** `context_fragments`(上下文记忆)。 |
[![][back-to-top]](#readme-top)
## 🔗 codex 子模块 本仓库以 git 子模块方式依赖 OpenAI 的 [`codex`](https://github.com/openai/codex),位于 `codex/`。上游 codex 的 Rust 工作区位于 `codex/codex-rs/`,**仓库根目录没有 `Cargo.toml`**,因此通过 git 依赖(`package = "codex-*"`)无法解析,必须打一个补丁。 `patch/0001-ARIACOMPUTE-PATCH-make-codex-a-root-cargo-workspace.patch` 把 `codex-rs` 的工作区定义提升到仓库根目录(并把内部的 `path` 依赖统一加上 `codex-rs/` 前缀)。这正是 `ariacompute/codex` fork(branch `main`) 所做的改动。在检出后,于 **`codex/` 子模块内部**应用该补丁: ```bash # 1. 克隆子模块(浅克隆) git submodule update --init --depth 1 codex # 2. 在子模块内部应用根工作区补丁 git -C codex apply ../patch/0001-ARIACOMPUTE-PATCH-make-codex-a-root-cargo-workspace.patch ``` 应用后,`codex/Cargo.toml` 会出现在仓库根目录,cargo 即可传递性地解析 `codex-*` 各个 crate。该补丁**不会**提交进子模块——它存放在仓库根的 `patch/` 目录——因此每次重新初始化子模块时都需重新执行。如需撤销补丁并恢复 子模块原状: ```bash git -C codex checkout -- . && git -C codex clean -fd ``` > 该补丁是 `docs/adr/0005-codex-integration.md` 描述的深度编译集成所必需的。 > 若你只构建不依赖 codex crate 的本仓库 crate,可以跳过此步。
[![][back-to-top]](#readme-top)
## 🌐 浏览器 sandbox `aria-agent-browser` 把一台真实的、隔离的浏览器嵌入到 agent 运行时中。它与命令 执行复用同一套 `Sandbox` trait 与 Docker/Kata 容器路径,因此每个浏览器容器同样 受 `SANDBOX_CPUS`、`SANDBOX_MEMORY`、`SANDBOX_PIDS_LIMIT` 限制,并经由 `DOCKER_HOST` / `ARIA_DOCKER_SOCKET` 访问守护进程,与 shell 工具完全一致。 `BrowserCatalog` 登记了 **六** 种浏览器镜像,每种各自作为一个独立的 sandbox 容器 启动: | Kind | 引擎 | 说明 | |------|------|------| | `chromium` | Playwright + Chromium | 默认。完整自动化:navigate / extract / click / fill / screenshot / evaluate,并集成 `playwright-captcha` 处理验证码。 | | `camoufox` | Camoufox(基于 Firefox) | 反检测 / 反爬画像;Playwright 兼容。 | | `lightpanda` | Lightpanda | 面向 AI 的原生无头浏览器;轻量抽取。 | | `obscura` | Chromium 隐身层 | 在 Chromium 之上叠加指纹对抗 / 反机器人层(`obscura`)。 | | `servo` | Servo(Rust) | 原生引擎;仅 navigate + extract,其余操作降级为 `Unsupported`。 | | `gosub` | Gosub engine(Rust) | 原生引擎;仅 navigate + extract,其余操作降级为 `Unsupported`。 | 用 `sandbox_provider = "browser"`(默认 kind `chromium`)开启。可在启动时选择 引擎,**也可在运行时切换**——`browser_use` 工具会销毁当前容器、按 catalog 拉起新 镜像;若镜像不可用则安全回退(不 panic、不掉线): ```json { "name": "browser_use", "arguments": { "engine": "camoufox" } } ``` 浏览器能力以工具族形式暴露,并优先经 `ToolHandler` 分派(未命中再回退 shell): `browser_navigate`、`browser_extract`、`browser_click`、`browser_fill`、 `browser_screenshot`、`browser_evaluate`、`browser_solve_captcha`,以及用于切换的 `browser_use`。示例工具调用: ```json { "name": "browser_navigate", "arguments": { "url": "https://example.com" } } { "name": "browser_extract", "arguments": { "mode": "markdown" } } ``` 镜像标签可按引擎经 `ARIACOMPUTE_BROWSER_IMAGE_`(如 `ARIACOMPUTE_BROWSER_IMAGE_CHROMIUM`)覆盖,缺省回退内置默认值。详见 `docs/adr/0012-browser-sandbox.md`。
[![][back-to-top]](#readme-top)
## 🧠 记忆图与 memo 工具族 agent 不只是读/写 `aria-agent-memo` 存储——它会在**运行过程中主动管理自己的记忆**: * **记忆图(memory graph)。** 每一轮在向量 `recall` 之外,用受界的图 `expand` (以召回片段为种子)补充上下文,并把该轮 user↔assistant 片段用 `Temporal` 关系相连;后续轮次可沿对话图检索上下文,而不仅依赖嵌入相似度。 * **自动压缩(auto-compact)。** 当某会话片段数超过配置阈值(SDK 默认 `32`)时, 运行循环将其 `compact` 为单条笔记(幂等——多次压缩复用同一笔记 id,历史有界)。 * **memo 工具族。** 模型可调用四个长期记忆工具,经 `ToolHandler` 在 shell 回退之前 分派: | 工具 | 用途 | |------|------| | `memo_store` | 按 key 持久化一条长期记忆(如用户偏好)。 | | `memo_get` | 按 key 读回一条长期记忆。 | | `memo_search` | 对会话记忆做语义(向量)检索。 | | `memo_related` | 检视记忆图:列出 `from`/`to`/`kind`/`score` 各边。 | 记忆图与自动压缩都由 `AgentConfig::memory_graph` 控制(在 `aria-agent-core` 中默认 关闭以保证其单测封闭;SDK 默认开启)。`aria-agent-core` 只依赖 `ContextStore` trait、 绝不依赖 `aria-agent-memo`,因此本地(aria memo / SQLite)与云端(pgvector)后端可互换。 详见 `docs/adr/0013-agent-memo-integration.md`。
[![][back-to-top]](#readme-top)
## 🔌 OpenAI Agents API 兼容性 `POST /v1/agents/sessions/{id}/events/stream` 采用 **OpenAI beta Agents 流式协议**, 因此 OpenAI 客户端可以零改造直接消费。每个 SSE `data:` 帧都是 `agent.*` 事件, 带 `event_id`、`session_id`、`turn_id` 与单调递增的 `sequence_number`: | 事件 | 载荷 | |---|---| | `agent.turn.created` | `turn{id,status,session_id,agent_id}` | | `agent.turn.in_progress` | 阶段边界;额外带 `phase`(`recall` / `model` / `tool_exec` / `loop_guard`)与 `label` | | `agent.turn.item.added` | `item` —— `message`(assistant,`role`)或 `function_call`(`name`、`arguments`) | | `agent.turn.item.done` | 完成的 `item`;`function_call` 还附带执行结果 `result` | | `agent.turn.output_text.delta` | `item_id`、`delta` | | `agent.turn.output_text.done` | `item_id`、`text` | | `agent.turn.completed` | 终止事件:`turn{status:"completed", output}` | | `agent.turn.failed` | 终止事件:`turn{status:"failed"}`、`error` | `agent.turn.completed`(或 `agent.turn.failed`)即为终止事件:客户端收到它便 意味着运行结束(没有额外的 `data: [DONE]` 尾帧)。 编排所需、但 beta 协议未建模的字段(agentic `phase`、工具 `result`)都被放在 这些帧**内部**作为额外字段,严格的 OpenAI 客户端会自动忽略,不会产生私有 `aria.*` 帧。 推荐使用上面的 SDK 消费该协议 —— 帧解析由 SDK 完成(对应 `result.final_output` / `result.finalOutput`): ```python from ariacompute_agent import Agent, Runner agent = Agent(name="Agent Demo") streamed = await Runner.run_streamed(agent, "tell me a joke") async for event in streamed.events: if event["type"] == "agent.turn.output_text.delta": print(event["delta"], end="", flush=True) ``` ### 默认智能体 `ensure_schema` 会预置一个默认智能体 —— `id: agent-demo`、 `name: Agent Demo` —— 让全新部署(以及 Aria Playground)开箱即可运行。 已用旧默认值(`playground-demo`)预置过的部署,会在下次启动时被就地重命名。 用 `GET /v1/agents` 可列出当前密钥可见的智能体(管理员看到全部,租户只看自己 的)。
[![][back-to-top]](#readme-top)
## 📦 SDK 示例 以下示例**全部使用官方 SDK**,不再手写 HTTP。所有 SDK 都镜像 OpenAI Agents SDK 的用法:定义 `Agent`,然后运行。 | 语言 | 安装 | 包 / 模块 | |---|---|---| | Python | `pip install ariacompute-agent` | `ariacompute_agent`(镜像 `openai-agents`) | | TypeScript / JavaScript | `npm i @ariacompute/agent` | `@ariacompute/agent`(镜像 `@openai/agents`) | | Rust | `cargo add ariacompute-agent` | `aria_agent_ffi`(进程内 / 原生) | | Swift | SwiftPM / CocoaPods `AriaComputeAgent` | `bindings/swift`(进程内 / 原生) | | Kotlin | `com.ariacompute:agent` | `bindings/kotlin`(进程内 / 原生) | JS / Python SDK 是服务仍然暴露的 beta Agents REST API 的薄客户端 —— 协议细节见 [OpenAI Agents API 兼容性](#-openai-agents-api-兼容性);Rust / Swift / Kotlin SDK 把运行时**嵌入进程内**,上下文保存在端侧存储(无服务端、无网络)。 云端 SDK 需要服务已启动(见「快速开始」),并通过 `ARIA_AGENT_BASE_URL` (默认 `http://localhost:3000`)与 `ARIA_AGENT_API_KEY`(当设置了 `AGENT_CLOUD_API_KEY` 时)配置连接。 每个示例都遵循相同的三步流程: 1. **一次性运行(one-shot run)** —— `run(agent, input)` / `Runner.run(agent, input)`; 2. **复用同一会话继续对话**(先用 `memorize` 写入一条长期记忆); 3. **流式运行(streaming run)** —— `runStreamed` / `Runner.run_streamed`。 长期记忆保存在 agent 自己的上下文存储中:`memorize(key, value)` 会把一条事实以 `long_term` 上下文片段持久化,同一会话的后续轮次可以语义召回它(重启后依然有效)。 云端 SDK 的存储是 Postgres + pgvector;原生 SDK 的存储是端侧(on-device)存储。 ### 记忆后端:cloud / local / both 每个 SDK 都可以选择记忆上下文存放在哪里: | 后端 | 存储 | 说明 | |---|---|---| | `cloud` | agent-cloud(Postgres + pgvector) | 跨设备共享;需要服务在运行 | | `local` | **aria memo**(SQLite,`memo.db`) | 端侧、可离线;可用 `aria-memo list --json` 查看 | | `both` | 云端 **与** 本地 | 双写;读取合并去重(云端副本优先) | 既可以在 agent / session 构造时配置,也可以单次调用覆盖: ```python agent = Agent(name="History tutor", memory_backend="both", memo_db="~/.ariacompute/memo.db") session.memorize("user_name", "Ada") # -> 写入 both session.recall("user_name", backend="local") # -> 只读 local 副本 ``` ```typescript const agent = new Agent({ name: "History tutor", memory: "both", memoDb: "~/.ariacompute/memo.db" }); await session.memorize("user_name", "Ada"); // -> 写入 both await session.recall("user_name", { backend: "local" }); // -> 只读 local 副本 ``` `both` 不伪造数据:单端失败只记录告警并容忍,双端都失败才报错。 ### Python ```python import asyncio from ariacompute_agent import Agent, Runner, Session, function_tool @function_tool def history_fun_fact() -> str: """Return a short history fact.""" return "Sharks are older than trees." agent = Agent( name="History tutor", instructions="Answer history questions clearly and concisely.", model="gpt-4o-mini", tools=[history_fun_fact], # 记忆后端:cloud | local(aria memo)| both memory_backend="both", memo_db="~/.ariacompute/memo.db", ) async def main() -> None: session = Session.create(agent) # 1) 一次性运行 first = await Runner.run(agent, "罗马帝国何时灭亡?", session=session) print(first.final_output) # 长期记忆保存在该会话的上下文存储(Postgres + pgvector): # `memorize` 持久化一条事实,后续每一轮都能召回。 session.memorize("user_name", "Ada") # 2) 复用同一会话继续对话 —— 回复可以同时引用前面的轮次与这条记忆 second = await Runner.run(agent, "我叫什么名字?", session=session) print(second.final_output) print(session.recall("user_name")) # -> "Ada" print(session.recall("user_name", backend="local")) # 只读 local(aria memo)副本 # 3) 流式运行 streamed = await Runner.run_streamed(agent, "讲一个冷知识", session=session) async for event in streamed.events: if event["type"] == "agent.turn.output_text.delta": print(event["delta"], end="") final = await streamed.completed print(final.final_output) asyncio.run(main()) ``` ### 错误处理与限制 传输层会把失败统一归一化为带类型的 `AriaError`(`kind ∈ {auth, network, api, config, unknown}`):`401/403` → `auth`,其他非 2xx → `api`(携带 HTTP `status`),连接失败 → `network`,本地误用(例如非正的 `max_turns`)→ `config`。 请基于 `err.kind` 分支,而不是去匹配错误文案。 ```python from ariacompute_agent import AriaError, AriaErrorKind try: final = await Runner.run(agent, "hi", session=session, max_turns=1) except AriaError as err: if err.kind == AriaErrorKind.AUTH: print("check your API key") elif err.kind == AriaErrorKind.CONFIG: print("bad option:", err) ``` 只读的会话端点(不修改上下文)同样可用:`session.get_items()` 与 `session.get_turns()`(GET `…/items`、`…/turns`)。JS SDK 镜像了上述全部能力 (`AriaError`、`run`/`runStreamed` 的 `maxTurns`、`Session.getItems()`/`getTurns()`)。 ### TypeScript ```typescript import { Agent, run, runStreamed, tool, Session } from "@ariacompute/agent"; const historyFunFact = tool({ name: "history_fun_fact", description: "Return a short history fact.", parameters: { type: "object", properties: {} }, }); const agent = new Agent({ name: "History tutor", instructions: "Answer history questions clearly and concisely.", model: "gpt-4o-mini", tools: [historyFunFact], // 记忆后端:cloud | local(aria memo)| both memory: "both", memoDb: "~/.ariacompute/memo.db", }); const session = await Session.create(agent); // 1) 一次性运行 const first = await run(agent, "罗马帝国何时灭亡?", { session }); console.log(first.finalOutput); // 长期记忆保存在该会话的上下文存储(Postgres + pgvector): // `memorize` 持久化一条事实,后续每一轮都能召回。 await session.memorize("user_name", "Ada"); // 2) 复用同一会话继续对话 —— 回复可以同时引用前面的轮次与这条记忆 const second = await run(agent, "我叫什么名字?", { session }); console.log(second.finalOutput); console.log(await session.recall("user_name")); // -> "Ada" console.log(await session.recall("user_name", { backend: "local" })); // 只读 aria memo // 3) 流式运行 const streamed = await runStreamed(agent, "讲一个冷知识", { session }); for await (const event of streamed.events) { if (event.type === "agent.turn.output_text.delta") { process.stdout.write(event.delta); } } console.log((await streamed.completed).finalOutput); ``` ### Rust(进程内) ```rust use aria_agent_ffi::{create_agent, SdkAgentListener, SdkStreamEvent}; struct Printer; impl SdkAgentListener for Printer { fn on_event(&self, event: SdkStreamEvent) { if let SdkStreamEvent::Token { text } = event { print!("{text}"); } } } fn main() -> Result<(), Box> { // 无需传入会话:运行时为每个 agent 实例分配一个隔离的上下文作用域 // `memory` 选择 cloud | local(aria memo)| both,默认 `local` let agent = create_agent_with(SdkAgentConfig { agent_name: "History tutor".into(), model: "gpt-4o-mini".into(), memory: SdkMemoryConfig { backend: "both".into(), local_db_path: "~/.ariacompute/memo.db".into(), cloud_base_url: "http://localhost:3000".into(), cloud_api_key: String::new(), }, ..Default::default() })?; // 1) 一次性运行 println!("{}", agent.run("罗马帝国何时灭亡?".into())?); // 长期记忆保存在端侧存储:`memorize` 把一条事实以 `long_term` 片段持久化, // 该 agent 的后续轮次可以召回它。 let session = agent.session(); session.memorize("user_name".into(), "Ada".into())?; // 2) 复用同一会话继续对话(同一 agent / 会话作用域)—— 回复可引用前面的轮次与记忆 println!("{}", agent.run("我叫什么名字?".into())?); println!("{:?}", session.recall("user_name".into(), None)?); // -> Some("Ada") // 单次覆盖:只读 aria memo 副本 println!("{:?}", session.recall("user_name".into(), Some("local".into()))?); // 3) 流式运行 —— 事件推送给监听器 agent.run_stream("讲一个冷知识".into(), Box::new(Printer))?; Ok(()) } ``` ### Swift(进程内) 嵌入 `libaria-agent_ffi` 与生成的 `AriaComputeAgent` 模块(见 `bindings/swift/README.md`)。 ```swift import AriaComputeAgent do { // 无需传入会话:运行时为每个 agent 实例分配一个隔离的上下文作用域 // `memory` 选择 cloud | local(aria memo)| both,默认 `local` let agent = try createAgentWith(config: SdkAgentConfig( agentName: "History tutor", instructions: "", model: "gpt-4o-mini", sandboxProvider: "docker", memory: SdkMemoryConfig( backend: "both", localDbPath: "~/.ariacompute/memo.db", cloudBaseUrl: "http://localhost:3000", cloudApiKey: "" ) )) // 1) 一次性运行 print(try agent.run("罗马帝国何时灭亡?")) // 长期记忆保存在端侧存储:`memorize` 把一条事实以 `long_term` 片段持久化, // 该 agent 的后续轮次可以召回它。 let session = agent.session() try session.memorize(key: "user_name", value: "Ada") // 2) 复用同一会话继续对话(同一 agent / 会话作用域)—— 回复可引用前面的轮次与记忆 print(try agent.run("我叫什么名字?")) print(try session.recall(key: "user_name", backend: nil) ?? "") // -> "Ada" // 单次覆盖:只读 aria memo 副本 print(try session.recall(key: "user_name", backend: "local") ?? "") // 3) 流式运行 —— 事件交给监听器处理 try agent.runStream("讲一个冷知识", listener: Printer()) } catch { print(error) } ``` ### Kotlin(进程内) 添加 `com.ariacompute:agent` 依赖与 `libaria-agent_ffi` 原生库(见 `bindings/kotlin/agent-sdk/README.md`)。 ```kotlin import com.ariacompute.agent.uniffi.aria_agent_ffi.* fun main() { // 无需传入会话:运行时为每个 agent 实例分配一个隔离的上下文作用域 // `memory` 选择 cloud | local(aria memo)| both,默认 `local` val agent = createAgentWith( SdkAgentConfig( agentName = "History tutor", instructions = "", model = "gpt-4o-mini", sandboxProvider = "docker", memory = SdkMemoryConfig( backend = "both", localDbPath = "~/.ariacompute/memo.db", cloudBaseUrl = "http://localhost:3000", cloudApiKey = "" ) ) ) // 1) 一次性运行 println(agent.run("罗马帝国何时灭亡?")) // 长期记忆保存在端侧存储:`memorize` 把一条事实以 `long_term` 片段持久化, // 该 agent 的后续轮次可以召回它。 val session = agent.session() session.memorize("user_name", "Ada") // 2) 复用同一会话继续对话(同一 agent / 会话作用域)—— 回复可引用前面的轮次与记忆 println(agent.run("我叫什么名字?")) println(session.recall("user_name", null)) // -> "Ada" // 单次覆盖:只读 aria memo 副本 println(session.recall("user_name", "local")) // 3) 流式运行 —— 事件交给监听器处理 agent.runStream("讲一个冷知识", Printer()) } ``` #### 编译 Android AAR `bindings/kotlin/agent-sdk` 中的 Kotlin 绑定以 Android library AAR 形式发布, 其中打包了 `arm64-v8a` 与 `x86_64` 两种 ABI 的原生库 `libaria_agent_ffi.so`。 从源码编译步骤: 1. 前置条件:Android SDK + NDK(设置 `ANDROID_NDK`),以及 Rust 的 Android 目标: ```bash rustup target add aarch64-linux-android x86_64-linux-android ``` 新增 `agent/.cargo/config.toml`,让这两个目标使用 NDK 的 clang 链接器(API 24),例如: ```toml [target.aarch64-linux-android] linker = "aarch64-linux-android24-clang" [target.x86_64-linux-android] linker = "x86_64-linux-android24-clang" ``` (构建时须将 NDK 的 `toolchains/llvm/prebuilt/linux-x86_64/bin` 加入 `PATH`。) 2. 为每个 ABI 编译并放置原生 cdylib: ```bash cargo build -p ariacompute-agent --target aarch64-linux-android --release cargo build -p ariacompute-agent --target x86_64-linux-android --release cp target/aarch64-linux-android/release/libaria_agent_ffi.so \ bindings/kotlin/agent-sdk/src/main/jniLibs/arm64-v8a/ cp target/x86_64-linux-android/release/libaria_agent_ffi.so \ bindings/kotlin/agent-sdk/src/main/jniLibs/x86_64/ ``` (仅当 FFI 接口发生变化时,才需要用 `just ffi` 重新生成 Kotlin 绑定。) 3. 安装 JDK 17 与 Gradle 8.9+(本项目不含 Gradle wrapper,因此 `java` 与 `gradle` 都必须在 `PATH` 中)。若提示 `JAVA_HOME is not set and no 'java' command could be found`,请安装 JDK 并导出 `JAVA_HOME`: ```bash # Ubuntu/Debian:OpenJDK 17(AGP 8.5 要求 JDK 17) sudo apt install openjdk-17-jdk export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java)))) # 持久化(例如将上述 export 写入 ~/.bashrc) ``` 随后安装 Gradle 8.9+——注意 apt 源中的版本是旧的 4.x,**无法使用**: ```bash # Ubuntu/Debian:snap 渠道提供较新的 8.x(如 8.14.4) sudo snap install gradle # 若受 confinement 限制可加 --classic # 备选 —— 官方压缩包(固定任意 8.9+ 版本): # curl -L https://services.gradle.org/distributions/gradle-8.9-bin.zip -o /tmp/gradle.zip # unzip /tmp/gradle.zip -d /opt && export PATH="/opt/gradle-8.9/bin:$PATH" # 备选 —— SDKMAN: # sdk install gradle 8.9 ``` (**不要**执行 `sudo apt install gradle`,它提供的是 Gradle 4.4.1,对当前 AGP/Kotlin 插件版本而言过旧。) 4. 构建 AAR: ```bash cd bindings/kotlin/agent-sdk gradle assembleRelease ``` 产物为 `bindings/kotlin/agent-sdk/build/outputs/aar/agent-sdk-release.aar`。 AAR 版本跟随 agent 的 git tag(如 `v1.2.0` → `1.2.0`),可用 `ARIA_VERSION` 环境变量覆盖。 消费方可通过 `flatDir` 仓库或 Maven 仓库解析该 AAR(示例见 `cockpit/app` README)。
[![][back-to-top]](#readme-top)
## 🔏 信任库安全 2026-10 的 eMudhra(emSign)中间 CA 私钥泄露事件,对 agent 通过两层兜底: - **Rust(云端 + CLI 二进制)。** 出向 TLS(provider API 调用、`upgrade` 从 GitHub/Gitee Releases 下载)经 `reqwest` + `rustls-tls`,其信任库为编译进二进制的 **`webpki-roots`**, 在 `aria-agent-cloud` 中钉 `>=1.0.10`,从而移除被泄露的 emSign 根。二进制不走系统 bundle,因此锁升级是决定性修复。 - **JS SDK。** 构建与测试使用 **bun >= 1.4.4**;除对云端服务发起的 fetch 外,自身不做出向 TLS。 - **Python SDK。** 不打包信任库,依赖宿主 / 容器 `ca-certificates`。 `scripts/check-emudhra.sh` 离线扫描系统或镜像 CA bundle 中已知根/中间证书的 SHA-256 指纹(退出码 `0` 干净、`1` 命中、`2` 无法读取): ```bash scripts/check-emudhra.sh /etc/ssl/certs/ca-certificates.crt ```
[![][back-to-top]](#readme-top)
## 🔨 开发 | 目标 | 作用 | |---|---| | `cargo build --workspace` | 构建全部 crate | | `cargo test --workspace` | 运行 workspace 测试套件 | | `cargo clippy --workspace --all-targets -- -D warnings` | 以警告即错误方式 lint | | `just ffi` | 重新生成 Swift / Kotlin 绑定(需先构建 cdylib) | | `cargo run -p aria-agent-cloud` | 运行云端 API(需 Postgres + OpenAI 密钥) | | `aria-agent serve` | 启动云端 HTTP 服务(默认子命令) | | `aria-agent setup` / `upgrade` | 配置 Releases 源 / 自更新二进制与 cdylib | 任何依赖 `codex-*` 的 crate 在构建前都必须初始化 `codex` 子模块(并应用根工作区补丁)—— 参见 [codex 子模块](#-codex-子模块)。不依赖 codex 的 crate 无需子模块即可构建。
[![][back-to-top]](#readme-top)
## 📚 文档 详细设计记录位于 [`docs/adr/`](./docs/adr),以英文撰写: | 文档 | 覆盖内容 | |---|---| | [子模块策略](./docs/adr/0001-submodule-strategy.md) | 为何 `codex` 不进 workspace、以设计方式引用 | | [FFI 边界](./docs/adr/0002-ffi-boundary.md) | `ariacompute-agent` 稳定导出面与重新生成规则 | | [Sandbox 提供者](./docs/adr/0003-sandbox-providers.md) | Docker / Kata / Cube / Browser provider trait | | [Memo 存储](./docs/adr/0004-memo-storage.md) | `aria-agent-memo` 端侧存储表结构 | | [codex 集成](./docs/adr/0005-codex-integration.md) | 与 `codex` 子模块的深度编译集成 | | [多租户 API Key](./docs/adr/0007-multi-tenant-api-keys.md) | 管理员 / 租户密钥模型与 `POST /v1/api-keys` | | [SDK 接口](./docs/adr/0008-sdk-interface.md) | 无会话的 happy-path SDK 人体工学 | | [OpenAI beta Agents API](./docs/adr/0009-openai-beta-agents-api.md) | 流式事件信封契约 | | [上下文存储 pgvector](./docs/adr/0010-context-storage-pgvector.md) | 云端上下文为何强制 pgvector | | [记忆后端切换](./docs/adr/0011-memory-backend-switch.md) | `cloud` / `local` / `both` 选择与合并 | | [浏览器 sandbox](./docs/adr/0012-browser-sandbox.md) | 六镜像 `BrowserCatalog` 与 `BrowserSandbox` | | [Agent–memo 集成](./docs/adr/0013-agent-memo-integration.md) | 记忆图、自动压缩、memo 工具族 |
[![][back-to-top]](#readme-top)
## 📝 许可证 MIT,详见 [LICENSE](./LICENSE)。
[![][back-to-top]](#readme-top)
## 🤝 工程约定 本仓库遵循 Harness Engineering:仓库本身即 Agent 的上下文,治理它的文档与代码一同版本化。 - [`AGENTS.md`](AGENTS.md):Agent 工程上下文入口与目录索引 - [`requirements.md`](requirements.md):需求规格(功能边界 / 异常 / 验收,人审后编码) - [`task.md`](task.md):实施清单
[![][back-to-top]](#readme-top)
---
Copyright © 2026 [ariacompute][org].
This project is [MIT](./LICENSE) licensed.
[org]: https://github.com/ariacompute [release-link]: https://github.com/ariacompute/agent/releases [language-link]: ./Cargo.toml [license-link]: ./LICENSE [lastcommit-link]: https://github.com/ariacompute/agent/commits [sdks-link]: #-sdk-示例 [tests-link]: ./justfile [docs-link]: ./docs/adr [codex-link]: #-codex-子模块 [release-shield]: https://img.shields.io/github/v/release/ariacompute/agent?style=flat-square&labelColor=black&color=555 [language-shield]: https://img.shields.io/badge/language-Rust%20%2F%20Swift%20%2F%20Kotlin-555?style=flat-square&labelColor=black [license-shield]: https://img.shields.io/github/license/ariacompute/agent?style=flat-square&labelColor=black&color=555 [lastcommit-shield]: https://img.shields.io/github/last-commit/ariacompute/agent?style=flat-square&labelColor=black&color=555 [sdks-shield]: https://img.shields.io/badge/SDKs-5_languages-555?style=flat-square&labelColor=black [tests-shield]: https://img.shields.io/badge/tests-cargo_test--workspace-555?style=flat-square&labelColor=black [docs-shield]: https://img.shields.io/badge/docs-12_ADRs-555?style=flat-square&labelColor=black [codex-shield]: https://img.shields.io/badge/codex-submodule-555?style=flat-square&labelColor=black [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square