# 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