ari-api 是一个面向 Agent Runtime 的接口定义仓库。
它想解决的问题,与 Kubernetes 的 CRI(Container Runtime Interface)非常相似:
- 上层系统希望依赖一组稳定接口
- 下层运行时实现却高度异构
- 需要把“核心能力”与“可选能力”清晰分层
- 需要让调度器、UI、平台桥接层、远程网关都能用同一套语义驱动不同实现
对 ARI(Agent Runtime Interface)而言,下层不是 containerd / CRI-O,而是 codex、claudecode、cursor、gemini、opencode、qoder 这类 Agent CLI,或未来的远程 Agent Gateway。
一句话概括:
ARI 想成为 Agent 世界里的 CRI:为异构 Agent Runtime 提供稳定、可组合、可远程化的统一接口。
今天不同 Agent Runtime 的差异非常大:
- 会话创建方式不同
- 事件输出格式不同
- 权限审批机制不同
- 模型/Provider/模式切换能力不同
- 历史记录、记忆文件、技能、命令目录等扩展能力分布不一致
如果上层平台直接依赖某个具体 CLI:
- 适配成本会越来越高
- 新增 runtime 会牵动整个系统
- UI 与调度层很难形成稳定抽象
- 很多“本来属于协议层”的语义会被写死在实现细节里
ARI 的目的,就是把这些差异收敛为一套稳定协议:
- 上层只关心
Session、Turn、Event、PermissionRequest - 下层 runtime 负责把自己的私有行为映射成统一语义
- 可选能力通过 capability 暴露,而不是污染核心接口
ARI 的设计直接借鉴两类成熟实践:
cri-api 给出的最重要启发不是 “使用 gRPC”,而是:
- 把复杂系统拆成多个小接口
- 用顶层 service 聚合最小必需能力
- 在接口注释中写清楚语义契约、线程安全和生命周期要求
- 保持上层与具体 runtime 实现解耦
对应参考文件:cri-api/pkg/apis/services.go
codeclaw 已经沉淀出一套非常实用的 Agent 抽象,尤其体现在:
core.Agentcore.AgentSessionHistoryProviderModelSwitcherProviderSwitcherModeSwitcherMemoryFileProviderSkillProviderCommandProviderContextCompressorSessionDeleter
对应参考文件:
codeclaw/core/interfaces.gocodeclaw/core/message.gocodeclaw/agent/*
codeclaw 的价值在于,它已经证明了 Agent Runtime 的真正核心并不大;复杂度主要来自“扩展能力分层”。
ARI 不是:
- 模型厂商 API 的统一层
- Prompt 标准
- Agent 内部工具系统标准
- Memory/Skill 实现规范
- 针对某个产品的私有 SDK
ARI 是:
- Agent Runtime 的统一抽象层
- 面向会话、事件流、权限审批的协议语义
- 本地 SDK 和远程 RPC 协议的共同语义基座
- 上层调度器、控制面、聊天桥接层、IDE 插件、审计系统之间的稳定契约
也就是说,ARI 关心的是:
- 如何创建/恢复会话
- 如何发送一轮输入
- 如何接收持续事件流
- 如何处理权限请求
- 如何发现 runtime 的可选能力
- 如何用稳定错误语义表达失败原因
而不是关心 runtime 内部到底通过什么 prompt、什么 CLI flag、什么进程模型来实现这些能力。
- 屏蔽不同 Agent CLI 的调用差异
- 把
Session作为第一等资源 - 把事件流作为核心交互通道
- 把权限审批作为核心协议概念,而不是插件能力
- 支持本地嵌入式调用,也支持未来的 gRPC / HTTP 远程 runtime
- 支持 capability discovery,让高级能力按需探测
- 让上层系统只依赖 ARI,而不依赖具体 runtime 实现
- 不试图统一 OpenAI / Anthropic / Google 等底层推理 API
- 不规定 Agent 内部 prompt、记忆、工具框架如何组织
- 不要求所有 runtime 在行为体验上完全一致
- 不把每个产品特性都塞进核心接口
- 不在 v1 解决跨 runtime session 迁移、复杂多租户治理、细粒度资源调度
Runtime 表示一个可被上层系统驱动的 Agent 执行后端,例如:
- Codex CLI runtime
- Claude Code runtime
- Cursor Agent runtime
- Gemini CLI runtime
- OpenCode runtime
- Qoder runtime
- 远程 Agent Gateway runtime
它负责:
- 汇报版本与状态
- 创建、恢复、查询会话
- 暴露支持的 capabilities
- 管理全局资源与关闭逻辑
Session 是 ARI 的第一等资源,对应一个长期存在的逻辑对话上下文。
它通常包含:
- runtime 内部维护的会话标识
- 工作目录/项目上下文
- 对话历史的连续性
- 一条长期存在的事件流
这与 codeclaw 中的 AgentSession 高度一致,但 ARI 会把它提升为协议级资源。
Turn 表示一次用户输入驱动的一轮执行:
- 一次
SendMessage触发一个 turn - 一个 turn 可能产生多条事件
- 一个 turn 可能包含工具调用、权限请求、中间文本、最终结果或错误
Turn 可以是显式资源,也可以只是事件与请求中的关联字段;但语义上必须存在。
事件流是 ARI 的核心交互方式。
参考 codeclaw/core/message.go,ARI 需要稳定支持下列事件族:
textthinkingtool_calltool_resultpermission_requestresulterrorlifecycle
ARI 不强制所有 runtime 提供 token 级流式输出,但必须允许支持流式事件。
权限请求是 Agent Runtime 区别于普通 LLM API 的关键点之一。
对 ARI 来说,它必须是协议核心,而不是扩展插件,因为很多 coding agent 的真实工作流都依赖:
- shell 命令审批
- 文件编辑审批
- 网络访问审批
- 危险操作审批
一个最小权限请求至少应包含:
request_idsession_idturn_idtool_nametool_input_summarytool_input_rawmessage/reason- 超时或取消信息(可选)
一个最小响应至少应包含:
allow/denyupdated_input(可选)message(可选)
ARI 的设计不是简单复制其中一个项目,而是做一次“概念嫁接”:
| 来源 | 借鉴点 | 在 ARI 中的体现 |
|---|---|---|
cri-api |
小接口拆分 | RuntimeVersioner、SessionManager、InteractionManager、EventManager |
cri-api |
顶层服务聚合 | RuntimeService |
cri-api |
语义契约优先 | 线程安全、幂等、状态语义、错误语义 |
codeclaw/agent |
Session 为核心对象 | Session 成为 ARI 第一等资源 |
codeclaw/agent |
事件驱动交互 | SessionEvents() / streaming API |
codeclaw/agent |
权限回调机制 | RespondPermission() |
codeclaw/agent |
可选能力分层 | model/provider/mode/history/skill/memory 等 capability |
可以把 ARI 的核心思路概括为:
- 用
cri-api的方式设计接口边界 - 用
codeclaw的方式定义会话与事件语义
核心接口只保留几乎所有 runtime 都会支持的能力:
- 版本
- 状态
- 创建/恢复会话
- 发送消息
- 接收事件
- 响应权限
- 关闭会话/关闭 runtime
以下能力不应进入最小核心接口:
- 历史检索
- 模型切换
- Provider 切换
- 权限模式切换
- 技能发现
- 命令发现
- 记忆文件发现
- 上下文压缩
这些能力应该像 codeclaw/core/interfaces.go 一样,单独做 capability interface。
对于上层系统来说:
- runtime 是持久进程,还是每轮拉起再 resume
- 会话 ID 是本地文件名,还是远程线程 ID
- 权限请求通过 stdio、socket 还是 RPC 发送
都不重要。
ARI 需要稳定暴露的是:
- 逻辑会话
- 逻辑 turn
- 统一事件流
- 统一权限回调语义
像 CRI 一样,ARI 应避免接口中出现一长串裸参数。
统一使用显式请求/响应对象,便于:
- 未来兼容扩展
- gRPC / protobuf 映射
- 审计与日志记录
- 保持接口演进稳定
上层不能假定所有 runtime 都支持:
- 删除 session
- 获取历史
- 动态切换模型
- 动态切换 provider
- 取消 turn
- 上下文压缩
因此 ARI 需要支持 capability discovery。
下面的分层方式直接对应 cri-api/pkg/apis/services.go 的思路。
package ari
import "context"
type RuntimeVersioner interface {
// Version returns runtime name, runtime version and ARI API version.
Version(ctx context.Context, apiVersion string) (*VersionResponse, error)
}
type RuntimeStatuser interface {
// Status returns health, readiness and capability summary of the runtime.
Status(ctx context.Context, verbose bool) (*StatusResponse, error)
}package ari
import "context"
type SessionManager interface {
CreateSession(ctx context.Context, req *CreateSessionRequest) (*CreateSessionResponse, error)
ResumeSession(ctx context.Context, req *ResumeSessionRequest) (*ResumeSessionResponse, error)
GetSession(ctx context.Context, req *GetSessionRequest) (*GetSessionResponse, error)
ListSessions(ctx context.Context, filter *SessionFilter) ([]*Session, error)
CloseSession(ctx context.Context, sessionID string) error
DeleteSession(ctx context.Context, sessionID string) error
}package ari
import "context"
type InteractionManager interface {
SendMessage(ctx context.Context, req *SendMessageRequest) (*SendMessageResponse, error)
RespondPermission(ctx context.Context, req *RespondPermissionRequest) (*RespondPermissionResponse, error)
CancelTurn(ctx context.Context, req *CancelTurnRequest) (*CancelTurnResponse, error)
}约束建议:
SendMessage不要求同步返回完整结果- 完整输出主要通过事件流返回
CancelTurn可选;不支持时返回Unimplemented或ErrNotSupported
package ari
import "context"
type EventManager interface {
SessionEvents(ctx context.Context, req *SessionEventsRequest) (<-chan *Event, error)
}如果未来提供 RPC 版本,优先映射为 server streaming:
rpc SessionEvents(SessionEventsRequest) returns (stream Event);package ari
import "context"
type HistoryManager interface {
GetSessionHistory(ctx context.Context, req *GetSessionHistoryRequest) (*GetSessionHistoryResponse, error)
}
type ModelManager interface {
GetModel(ctx context.Context, req *GetModelRequest) (*GetModelResponse, error)
SetModel(ctx context.Context, req *SetModelRequest) (*SetModelResponse, error)
ListModels(ctx context.Context, req *ListModelsRequest) (*ListModelsResponse, error)
}
type ModeManager interface {
GetMode(ctx context.Context, req *GetModeRequest) (*GetModeResponse, error)
SetMode(ctx context.Context, req *SetModeRequest) (*SetModeResponse, error)
ListModes(ctx context.Context, req *ListModesRequest) (*ListModesResponse, error)
}
type ProviderManager interface {
GetActiveProvider(ctx context.Context, req *GetActiveProviderRequest) (*GetActiveProviderResponse, error)
SetActiveProvider(ctx context.Context, req *SetActiveProviderRequest) (*SetActiveProviderResponse, error)
ListProviders(ctx context.Context, req *ListProvidersRequest) (*ListProvidersResponse, error)
}package ari
import "context"
type RuntimeService interface {
RuntimeVersioner
RuntimeStatuser
SessionManager
InteractionManager
EventManager
// Close shuts down the runtime client or underlying connection.
Close(ctx context.Context) error
}参考 codeclaw/core/message.go,建议定义稳定事件结构:
package ari
type EventType string
const (
EventText EventType = "text"
EventThinking EventType = "thinking"
EventToolCall EventType = "tool_call"
EventToolResult EventType = "tool_result"
EventPermissionRequest EventType = "permission_request"
EventResult EventType = "result"
EventError EventType = "error"
EventLifecycle EventType = "lifecycle"
)
type Event struct {
Type EventType
SessionID string
TurnID string
RequestID string
Content string
ToolName string
ToolInput string
ToolInputRaw map[string]any
ToolResult string
Done bool
Error *ErrorStatus
Timestamp int64
}设计建议:
SessionID始终带上,便于多路复用TurnID显式化,便于取消、中断、审计和并发扩展Done表示当前 turn 结束,不表示 session 关闭- 错误尽量结构化,不只返回字符串
lifecycle用于会话创建、恢复、关闭、心跳等状态变化
ARI 应优先使用显式请求/响应对象,而不是裸参数。
例如:
package ari
type SendMessageRequest struct {
SessionID string
Message *Message
IdempotencyKey string
Metadata map[string]string
}
type Message struct {
Role string
Content string
Parts []*ContentPart
}
type ContentPart struct {
Type string // text, image, audio, file
Text string
MimeType string
Uri string
Data []byte
Name string
}这样做的收益:
- 易于支持多模态输入
- 易于从本地 SDK 演进到远程协议
- 易于做幂等、审计、回放与 tracing
- 避免未来为了加字段而频繁破坏接口
ARI 需要稳定错误语义,而不是只依赖字符串错误信息。
建议至少定义这些语义级错误:
NotFoundAlreadyExistsInvalidArgumentFailedPreconditionPermissionDeniedUnimplementedUnavailableDeadlineExceededCanceledInternal
如果未来有 gRPC 版本:
- 本地实现内部可以继续使用语言原生错误
- 对外传输统一映射到稳定状态码
ARI 需要同时支持两种能力发现模式。
适合嵌入式调用:
if m, ok := runtime.(ModelManager); ok {
// runtime supports model switching
}适合 gRPC / HTTP:
package ari
type Capability struct {
Name string
Version string
Enabled bool
Details map[string]string
}然后在 StatusResponse 里声明:
- 支持哪些能力
- 当前是否启用
- 是否实验性
- 是否只对新 session 生效
建议两种方式并存:
- 本地用接口断言
- 远程用 capability 列表
如果未来把 codeclaw/agent 作为 ARI 的一组 adapter,实现关系大致如下。
codeclaw |
ARI |
|---|---|
core.Agent.StartSession |
CreateSession / ResumeSession |
core.Agent.ListSessions |
ListSessions |
core.Agent.Stop |
RuntimeService.Close |
core.AgentSession.Send |
SendMessage |
core.AgentSession.RespondPermission |
RespondPermission |
core.AgentSession.Events |
SessionEvents |
core.AgentSession.Close |
CloseSession |
codeclaw |
ARI 建议归属 |
|---|---|
HistoryProvider |
HistoryManager |
ModelSwitcher |
ModelManager |
ProviderSwitcher |
ProviderManager |
ModeSwitcher |
ModeManager |
MemoryFileProvider |
MemoryManager 或 capability |
SkillProvider |
SkillManager 或 capability |
CommandProvider |
CommandManager 或 capability |
ContextCompressor |
SessionUtilityManager 或 capability |
SessionDeleter |
DeleteSession |
codeclaw 当前更像“嵌入式 SDK 抽象”;
ari-api 更适合演进成“语言无关的接口/协议定义仓库”。
因此 ARI 不应机械复制 codeclaw/core.Agent,而应:
- 保留它最重要的会话/事件/权限语义
- 用 CRI 风格重新拆分服务接口
- 显式区分 mandatory core 与 optional capability
cri-api 在接口设计上非常强调 thread-safe;ARI 也应该直接写明类似约束。
建议明确以下规则:
RuntimeService的公开方法必须线程安全CloseSession应尽量幂等DeleteSession对不存在对象应尽量幂等- 同一
Session可以被多个消费者订阅事件流 - 同一
Session是否允许并发 turn,必须显式定义 - 如果 runtime 不支持同 session 并发 turn,应返回
FailedPrecondition Close后的对象不可继续发送消息或订阅事件
ARI v1 应优先保证“语义稳定”,而不是一次性覆盖所有能力。
建议采用以下分层:
所有 runtime 都应支持:
VersionStatusCreateSession/ResumeSessionSendMessageSessionEventsRespondPermissionCloseSessionClose
ListSessionsGetSessionGetSessionHistoryCancelTurnDeleteSession
- 模型切换
- Provider 切换
- 模式切换
- 技能发现
- 命令发现
- 记忆文件发现
- 上下文压缩
原则是:
- 核心接口一旦进入 v1,应尽量长期稳定
- 增强能力通过 capability 扩展,而不是频繁修改核心接口
ARI 本质上是“接口定义”,不是某个 runtime 的业务实现。
因此更推荐仓库长期演进成下面两层之一:
适合把文档、proto、规范放在一起:
ari-api/
README.md
docs/
overview.md
object-model.md
error-model.md
versioning.md
proto/
ari/runtime/v1/api.proto
如果你希望最直接借鉴 cri-api:
ari-api/
README.md
doc.go
go.mod
pkg/
apis/
services.go
types.go
errors.go
testing/
fake_runtime_service.go
runtime/
v1/
api.proto
constants.go
api.pb.go
api_grpc.pb.go
当前仓库是一个 Rust 初始化项目(Cargo.toml 已存在),这不妨碍先把 ARI 定义为语言无关协议。
因此推荐做法是:
- 先把 README 和对象模型写清楚
- 再决定“规范主仓”究竟用 Rust、Go 还是 proto-first 方式维护
- 不要因为当前仓库语言选择,提前锁死协议形态
如果现在要启动 ari-api,建议 v1 只覆盖最核心的运行时抽象。
RuntimeVersionerRuntimeStatuserSessionManagerInteractionManagerEventManagerRuntimeService
SessionSessionStateMessageContentPartEventPermissionRequestPermissionResponseCapabilityVersionResponseStatusResponse
- Skill / Command / Memory 的详细文件系统协议
- Provider 配置细节标准化
- 多租户鉴权模型
- 资源配额与调度语义
- 跨 runtime session 迁移
- 复杂审计与回放规范
先补齐:
README.mddocs/object-model.mddocs/error-model.mddocs/versioning.md
目标是把概念边界、生命周期与错误语义写稳。
无论最终选 Go、Rust 还是 proto-first,都先定义:
servicestypeserrors
并用 fake runtime 验证:
- session 创建/恢复
- turn 执行
- 事件流
- 权限审批
- 历史查询
优先接一个最典型的 runtime:
codex- 或
claudecode
因为这两类 runtime 在会话、权限、事件、模式切换上都足够典型,最适合验证 ARI 抽象是否成立。
只有当本地抽象稳定后,再继续:
api.proto- gRPC 映射
- HTTP gateway
- capability registry
换句话说:
先稳定语义,再稳定接口,最后再稳定传输协议。
如果 ARI v1 满足下面几点,就已经足够开始落地:
- 上层系统可以不关心具体 agent 类型,统一创建/恢复会话
- 上层系统可以统一发送消息并消费事件流
- 上层系统可以统一处理权限请求
- runtime 可以按 capability 暴露增强能力
- 至少能把
codeclaw/agent中 2 个不同 runtime 映射到 ARI 而不出现语义扭曲
只要做到这些,ARI 就已经具备独立价值。
ari-api 最值得从 cri-api 借鉴的,不是某种具体技术栈,而是接口设计方法:
- 小接口拆分
- 顶层服务聚合
- 核心/扩展能力分层
- 语义契约优先于实现细节
ari-api 最值得从 codeclaw/agent 借鉴的,是 Agent Runtime 的真实工作流语义:
Session是核心资源Event Stream是核心交互方式Permission Request是第一等协议概念- 很多能力本质上应该是 optional capability
因此,ARI 最合理的方向是:
用 CRI 风格定义接口边界,用 codeclaw 风格定义会话、事件与权限语义。
如果后续继续推进,这个仓库最自然的下一步是把 README 中的设计,收敛成三类正式产物:
services:服务接口定义types:对象模型与事件模型errors:稳定错误语义
等这三块稳定下来,再去做 adapter、fake runtime、proto/gRPC,成本会低很多,也更不容易返工。