状态:Experimental(Runtime / Host)· 目标规范:Draft — 本文主体是 HARP 目标规范,不代表 Fellow Host 已满足全部条款。当前实现支持
runtime.kind: http的导入、导出、安装、启动、停止、升级、卸载和data/持久化;尚未完整实现validate/logsHost API、start.platforms、Manifest 网络权限强制和非 HTTP Runtime。当前产品边界见 Applications。
与智能对话互补
智能对话擅长探索、编排与一次性任务:用户描述意图,Agent 调用 Skills 与 MCP,交付报告、清单或写回系统。
但很多业务价值需要关掉聊天窗口仍然存在的软件——固定界面、长期运行、可独立迭代。传统路径是人工把对话里试通的流程再开发成 App;HARP 让非对话式软件的开发方式发生本质变化:智能体直接编写和维护可安装包,宿主隔离运行,用户长期拥有。
| 智能对话 | HARP 应用 | |
|---|---|---|
| 主要形态 | 会话中的任务与交付 | 独立安装的软件产品 |
| 生命周期 | 随会话推进(成果可归档) | 安装后持续运行 |
| 开发方式 | Skill + MCP 编排 | 智能体产出 harp.json 应用包 |
| 典型场景 | 探索、变体多、复杂一次性任务 | 高频固定流程、专用 UI、需本地常驻 |
Fellow 的特色在于两者同屏共存:在对话里验证流程,再固化为 HARP 应用进入「我的应用」——不是二选一,而是对话负责创造与编排,应用负责持久与专用。
软件厂商通过 伙伴合作 发布 HARP 包;以下为协议正文。
1. 一句话定义
HARP 应用是一份可安装、可长期运行、面向人类用户的便携包。智能体负责编写和维护它,宿主负责隔离执行,用户在对话结束后仍然拥有它。
它交付的是产品,不是一次工具调用,也不是一段提示词。
2. 要解决的问题
智能体已经能写代码、起服务、改仓库。缺的是一层跨产品可移植的应用协议:
- 换一个宿主,同一份应用无法安装或启动。
- 智能体不知道「什么叫安装成功」,只能靠产品私有工具。
- 用户数据、网络、密钥的隔离没有共同语言,宿主无法互认安全声明。
- MCP、Skill、MCP Apps、A2A 各自覆盖了工具、流程、对话内 UI、智能体互联,没有人覆盖「装到用户机器上长期跑的应用」。
HARP 补的就是这一层。它不取代上述标准,只与它们正交。
3. 定位:和相邻标准的边界
| 标准 | 回答的问题 | 生命周期 | 主消费者 |
|---|---|---|---|
| Agent Skills | 智能体怎么做这件事 | 读进上下文即结束 | 智能体 |
| MCP | 智能体可以调用什么能力 | 一次连接 | 智能体 |
| MCP Apps | 对话里怎么嵌一块 UI | 一轮对话 | 聊天窗口中的用户 |
| A2A | 智能体如何发现并调用另一个智能体 | 一次会话或任务 | 另一个智能体 |
| Agent Applications | 智能体如何用 CLI 操作一个有状态包 | 一次命令 | 智能体 |
| HARP | 用户留下什么可安装、可独立使用的产品 | 安装后独立存活 | 用户,其次才是智能体 |
硬规则:
- HARP MUST NOT 把应用定义成 MCP server。应用跑起来之后 MAY 再暴露 MCP。
- HARP MUST NOT 把
SKILL.md当作运行时。包内 MAY 附带 Skill,那是给智能体的作者指南。 - HARP MUST NOT 把对话内 widget 当作已安装应用。同一产品 MAY 同时提供 HARP 包和 MCP Apps 界面。
- HARP MUST NOT 要求应用本身是一个智能体。只有当应用要被其他智能体当对等方调用时,才 MAY 附带 A2A Agent Card。
4. 设计原则
- 原语极小。 v1 只锁定身份、启动、就绪、数据目录四件事。预留名列表不是 v1 约定。其余全部协商或扩展。
- 三个角色,三份约定。 Package / Runtime / Host 分开,禁止揉进一个单体清单。
- 声明能力,不规定实现。 规范不写死语言、框架、打包器、操作系统。
- 用户数据默认私有。 可变状态有且只有一个宿主注入的数据目录。
- 安全是核心,不是附录。 权限必须由宿主强制,不能只靠应用自觉。
- 渐进披露。 列表只看身份;安装看完整清单;排障再看日志和健康细节。这是 Skill 能规模化的同一原因。
- 扩展用命名空间,不靠 fork。 产品私有字段走
x-<vendor>.*。 - 一致性测试即规范。 没有可运行的 fixture 和测试,就还只是文档。
5. 角色
| 角色 | 定义 |
|---|---|
| Author | 编写包的人或智能体。产出符合 Package Format 的目录。 |
| Package | 一个目录(或由其得到的不可变制品)。根上有清单文件。 |
| Host | 安装、隔离、启动、停止、升级应用的运行环境(桌面、云、浏览器运行时等)。 |
| User | 拥有已安装应用及其数据的人。 |
| Agent | 代表用户编写、安装、排障应用的智能体。它是 Author 的一种,不是第四种运行时。 |
宿主 MUST 实现 Runtime ABI。
宿主 MAY 用私有 API 暴露 install/start/stop。那部分在 v1 是建议,不是强制互操作范围。
6. 架构:三层约定
v1 的互操作承诺只有 Layer 1 + Layer 2。
两个兼容宿主拿到同一份包,MUST 能:校验清单、注入 ABI、拉起进程、用健康检查判定就绪、把状态限制在数据目录。
预留 kind、operations 与 Host API 传输不在这份承诺里。
7. Layer 1 — Package Format
7.1 包是一个目录
一个 HARP 包 MUST 是一个目录,根上 MUST 有且仅有一份主清单:
harp.json
包 MUST NOT 依赖清单之外的隐式入口(例如「没有清单就跑 package.json 的 start」)。
包 MAY 另含任意实现文件。规范不解释这些文件。
建议布局(信息性,非强制):
my-notes/
harp.json
README.md
src/ 或 server.mjs
assets/
分发时,包 MAY 被打成 zip / tarball / OCI image。解包后 MUST 仍满足「根上有 harp.json」。
容器化是一种分发与隔离策略,不是第二种应用模型。
7.2 清单:最小合法集
{
"schemaVersion": 1,
"id": "notes",
"name": "Notes",
"version": "1.0.0",
"description": "Personal notes. Install when the user wants a lasting notes app.",
"runtime": {
"kind": "http"
},
"start": {
"argv": ["node", "server.mjs"]
}
}
带可选字段的完整 v1 形态:
{
"schemaVersion": 1,
"id": "notes",
"name": "Notes",
"version": "1.0.0",
"description": "Personal notes. Install when the user wants a lasting notes app.",
"license": "MIT",
"runtime": {
"kind": "http"
},
"start": {
"argv": ["node", "server.mjs"]
},
"health": {
"path": "/health",
"timeoutMs": 20000
},
"entrypoints": {
"ui": {
"kind": "http",
"path": "/"
}
},
"permissions": {
"network": "loopback",
"filesystem": "data-only"
},
"x-fellow": {
"comment": "vendor extension; hosts that do not understand it MUST ignore"
}
}
7.3 字段规范
未知顶层字段:不带 x- 前缀的 MUST 被拒绝(strict)。
x-<vendor> 对象 MUST 被不认识它的宿主忽略。
| 字段 | 必填 | 约束 |
|---|---|---|
schemaVersion | 是 | 整数。v1 清单 MUST 为 1 |
id | 是 | 1–64 字符;[a-z0-9] 与 -;不能以 - 开头或结尾 |
name | 是 | 1–80 字符,给人看的短名 |
version | 是 | 1–40 字符。SHOULD 使用 SemVer |
description | 否,但强烈建议 | 1–1024 字符。SHOULD 同时写清做什么、何时安装或打开 |
license | 否 | SPDX id,或指向包内许可证文件的相对路径 |
runtime.kind | 是 | v1 MUST 为 http。预留名见 7.4,ABI 大纲见 预留约定 |
start.argv | 是 | 1–32 项;每项 1–500 字符。argv[0] 的两种形态见 7.5 |
start.platforms | 否 | 按平台覆盖 argv,见 7.5.3。未实现的宿主遇到该字段 MUST 拒绝 |
health.path | 否 | 以 / 开头,最长 200。缺省为 / |
health.timeoutMs | 否 | 1000–120000。缺省由宿主决定,SHOULD 为 20000 |
entrypoints | 否 | 见第 10 节。v1 schema 只接受 ui.kind=http |
permissions | 否 | 见第 9 节。缺省见 9.3 |
id 是宿主本地标识,不是跨作者、跨宿主的全球身份。
- 同一宿主上相同
idMUST 视为升级:替换包体,保留APP_DATA_DIR。 idMUST NOT 在升级时改变。idMUST NOT 编码路径、用户或宿主名。安装位置由宿主决定。- 两个作者都可以使用
notes。宿主 MUST NOT 把它们当成两份并列安装;后装入的覆盖先装的包体。 - 跨宿主互认(发布者、内容摘要、签名来源)是 v1 非目标。
start.argv 中除 argv[0] 按 7.5 解析外,其余看起来像路径的参数 MUST 是相对包根的路径,MUST NOT 为绝对路径,MUST NOT 逃出包根。
7.4 runtime.kind
runtime.kind 是注册名,不是自由文本。宿主 MUST 按下表分类,MUST NOT 猜测。
| 值 | 分类 | 错误 |
|---|---|---|
http | v1 强制 | 继续校验并启动 |
| 下列预留名 | 已知但未实现 | unsupported(C15),除非能力集声明了该 kind |
| 其他任何字符串 | 非法 | illegal(C03) |
v1 JSON Schema 只接受 http。预留名列表的存在,是为了区分「不支持」和「清单非法」,不是为了让 schema 放行。
v1 强制实现(自称 HARP v1 Host 的实现必须能跑):
| kind | 状态 | 含义 |
|---|---|---|
http | v1 强制 | 拉起进程;听 HOST/PORT;用 HTTP 健康检查判定就绪 |
v1 预留(名字和意图冻结,ABI 大纲见 预留约定。v1 宿主 MUST 拒绝,除非它在能力集里声明了该 kind):
| kind | 状态 | 意图 |
|---|---|---|
http-static | 预留 | 无进程。宿主把包内静态文件托管到分配的 HOST/PORT |
window | 预留 | 原生窗口进程(Qt / SwiftUI / WinUI / Flutter 等)。不依赖 HTTP |
headless | 预留 | 长期后台进程,无人类 UI,也不听 HTTP |
oneshot | 预留 | 一次性任务(CLI / 批处理)。退出码即结果 |
container | 预留 | 包指向 OCI 镜像或内嵌 image layout;隔离由容器运行时承担 |
wasm | 预留 | Wasm 组件。启动器不是 OS 进程 |
产品私有运行时 MUST NOT 占用上表名字,也 MUST NOT 自造核心 kind。只允许 x-<vendor> 扩展字段,不能发明 runtime.kind。
7.5 启动器:宿主运行时与包内二进制
start.argv[0] 只有两种合法形态。这样任意语言都可以做应用:要么用宿主提供的解释器,要么把编译好的可执行文件打进包。
7.5.1 宿主运行时名
若 argv[0] 不含 / 或 \,也不以 . 开头,它是逻辑运行时名(node、python、deno、bun)。
- 宿主 MUST 解析为自己声明提供的运行时。
- 解析失败 MUST 拒绝启动。
- MUST NOT 退回去执行系统 PATH 上的任意同名二进制,除非能力集显式声明
unmanaged-path。 - 其余 argv 按原样传给该运行时。
"start": { "argv": ["python", "server.py"] }
7.5.2 包内可执行文件
若 argv[0] 以 ./ 开头,或含有 / / \,它是相对包根的可执行文件。
- MUST 相对包根;MUST NOT 为绝对路径;MUST NOT 含
..段。 - 宿主 MUST 在启动前确认该文件存在于包内(Windows 上若写的是
./bin/server而只有server.exe,宿主 MAY 补.exe)。 - 宿主 MUST 直接执行该文件,MUST NOT 再拿它去匹配宿主运行时名。
- 其余 argv 传给该二进制。
- 文件 MUST NOT 是逃出包根的符号链接。
"start": { "argv": ["./bin/notes"] }
这是 Go / Rust / C / C# 单文件分发的标准写法。包作者 MUST 保证该二进制对目标平台可执行;宿主不负责交叉编译。
7.5.3 预留:start.platforms
多平台包 MAY 用 platforms 覆盖默认 argv。键为平台三元组:linux-x64、linux-arm64、darwin-x64、darwin-arm64、win32-x64、win32-arm64。
"start": {
"argv": ["./bin/notes"],
"platforms": {
"win32-x64": { "argv": ["./bin/notes.exe"] },
"darwin-arm64": { "argv": ["./bin/notes-darwin-arm64"] }
}
}
- 未声明
platforms时,所有平台都用顶层argv。 - 声明了
platforms且当前平台有键:宿主 MUST 用该键的argv。 - 声明了
platforms且当前平台无键:宿主 MUST 拒绝启动,MUST NOT 回退到错误架构的二进制。 - v1 宿主 SHOULD 实现本节;尚未实现时,MUST 在能力集省略
start.platforms,并在清单出现该字段时拒绝(不可静默忽略,否则会跑错架构)。
宿主 SHOULD 注入预留变量 APP_PLATFORM(上列三元组之一),供启动脚本自辨。v1 不强制应用读取它。
7.6 预留 ABI
预留 kind 与 ready 的大纲已移到 预留约定。
v1 宿主 MUST 按 7.4 拒绝它们。v1 清单 MUST NOT 包含 ready。
7.7 JSON Schema(规范性附录)
本 schema 是 v1 可安装清单 的机械真源。runtime.kind 仅为 http;start 必填。
预留 kind / ready / operations 不在此列。宿主仍须认识预留 kind 名,以便给出 C15 的 unsupported。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://harp.dev/schema/v1.json",
"title": "HARP Manifest",
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^x-[a-z0-9-]+$": true
},
"required": ["schemaVersion", "id", "name", "version", "runtime", "start"],
"properties": {
"schemaVersion": { "const": 1 },
"id": {
"type": "string",
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
},
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
"version": { "type": "string", "minLength": 1, "maxLength": 40 },
"description": { "type": "string", "minLength": 1, "maxLength": 1024 },
"license": { "type": "string", "minLength": 1, "maxLength": 200 },
"runtime": {
"type": "object",
"additionalProperties": false,
"required": ["kind"],
"properties": {
"kind": { "const": "http" }
}
},
"start": {
"type": "object",
"additionalProperties": false,
"required": ["argv"],
"properties": {
"argv": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"items": { "type": "string", "minLength": 1, "maxLength": 500 }
},
"platforms": {
"type": "object",
"additionalProperties": false,
"properties": {
"linux-x64": { "$ref": "#/$defs/startOverride" },
"linux-arm64": { "$ref": "#/$defs/startOverride" },
"darwin-x64": { "$ref": "#/$defs/startOverride" },
"darwin-arm64": { "$ref": "#/$defs/startOverride" },
"win32-x64": { "$ref": "#/$defs/startOverride" },
"win32-arm64": { "$ref": "#/$defs/startOverride" }
}
}
}
},
"health": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": { "type": "string", "pattern": "^/[^\\s]*$", "maxLength": 200 },
"timeoutMs": { "type": "integer", "minimum": 1000, "maximum": 120000 }
}
},
"entrypoints": {
"type": "object",
"additionalProperties": false,
"properties": {
"ui": {
"type": "object",
"additionalProperties": false,
"required": ["kind"],
"properties": {
"kind": { "const": "http" },
"path": { "type": "string", "pattern": "^/[^\\s]*$", "maxLength": 200 }
}
},
"openapi": {
"type": "string",
"pattern": "^[^/].+",
"maxLength": 200
},
"mcp": {
"type": "object",
"additionalProperties": false,
"required": ["path"],
"properties": {
"path": { "type": "string", "pattern": "^/[^\\s]*$", "maxLength": 200 }
}
}
}
},
"permissions": {
"type": "object",
"additionalProperties": false,
"properties": {
"network": { "enum": ["none", "loopback", "allow"] },
"filesystem": { "enum": ["data-only", "none"] }
}
}
},
"$defs": {
"startOverride": {
"type": "object",
"additionalProperties": false,
"required": ["argv"],
"properties": {
"argv": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"items": { "type": "string", "minLength": 1, "maxLength": 500 }
}
}
}
}
}
8. Layer 2 — Runtime ABI
这是协议的硬核。换宿主还能跑,靠的是这一节,不是清单里多几个字段。
8.1 宿主在启动前 MUST 注入的环境变量
| 变量 | 何时注入 | 含义 |
|---|---|---|
HOST | http 强制 | 监听地址。v1 MUST 为 loopback(127.0.0.1 或 ::1) |
PORT | 同上 | 由宿主分配。应用 MUST NOT 自己选端口 |
APP_ID | 强制 | 等于清单 id |
APP_DATA_DIR | 强制 | 该应用可写数据目录的绝对路径 |
APP_CACHE_DIR | 强制 | 可丢弃缓存目录。宿主 MAY 随时清空 |
TMPDIR | 强制 | 临时目录。MUST 落在 APP_DATA_DIR 或 APP_CACHE_DIR 下 |
APP_PLATFORM | SHOULD | 平台三元组,见 7.5.3 |
http 应用 MUST 从环境变量读 HOST/PORT,MUST NOT 把端口写进清单或源码。
预留 kind 的注入规则见 预留约定。
为避免和现有产品冲突,宿主 MAY 额外注入自己的别名(例如 VENDOR_APP_DATA_DIR),但 MUST 同时提供上表标准名。应用作者 SHOULD 只读标准名。
8.2 工作目录与文件系统
- 进程 cwd MUST 是包根(安装后的 bundle 根)。
- 包目录对应用 MUST 为只读,或在写失败时行为与只读等价。
- 可变状态 MUST 只写
APP_DATA_DIR(以及APP_CACHE_DIR/TMPDIR)。 - 应用 MUST NOT 假设
node_modules、虚拟环境或系统全局包会被宿主拷贝。生产依赖 MUST 打进包,或由声明的 runtime 提供。
8.3 就绪
对 v1 强制的 http:
- 宿主分配 loopback 端口并启动进程。
- 宿主 MUST 对
http://$HOST:$PORT{health.path}做 GET(health.path缺省为/)。 - 在超时内收到任意 2xx,即视为就绪。
- 进程在就绪前退出,MUST 视为启动失败。
- 超时仍未就绪,宿主 MUST 停止进程并报失败。
健康检查 MUST NOT 要求鉴权。它不是业务 API。
预留 kind 的就绪判定见 预留约定。v1 宿主在启动前 MUST 拒绝预留或未知的 runtime.kind(C15 / C03),以及清单中的 ready 字段(C02)。
8.4 停止
- 宿主 MUST 先发
SIGTERM(Windows 上等价于温和结束请求)。 - 宽限期 SHOULD 为 5 秒,MAY 可配置,MUST NOT 超过 30 秒。
- 仍未退出则 MUST 强制结束(
SIGKILL或平台等价)。 - 应用 SHOULD 在 SIGTERM 后尽快刷盘并退出。
8.5 升级
- 同一宿主上同一
id的新版本替换包体。 APP_DATA_DIRMUST 保留。APP_CACHE_DIRMAY 被清空。- 清单
id若与已安装身份不一致,MUST 拒绝安装。 - 破坏性数据迁移是应用自己的责任;协议不规定迁移脚本。
8.6 日志
宿主 SHOULD 把 stdout/stderr 收到应用专属日志槽。
日志格式不标准化。协议只保证:启动失败时,宿主 SHOULD 让 Author/Agent 能读到最近的进程输出。
9. 权限与隔离
权限是声明,由宿主 强制。应用写 "network": "none" 却去连网,宿主 MUST 挡住或启动失败,不能当注释。
9.1 permissions.network
| 值 | 含义 |
|---|---|
none | 禁止一切网络,健康检查所需的 loopback 监听除外 |
loopback | 只允许回环。这是 v1 缺省 |
allow | 允许出站。宿主 SHOULD 在安装或首次启动时取得用户同意 |
入站在 v1 MUST 仅来自 loopback。把应用暴露到局域网或公网是宿主扩展,MUST 使用 x-<vendor> 并单独授权。
9.2 permissions.filesystem
| 值 | 含义 |
|---|---|
data-only | 可写 APP_DATA_DIR / APP_CACHE_DIR / TMPDIR;包只读。缺省 |
none | 无持久写。APP_DATA_DIR 可以不存在;写操作 MUST 失败 |
v1 MUST NOT 提供「读写用户任意文件夹」。那是后续能力,必须带路径声明和用户同意。
9.3 缺省
清单省略 permissions 时,宿主 MUST 按 { "network": "loopback", "filesystem": "data-only" } 执行。
这是安全默认,不是「未声明就可以为所欲为」。
9.4 密钥
v1 MUST NOT 把密钥写进清单。
需要 API key 的应用 SHOULD 在首次运行向用户采集,并只写入 APP_DATA_DIR。
宿主 MAY 用扩展提供密钥注入;未标准化前 MUST 走 x-<vendor>。
9.5 依赖声明(信息性,v1.1 候选)
后续可增加 needs:运行时、系统库、GPU、模型。v1 只要求:宿主解析不了 start.argv[0] 就失败并说明缺了什么。不要在 v1 发明完整的软件包依赖语言。
10. 入口(可选)
entrypoints 声明应用跑起来之后如何被访问,以及每个入口面向谁。
启动不依赖 entrypoints。v1 宿主:
- MUST 能在忽略
entrypoints的情况下按 Runtime ABI 把进程跑起来。 - 仅当它声称「打开人类 UI」时,MUST 把入口理解为
http://$HOST:$PORT{path}(path缺省为/)。 - 规范不规定用系统浏览器、内嵌 webview 还是 iframe。
v1 schema 只接受 entrypoints.ui.kind=http。window / none 是预留名,见 预留约定;写进 v1 清单会被 schema 拒绝。
10.1 entrypoints.ui(人类界面)
{ "kind": "http", "path": "/" }
10.2 entrypoints.openapi(机器界面)
值为包内相对路径,指向 OpenAPI 3 文档。
智能体 MAY 用它在运行期调用应用。这不是 HARP 的操作约定,SHOULD NOT 整份灌进系统提示。
10.3 entrypoints.mcp
应用进程在某个 HTTP 路径上暴露 MCP。
这是「应用把完整工具能力交给智能体」的挂钩,不是把 HARP 做成 MCP 的子集。
v1 MUST NOT 要求宿主自动把该 MCP 挂进智能体。
10.4 operations
operations / invoke 不是 v1 字段。出现在清单中按未知非 x- 字段拒绝(C02)。
名字与方向见 预留约定。
11. 完整示例
包:
notes/
harp.json
server.mjs
harp.json:
{
"schemaVersion": 1,
"id": "notes",
"name": "Notes",
"version": "1.0.0",
"description": "A private notes app. Install when the user wants to keep notes outside the chat.",
"license": "MIT",
"runtime": { "kind": "http" },
"start": { "argv": ["node", "server.mjs"] },
"health": { "path": "/health" },
"entrypoints": {
"ui": { "kind": "http", "path": "/" }
},
"permissions": {
"network": "loopback",
"filesystem": "data-only"
}
}
server.mjs 只需遵守 ABI:读 HOST/PORT/APP_DATA_DIR,提供 /health,状态落在数据目录。用什么框架与协议无关。
同一约定下的 Go 二进制(仍是 v1,只要宿主实现了 7.5.2):
{
"schemaVersion": 1,
"id": "notes",
"name": "Notes",
"version": "1.0.0",
"runtime": { "kind": "http" },
"start": {
"argv": ["./bin/notes"],
"platforms": {
"win32-x64": { "argv": ["./bin/notes.exe"] }
}
},
"health": { "path": "/health" }
}
下面不是合法 v1 包:过不了 v1 schema。写在这里只为说明预留名;宿主应报 unsupported(C15),而不是当成可安装清单:
{
"schemaVersion": 1,
"id": "sketch",
"name": "Sketch",
"version": "1.0.0",
"runtime": { "kind": "window" },
"start": { "argv": ["./bin/sketch"] },
"ready": { "kind": "window" },
"entrypoints": { "ui": { "kind": "window" } }
}
智能体侧最小闭环:
- 在工作区生成上述目录并自测健康检查。
- 请宿主
validate,再install。 start成功后把 URL 交给用户。- 用户关闭对话后,应用仍在;再次打开只需
start,不必重写。
12. 版本与兼容
schemaVersion标识清单约定,不是应用的version。- 同一
schemaVersion内:新加可选字段 MUST 保持旧宿主可忽略或可经x-扩展。 - 删除或改语义的字段 MUST 升
schemaVersion。 - 宿主 MUST 拒绝更高的
schemaVersion,MUST NOT 降级猜测。 - 应用
version只服务升级与展示;协议不比较它的大小,除非宿主自己做。 - v1 schema 是可安装清单的验收契约。预留名列表不在 schema 里;把
operations或新 kind 写进该契约,必须升文档版本(v1.1 / 新schemaVersion),不能靠「旧宿主忽略未知字段」——v1 对未知非x-字段是拒绝。
能力协商(给 Host API / v2):
{
"protocol": "harp",
"schemaVersions": [1],
"runtimeKinds": ["http"],
"readyKinds": ["http"],
"uiKinds": ["http"],
"executables": ["node", "python"],
"bundledExecutable": true,
"startPlatforms": true,
"permissions": {
"network": ["none", "loopback", "allow"],
"filesystem": ["none", "data-only"]
}
}
应用不需要嵌入这份文件。这是宿主描述自己的方式。
13. 安全考虑
HARP 应用是长期本地进程,攻击面大于 Skill,也大于一次 MCP 调用。v1 把能力收得很窄,是有意的。
宿主 MUST:
- 按第 9 节强制权限,而不是只记录。
- 把不同
id的APP_DATA_DIR隔开。 - 不把用户主目录、其他应用数据、宿主密钥目录挂进可写根。
- 校验相对路径,禁止
..与绝对路径。 - 在
network: allow前取得用户同意。
宿主 SHOULD:
- 用平台沙箱(容器、seatbelt、AppContainer、seccomp 等)落实声明。
- 对安装来源做提示(工作区生成 / 本地路径 / 远程包)。
- 限制 CPU、内存、磁盘。v1 不规定数字,但裸奔不应是默认。
已知非目标(v1 不管,文档必须写明以免误用):
- 代码签名与供应链(v1.1+)
- 跨宿主的包身份(发布者、内容摘要、签名来源)
- 多用户 ACL
- 把服务发布到公网
- 应用间 IPC
- 自动注入用户的第三方账号
14. 合规
一个实现要自称 HARP v1 Host,MUST 通过公开的一致性套件。套件与文字规范冲突时,以套件加 errata 为准——先把套件写进标准治理,避免各家「按自己理解合规」。
v1 套件至少包括:
| 编号 | 断言 |
|---|---|
| C01 | 接受第 7.2 节最小清单 |
| C02 | 拒绝非法 id、绝对路径、逃逸路径、未知非 x- 字段(含 v1 中的 ready、operations) |
| C03 | 拒绝未列入预留名列表的 runtime.kind(错误为 illegal) |
| C04 | 注入 HOST/PORT/APP_ID/APP_DATA_DIR |
| C05 | 应用把文件写到 APP_DATA_DIR 外时失败或不可见 |
| C06 | 健康检查 2xx 后报告就绪 |
| C07 | 超时未就绪则杀掉进程并失败 |
| C08 | 升级保留 APP_DATA_DIR |
| C09 | network: none 时出站失败 |
| C10 | 停止路径:温和结束,必要时强制结束 |
| C11 | 忽略 x-* 仍能启动 |
| C12 | 更高 schemaVersion 被拒绝 |
| C13 | start.argv[0] 为 ./rel 时执行包内文件,不解析为宿主运行时名 |
| C14 | 包内路径含 .. 或绝对路径时拒绝 |
| C15 | 预留名列表中的 runtime.kind 被拒绝,且错误为 unsupported,与 C03 的 illegal 可区分 |
套件 MUST 带一个最小 fixture 应用(约几十行),不依赖特定 Web 框架。
一个包要自称 HARP v1 App,MUST:
- 通过 schema 校验;
- 在合规宿主上于超时内就绪;
- 重启后只从
APP_DATA_DIR恢复可变状态。
15. 治理建议
标准要能离开第一家实现者独立活着,需要:
- 规范仓库与产品仓库分开(例如
harporg)。 - JSON Schema + 一致性套件作为唯一机械真源;Markdown 是对人的叙述。
- 变更流程: 先实现两个独立宿主再升「Final」。一个实现的字段不得进入核心。
- 扩展注册表: 记录
x-<vendor>前缀,避免碰撞,不审核产品语义。 - 许可: 规范文本与 schema 用开放许可(建议 CC-BY 4.0 + schema 的 Apache-2.0)。
- 命名空间: 正式站点建议
https://harp.dev;清单$id与之对齐。在独立域名可用前,草案可用https://harp.dev/schema/v1.json作为稳定标识,不必先上线。
第一年成功标准不是「字段多」,而是:两家宿主跑通同一份 fixture,第三家智能体产品能按 schema 生成可安装包。
16. Layer 3 — Host API(v1 信息性)
以下 不是 v1 合规条件。写出来是为了避免每个产品发明一套语义,给 v2 留对齐点。
建议的 v1 动作:
| 动作 | 输入 | 结果 |
|---|---|---|
validate | 包目录 | 清单 + 权限摘要,不安装 |
install | 包目录 | 按 id 安装;同 id 视为该宿主上的升级并保留数据 |
list | — | 身份字段 + 运行状态 |
start | id | 就绪后的本地 URL |
stop | id | 进程结束 |
uninstall | id | 删除包;SHOULD 询问是否删除 APP_DATA_DIR |
logs | id | 最近进程输出 |
v1.1 候选(现在实现也不进入 v1 合规):describe、invoke。见 预留约定。
智能体侧的渐进披露:
- 系统提示里只放已安装应用的
id/name/description。 - 需要安装或排障时再读完整
harp.json(仍 MUST NOT 把端口交给模型,除非要把 URL 给用户打开)。 - 启动失败再拉
logs。
传输(HTTP、JSON-RPC、MCP tool)不在 v1 规定。先冻结包与 ABI,再冻传输。
17. 智能体作为作者时的约定
HARP 的作者经常是模型,不是人类。规范因此要让「最小合法包」小到模型稳定生成,同时让宿主能机械拒绝危险包。
Author(含智能体)MUST:
- 在包根写下合法的
harp.json(通过 v1 schema)。 - 监听
HOST与PORT,并提供可匿名 GET 的健康路径。 - 只把可变状态写到
APP_DATA_DIR。 - 使用宿主运行时,或把可执行文件放进包并用
./相对路径启动。 - 不把密钥、绝对路径、用户主目录写进清单。
- 不填写预留
runtime.kind(生成器只引用第 7.2 节最小合法集)。
Author SHOULD:
- 写
description:做什么 + 何时安装/打开。 - 提供
entrypoints.ui(kind=http)。 - 用 SemVer。
- 在包内附
README.md给人类;需要时附SKILL.md作为作者指南。
宿主对智能体 MUST:
- 用 schema 校验,失败时返回机器可读错误(字段路径 + 原因)。
- 拒绝逃逸包根的
start.argv路径。 - 拒绝符号链接逃逸(若分发形态是目录拷贝)。
- 不在用户明确要求前卸载应用。
- 对预留
runtime.kind与未知runtime.kind给出可区分的错误(C15 / C03)。
预留,不是 v1
本节冻结名字与方向,方便以后补 ABI,并避免厂商抢注。
它不是 v1 互操作范围。v1 一致性套件不覆盖本节的字段形态。
v1 JSON Schema(见 Package Format)只接受 http、health、可选的 entrypoints.ui.kind=http,以及 permissions。
因此:
| 清单里出现 | v1 宿主的分类 | 套件 |
|---|---|---|
下表预留 runtime.kind | 已知但未实现 → 拒绝,错误为 unsupported | C15 |
未列入列表的 runtime.kind | 非法 → 拒绝,错误为 illegal | C03 |
ready、operations,或其他未收入 v1 schema 的非 x- 字段 | 未知字段 → 拒绝 | C02 |
在这些名字进入强制范围并写入 schema 之前,细节可以补字段,但不能改已公布的含义。
智能体生成 v1 包时 SHOULD NOT 阅读或引用本节。
18. 预留 ABI 大纲
http-static
- 无
start。 - 宿主把包内目录托管到分配的
HOST/PORT。 - 就绪:对该 origin 的
health.path或/返回 2xx。 - 可写状态仍只有
APP_DATA_DIR(若静态应用需要存储,SHOULD 另配一个小的http,而不是让静态托管写文件)。
window
start必填,解析规则同 Package Format 7.5。- MUST NOT 要求应用听 HTTP。宿主 MUST NOT 因未绑定
PORT而判失败。 HOST/PORTMAY 仍注入,供应用内嵌本地服务;应用可忽略。- 就绪默认预留
ready.kind=window:进程已创建且出现至少一个属于该进程的可见窗口。宿主无法检测窗口时,MUST 拒绝该 kind,不能改用「进程还活着」凑合。 entrypoints.ui.kindSHOULD 为window。用户入口是该窗口,不是 URL。
headless
start必填。- 无人类 UI。
entrypoints.uiSHOULD 为{ "kind": "none" }。 - 就绪预留
ready.kind=notify:进程在超时前向$APP_DATA_DIR/ready写入一行(建议 ISO-8601 时间戳)并关闭文件。宿主看到该文件出现即就绪。 - 也可声明
ready.kind=http或tcp,若进程选择听端口。
oneshot
start必填。- 就绪即进程退出。
ready.kind缺省为exit-zero:退出码 0 成功,非 0 失败。 - MUST NOT 当长期会话持有。Host API 的
start在这里语义是「运行一次并返回结果」。 - 用于批处理、导出、索引,不是常驻 App。
container / wasm
start.argv不适用或另有字段(镜像引用、component 路径)。具体字段在该 kind 升强制时再冻。- 数据目录、权限声明、身份字段仍然适用。
- v1 MUST NOT 把
http解释成「其实可以是容器」。
预留 ready
health 是 v1 http 的就绪声明。通用形态预留为:
"ready": {
"kind": "http",
"path": "/health",
"timeoutMs": 20000
}
ready.kind | 状态 | 判定 |
|---|---|---|
http | v1 已实施(经 health) | GET http://$HOST:$PORT{path} 返回 2xx |
tcp | 预留 | 能连上 $HOST:$PORT 即可,不要求 HTTP |
exit-zero | 预留 | 进程以状态 0 退出 |
notify | 预留 | $APP_DATA_DIR/ready 出现 |
window | 预留 | 属于该进程的可见窗口出现 |
方向(在该字段进入 schema 之前不构成 v1 合规):
- 若同时写
health与ready,ready优先。 ready.kind与runtime.kind不匹配(例如http+window)时拒绝。- 不认识的
ready.kind必须拒绝,不能当成「进程已启动即就绪」。
v1 清单 MUST NOT 包含 ready。写了即按未知字段拒绝。
19. 预留:operations(对话操作)
用户打开 UI,智能体在对话里操作同一份应用状态。这是第二条消费者,不是第二种应用。
v1 不把 operations 收入 schema,也 不要求宿主实现 invoke。
下列名字予以占位,避免各家用浏览器刮页或把整份 OpenAPI 塞进系统提示。字段形态在进入 v1.1 schema 前可以补,不能改已写含义。
预留名:operations、invoke、bind.kind = http | mcp | cli、APP_INVOKE_TOKEN。
方向:
- 状态仍在应用里。对话记忆不是源。
- 智能体只调用声明过的操作。
- 宿主代发,智能体不直连端口。
- 列表只给
id与一句话;调用前再加载 schema。 - 破坏性操作要确认。
示意(不是合法 v1 清单):
"operations": [
{
"id": "notes.add",
"description": "Add a note. Use when the user wants to save text into Notes.",
"confirmation": false,
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["text"],
"properties": { "text": { "type": "string", "minLength": 1 } }
},
"bind": {
"kind": "http",
"method": "POST",
"path": "/api/notes"
}
}
]
| 名字 | 意图 |
|---|---|
operations[].id | 应用内稳定名,对话引用它而不是 URL |
description | 做什么 + 何时调用 |
confirmation | 为真时先取得用户同意 |
inputSchema | 代发前校验 |
bind | 宿主如何执行;不交给模型 |
未进入 v1 的应用仍然完整:用户打开 UI,智能体 start / stop / 读描述。浏览器刮页不是 HARP 约定。
20. 路线
| 阶段 | 做什么 | 不做什么 |
|---|---|---|
| v1 Draft | Package + Runtime ABI + 包内二进制 + 只接受 http 的 schema + 预留名列表(不进 schema)+ 合规表 | 把预留 kind / operations 写进 v1 schema;Host API 传输 |
| v1 Final | 两个独立宿主通过 C01–C15;fixture 含解释器与包内二进制各一份 | 把预留 kind 升为强制 |
| v1.1 | operations + 宿主 invoke(可先只实现 bind.http);start.platforms 强制化 | 公网暴露;自动把整个 MCP 挂进对话 |
| v2 | Host API 传输;window / oneshot;bind.mcp / bind.cli | 把 MCP 或 Skill 收编为基座 |
刻意推迟的东西:
- 应用商店与发现索引
- 代码签名、SBOM、可重现构建
- GPU / 本地模型声明
- 应用之间的官方 IPC
- 计费与授权许可执行
这些都可以在核心稳定后以扩展或后续版本出现。过早写进 v1,标准会变成某一家运行时的说明书。
21. 决策记录
| 决策 | 选择 | 理由 |
|---|---|---|
| 协议名 | HARP / 宿主应用运行时协议 | 对准「包 ↔ 宿主运行时」;避开 Agent Application(s) 与 MCP Apps |
| 主文件名 | harp.json | 与协议同名,可校验;不和 PWA manifest.json、APP.md 抢名 |
| v1 唯一强制 runtime | http | 先冻一种可测的原语;其他 kind 只占名 |
| 语言与二进制 | 运行时名 或 ./ 包内文件 | 任意能起进程的语言都合法,不必等新 kind |
| 非 Web | 预留 kind / ready / ui | 名字冻结,避免厂商抢注;v1 不强制实现 |
| 不把 Host API 放入 v1 强制范围 | 只冻包和 ABI | Skill 靠文件约定成功;MCP 级 RPC 等有第二个实现再冻 |
| 默认网络 | loopback | 本地产品够用;出站必须用户同意 |
| 默认文件系统 | data-only | 长期进程的最低可信承诺 |
| 健康检查 | 匿名 HTTP GET | 不发明新的就绪协议 |
| 扩展 | x-<vendor> | 产品差异不污染核心 |
| 与 MCP 的关系 | 可选入口,不是基座 | 应用的主消费者是用户,不是工具调用方 |
| 对话操作 | v1.1 再收入 schema;v1 只占名 | 避免模型把 operations 当成最小合法集 |
id 语义 | 宿主本地标识 | 不假装全球唯一;跨宿主互认留到签名/摘要 |
22. 下一步(若要将草案推进行业)
- 把第 7.7 节 schema 与第 14 节用例抽成独立仓库
harp-spec。 - 实现官方 fixture 应用与宿主侧测试夹具(进程隔离可用假宿主)。
- 写一份不超过两页的 Authoring Guide for Agents,专供系统提示引用,且只引用第 7.2 节最小合法集。
- 找第二个宿主按本文实现,冻结 v1 Final。
- 在规范仓库用 errata 而不是产品发行说明演进语义。
在第二家实现出现之前,本文应保持 Draft。强制范围只减不增;预留名列表可以补 ABI 细节,但已公布的 kind / ready / ui 名字不得改义或挪作他用。