mcpField
R
Run402

Run402

远程服务
run402.com云与基础设施24今天更新

关于 Run402

Run402 是面向 AI Agent 的开源后端基础设施,提供 Postgres、用户认证、文件存储、无服务器函数和站点托管。Agent 可通过 CLI、SDK 或 MCP 自行部署,并以 x402 或 MPP 等方式支付用量。

主要功能

  1. 01Postgres 数据库与 REST API
  2. 02用户认证与内容寻址文件存储
  3. 03无服务器函数与静态站点托管
  4. 04分阶段部署流程与 deploy 检查
  5. 05CLI、TypeScript SDK 与 MCP 多种接入方式

适用场景

  1. 01由 Agent 自动为应用创建数据库与站点
  2. 02通过 CLI 或 CI 脚本部署并验证应用
  3. 03在 Claude Desktop 或 Cursor 中调用后端操作

MCP 相关

Run402 提供 run402-mcp,将核心后端操作以 MCP 工具的形式提供给 Claude Desktop、Cursor、Cline 与 Claude Code 等 MCP 客户端。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://mcp.run402.com/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

安装包
npmrun402-mcp4.104.2

工具

2

此工具无需参数。

详细介绍

run402 logo

run402:面向 AI Agent 的全栈后端基础设施

Postgres、身份验证、存储、无服务器函数和分阶段部署 — 均由 Agent 通过 CLI 创建、运行并付费,提供用于编写脚本的类型化 SDK, 并通过 MCP 服务支持原生工具宿主。无需云控制台,无需注册。开源。

Tests CodeQL npm: @run402/sdk npm: run402 npm: run402-mcp npm: @run402/functions License: MIT

Run402 是面向 AI Agent 和编码 Agent 的开源后端基础设施——一种面向机器而非个人的后端即服务。自主 Agent 可创建 Postgres 数据库、用户身份验证、文件存储、无服务器函数和网站托管,通过一个分阶段部署工作流将其发布,并自行支付使用费用。其功能范围与 Supabase、Firebase 或 Vercel 相近;不同之处在于,无需登录仪表板即可开始使用(控制台供人使用,Agent 无需访问),也无需复制由人签发的 API 密钥。

这是 Kychee 的开源产品所依托的后端。我们需要一个 Agent 能够端到端驱动、并可灵活满足各应用需求的基础层,但现成方案都无法完全满足,因此我们自行构建,并以开放应用的同样方式开源:本仓库包含 Agent 接口(MIT);run402-core 包含可自托管运行时的开源部分(Apache-2.0;托管版 Cloud 控制平面仍为专有,详见 CLOUD_VS_CORE.md);kysigned 是第一个基于它运行的产品。

调用一次 run402,Agent 即可获得完整的 Postgres 数据库、REST API、用户身份验证、内容寻址文件存储、静态网站托管、无服务器函数和图像生成,并通过 x402(Base 上的 USDC)、MPP(Tempo 上的 pathUSD,或通过 Bitcoin Lightning 支付的 sats)或银行卡充值额度付费。原型版套餐在测试网上免费。

**Run402 以 Agent 为先,是因为 Agent 是一等参与者,而不是因为人的参与消失了。**个人或 Agent 通过自己的 Run402 主体和验证器进行操作,且操作始终可追溯。身份用于回答谁执行了操作;成员关系、角色、授权、授权密钥、新鲜度和支出策略则决定该主体可以执行什么操作。

自主 Agent 可以合法地继续拥有自己创建的单人组织。个人可以通过明确的共同所有权加入。进入他人组织的 Agent 会获得有界权限,而不是借用人类账户。密钥各不相同,地位平等,权限明确。

默认使用 CLI 来创建、部署、检查和恢复。编写程序化的 TypeScript/JavaScript 工作流时,使用类型化且遵循约定的 SDK;Shell 脚本和 CI 仍可继续使用 CLI。MCP 面向原生支持 MCP 的宿主,直接 HTTP 则支持有意采用较低层级的集成。

此 monorepo 提供以下接口:

接口适用场景…
run402 CLI终端、脚本、CI、Agent 控制的 shell:JSON 输入、JSON 输出,失败时返回退出码
@run402/sdk在 TypeScript 中调用 run402:类型化内核,同构(Node 22 / Deno / Bun / V8 isolates),并提供自动加载本地 keystore + wallet + x402 / Lightning fetch 的 Node 入口
run402-mcpClaude Desktop、Cursor、Cline、Claude Code:将核心 run402 操作作为 MCP 工具使用
OpenClaw skillOpenClaw Agent(无需 MCP 服务)
Run402 for BuzzBuzz 用户与 Agent:从 run402.com 安装,预检/关联某个 Agent 的专用身份,部署一个上下文相关的演示,然后通过标准的 HTTPS/passkey 交接提供人类共同所有权;Buzz 保持不变
@run402/functions在已部署函数_内部_导入(db(req?)、adminDb()、auth.user()、email、ai、assets),并用于编辑器中的 TypeScript 自动补全。源码位于公开的 run402-core 仓库的 packages/functions 目录下;run402 Cloud 在打包函数 zip 时会使用已发布的 npm 包。
@run402/astro用于 SSR、ISR 缓存、托管认证组件和图片变体的 Astro 集成

这些接口在适当的情况下共享同一个类型化内核:@run402/sdk。MCP 工具、CLI 子命令和 OpenClaw 脚本都是 SDK 调用之上的轻量封装。@run402/functions 是在已部署代码内部运行的函数内辅助工具;registry 上的 npm 包就是 Cloud 打包时使用的产物。@run402/astro 将 SDK 和函数运行时分层集成到 Astro 的构建和 SSR 流程中。HTTP API 是基础;SDK 负责共享的客户端工作流与编排;CLI 和 MCP 以机器友好的形式暴露它们。原生 SDK/MCP 参考资料说明了有意为之的替代方案。

部署摘要共享 SDK 工作流视图。CLI 将脱敏后的详细信息写入 .run402/diagnostics/ 下;MCP 通过 expand_result 保留这些信息。类型化 SDK 调用方保留完整结果。快照收集会自动排除平台运行时文件。

30 秒快速开始

首先根据 Your first deploy 创建完整的 run402.json 和 index.html。在该应用目录中运行以下命令;--name 请求创建一个新项目。

npm install -g run402@latest
run402 up --name my-app -y                           # bootstrap wallet/tier/project/link, then deploy manifest
run402 up verify                                     # rerun app HTTP verification without deploying
run402 up --verify                                   # deploy, then wait for gateway/edge coherence

这就是一个真实的 Postgres 数据库 + 一个已部署的静态站点,并使用测试网 USDC 自主完成支付。

使用同一个钱包从任意 x402 卖家购买,默认上限为 $0.10:

run402 pay https://seller.example/translate --method POST \
  --body '{"text":"hello"}' --max-usd 0.05 \
  --idempotency-key translation:1 --require-receipt

SDK 中等价的写法是 r.pay.fetch(url, init, { maxUsdMicros, idempotencyKey, requireReceipt }); MCP 调用方通过 run 片段执行相同的 SDK 调用。三者都返回 相同的 x402-commerce-result.v1 结算、movement/replay、交付、offer、 merchant-receipt、signer-relationship、policy 和原始证据字段,并将 未定价的 URL 以 payment: null 透传。当没有可用的基于钱包的 offer 时, 要求提供 receipt 会在支付前拒绝。如果承诺的 receipt 在结算后无法 验证,PaymentPolicyError 会保留上游响应和已支付结果,并告知调用方 进行对账——绝不要再次支付。 对于可信的 Run402 PAYMENT_INTENT_PENDING,三个接口都规定了一条 恢复路径:等待 Retry-After,然后用相同的付款方和密钥重复相同的请求。 绝不要更换密钥。只要进程还存活,SDK 和 MCP 还可以重新出示 不明确的证明;自定义/任意卖家的情况仍不明确,需要人工对账。 当仓库中存在 run402.deploy.json 或 app.json 时,优先使用 run402 up。CLI 始终是 Node SDK 动作运行器(r.actions.run(...) / r.up(...))之上的轻量封装:它先校验 manifest,然后仅递归执行缺失的前置条件。项目解析顺序为 --project、.run402/project.json、manifest 中的 project_id、经批准的、由 --name 创建的项目;全局活动状态绝不会用于选择部署目标。--name 仅作为项目创建/关联的元数据;它不属于部署 manifest,也绝不会重命名已有项目。在应用之前,使用 --check 进行本地校验,使用 --plan 获取经网关审核的意图。本地校验覆盖 manifest 引用的每个文件(迁移的 sql_path/sql_file、函数源文件与 files、站点路径与 dir() 目标、assets.put 源文件):任一文件缺失都会在任何网关调用之前、在所有模式下以及 run402 deploy 中,以 MANIFEST_FILE_MISSING 失败(details.missing[] 为 { field_path, path, kind },每个文件对应一个 create_file 后续动作)。当工作目录中没有 manifest 时,UP_MANIFEST_REQUIRED 会向下查找一层目录并说明找到的内容(details.nearby_manifests[],对单个候选返回只读的 run_in_directory 动作,对多个应用返回一个不分先后的 select_application 动作);--manifest <path> 指向不存在的文件时,会返回类型化的 MANIFEST_NOT_FOUND。

如果应用 manifest 定义了 verify.http[],run402 up 会在部署后校验这些 URL。在绑定仍在收敛期间,新的 run402 边缘哨兵未命中会报告为 propagation_pending,而非永久性失败;可用 --propagation-budget-s(默认 120)调整该等待时间,或用 --no-propagation-wait 立即返回。run402 up verify 会重新运行相同的 HTTP 检查,而不上传、部署、创建项目或变更资源。

CLI 会机会性地检查是否有更新的 run402 版本,并采用 fail-open 策略。成功的 stdout 仍是命令结果;版本过旧的提示是 stderr 上的建议性 JSON,或在 --json-stream 中表现为 cli.update_available NDJSON 事件。run402 doctor --refresh 是显式的实时 npm 检查,会报告安装上下文,以及针对本地、全局或临时安装的最安全升级命令。run402 doctor 返回 { ok, blocking[], warnings[], checks[] }:当且仅当 blocking[] 为空时 ok 为 true,每项检查都带有 severity: "blocking" | "advisory" | "info",建议性发现(未绑定的 passkey、过旧的 CLI、vault 缺口、仍可访问另一组织项目的无层级自有组织:TIER_MISSING_ON_OWN_ORG)会进入 warnings[],而不改变 ok 或退出码,并且 tier 检查的 status 使用固定词汇表(ok | inactive | frozen | past_due | dormant | purged | missing | unknown | error,绝不会是层级名称;名称与原始生命周期位于 value.tier / value.lifecycle)。

类型化部署配置使用相同的命令。可执行配置是受信任的本地代码,因此 v1 仅在显式传入时才运行它们:

run402 up --manifest run402.deploy.ts --check
run402 up --manifest run402.deploy.ts --plan
run402 up --manifest run402.deploy.ts --require-plan plan_...

--check 和 --print-spec 仅限本地(两者都会校验每个被引用的文件是否存在)。--plan 会向网关请求一份经审核的计划,包含 plan_id、plan_fingerprint、警告、diff 以及一个后续动作。仅当规范化后的 spec 与经审核的网关事件仍然匹配时,--require-plan 才会重新应用。

import { defineConfig, dir, nodeFunction, sqlFile } from "@run402/sdk/config";

export default defineConfig(({ env }) => ({
  project_id: env.required("RUN402_PROJECT_ID"),
  database: { migrations: [sqlFile("db/001_init.sql")] },
  site: { replace: dir("dist"), public_paths: { mode: "implicit" } },
  functions: { replace: { api: nodeFunction("dist/functions/api.js") } },
  secrets: { require: ["OPENAI_API_KEY"] },
}));

辅助函数会规范化为与 JSON manifest 相同的 ReleaseSpec。dir() 以确定性方式遍历,并拒绝不安全的文件,除非显式允许;sqlFile() 从文件名推导迁移 id,除非另行提供;nodeFunction() 目前期望 JavaScript 输出;请让 TypeScript 函数指向已构建的 .js 文件。

各模式

即贴即用资产:带 SRI 的内容寻址 URL

使用 CLI 上传文件。在应用程序数据中保存引用时,保留返回的 AssetRef,包括不可变身份和图像变体。不要自行重建内容哈希或变体 URL。

run402 assets put ./logo.png ./app.js ./app.css --project prj_example

当这些文件必须随某个发布版本一起激活时,请使用 manifest 资产切片。有关 HTML 输出器和以编程方式组合,请参阅存储指南和原生 SDK AssetRef 辅助工具。二进制文件必须保持为字节;在上传前绝不要将其作为 UTF-8 读取。

默认不可见的表 + expose manifest

你创建的表在 manifest 中声明之前,无法通过 /rest/v1/* 访问。这堵住了“Agent 创建了表,忘记设置 RLS,数据泄露”这一自伤陷阱。针对一个存在但未声明的表使用有效的 anon key 会收到结构化的 403 TABLE_NOT_EXPOSED(绝不是裸的 Postgres 42501),其 next_actions 会明确说明:expose_table(声明它并重新部署)、edit_request(expose 端点),或 use_function(让它保持不可见,并通过 adminDb() 从函数中读取);这不是 RLS 问题。manifest 是收敛的:应用两次是无操作;在两次应用之间移除的项,其策略、授权、触发器和视图都会被删除。

cat > manifest.json <<'EOF'
{
  "$schema": "https://run402.com/schemas/manifest.v1.json",
  "version": "1",
  "tables": [
    { "name": "items",  "expose": true,  "policy": "user_owns_rows",
      "owner_column": "user_id", "force_owner_on_insert": true },
    { "name": "audit",  "expose": false }
  ],
  "views": [
    { "name": "leaderboard", "base": "items", "select": ["user_id", "score"], "expose": true }
  ],
  "rpcs": [
    { "name": "compute_streak", "signature": "(user_id uuid)", "grant_to": ["authenticated"] }
  ]
}
EOF

run402 projects validate-expose <project_id> --file manifest.json
run402 projects apply-expose    <project_id> --file manifest.json
run402 projects get-expose   <project_id>

内置策略:user_owns_rows(满足 owner_column = auth.uid() 的行;当 force_owner_on_insert: true 时,由 BEFORE INSERT 触发器设置它)、public_read_authenticated_write(任何人都可读,任何已认证用户可写)、public_read_write_UNRESTRICTED(完全开放;需要 i_understand_this_is_unrestricted: true),以及 custom(逃生通道:你自己的 CREATE POLICY SQL)。

在应用前,使用 run402 projects validate-expose 进行非变更式反馈循环。可选的迁移 SQL 仅用于检查 manifest 引用;它不会作为 PostgreSQL dry run 执行,并且这不会验证 deploy manifest。

Auth-as-SDLC: 将相同的 JSON 放在 v2 ReleaseSpec 的 database.expose 下。网关在部署期间会根据你的迁移 SQL 对其进行验证,并以结构化的 errors 数组拒绝不匹配项,列出每一处违规。

目录部署(高级原语)

对于现有项目上的独立静态目录:

run402 sites deploy-dir ./dist --project prj_example > result.json 2> events.log

对于带有 deploy manifest 的完整应用程序,请使用 run402 up。SDK 负责文件哈希、上传去重和发布编排;CLI 呈现进度和结果。

同源 Web 路由:静态站点 + 函数入口

Apply-v1 路由和静态公共路径是发布资源:发布指针在 run402 deploy 中所需的部署阶段完成后才会激活。已应用的迁移和外部副作用不会因更改该指针而回滚。发布静态资产路径(如 events.html)与浏览器可见的公共静态路径(如 /events)不同。对于普通的简洁静态 URL,请使用 site.public_paths;将路由保留给函数入口和精确的、感知方法的静态别名。

{
  "project_id": "prj_...",
  "site": {
    "replace": {
      "index.html": { "data": "<!doctype html><main id='app'></main><script>fetch('/api/hello')</script>" },
      "events.html": { "data": "<!doctype html><h1>Events</h1>" }
    },
    "public_paths": {
      "mode": "explicit",
      "replace": {
        "/events": { "asset": "events.html", "cache_class": "html" }
      }
    }
  },
  "functions": {
    "replace": {
      "api": {
        "runtime": "node22",
        "source": {
          "data": "export default async function handler(req) { const url = new URL(req.url); return Response.json({ ok: true, path: url.pathname }); }"
        }
      },
      "login": {
        "runtime": "node22",
        "source": { "data": "export default async function handler(req) { return Response.json({ ok: true }); }" }
      }
    }
  },
  "routes": {
    "replace": [
      { "pattern": "/api/*", "methods": ["GET", "POST", "OPTIONS"], "target": { "type": "function", "name": "api" } },
      { "pattern": "/login", "methods": ["POST"], "target": { "type": "function", "name": "login" } }
    ]
  }
}

site.public_paths.mode: "explicit" 意味着只有完整的 public_paths.replace 表可直接作为静态 URL 访问。在示例中,/events 提供发布资产 events.html,而 /events.html 除非单独声明,否则不是公共的。mode: "implicit" 会恢复基于文件名的公共可访问性,并可能扩大访问范围,因此在确认它之前请先查看网关警告。 省略 routes 或传入 routes: null,即可沿用基础路由。使用 routes: { "replace": [] } 可清空路由表。路由条目是有序的 replace 列表,而不是以路径为键的映射。函数目标使用 { "type": "function", "name": "<materialized function name>" }。静态路由目标仅使用精确模式,方法为 ["GET"] 或 ["GET","HEAD"],并使用 { "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } },其中 file 是发布静态资源路径,而非公共路径、URL、CAS 哈希、重写或重定向。对于支持方法区分的别名,例如静态 GET /login 加函数 POST /login,请使用静态路由目标;在显式公共路径模式下,底层资源可以通过文件名保持私有。直接调用 /functions/v1/:name 仍受 API 密钥保护;浏览器路由路径则是公开的同源入口。

函数路由可以在处理程序运行前收取固定的租户 x402 费用,方法是在路由条目中添加 pricing: { "mode": "always", "amount_usd_micros": 250000, "pay_to": "org_default_payout" }。250000 表示每个匹配操作收费 $0.25。可移植的 ReleaseSpec 合约也允许在定价函数路由上使用 receipt: "on_fulfillment";兼容的主机随后会要求函数返回 payment.fulfilled(response),才会生成收据。Run402 托管的广告功能仍处于关闭状态,直到标准委托签名者载体可用为止——收据意图绝不会被静默降级。生产环境仅使用主网时省略 networks;接受测试网时显式包含 "testnet"。添加 "lightning"(或 "lightning-testnet"),即可通过 Bitcoin Lightning 上的 x402 exact 方案以聪(sats)出售,并结算到你自己的节点:使用 run402 orgs lightning-receiver set <org_id> - 注册一次仅接收的 Nostr Wallet Connect 配对(配对信息通过标准输入传入),并使用 "amount_msat" 固定聪价格,或者让每次挑战都按美元价格报价。仅使用 Lightning 的路由无需收款钱包;Run402 不会持有聪、可用于支出的配对信息或原像。静态别名不能定价,直接调用函数不会被收费,服务密钥和管理密钥也不能绕过已定价的浏览器路由。所属组织必须有可解析的收款钱包:使用 run402 orgs payout-wallet <org_id> <wallet_address> 设置。条件式积分系统应提供一个固定价格路由(例如 POST /api/credits),然后让应用的其余部分继续使用未定价路由,并在应用内部进行授权。

匹配仅支持精确匹配或末尾前缀通配符。/admin 和 /admin/ 是等效的精确路径;/admin/* 匹配子路径,但不匹配 /admin、/admin/、/admin.css 或 /administrator,因此,对于经过路由的版块根路径,请同时部署 /admin 和 /admin/*。匹配时会忽略查询字符串,并在处理程序的完整公共 req.url 中保留查询字符串。精确路由优先于前缀路由;前缀越长,优先级越高;方法匹配的动态路由优先于静态资源。POST /login 路由可以与静态 GET /login HTML 并存。方法不匹配的不安全请求会返回 405;匹配到的动态路由失败时会直接失败关闭,不会回退到静态文件。 路由函数使用 Node 22 Fetch Request → Response 契约:export default async function handler(req) { ... }。req.method 是浏览器请求方法,req.url 是托管子域、主机和已验证自定义域上的完整公开 URL。请据此推导 OAuth 回调,例如 new URL("/admin/oauth/google/callback", new URL(req.url).origin)。使用 headers.append("Set-Cookie", value) 追加多个 Cookie;重定向、Cookie 和查询字符串都会保留。在计费路由上,从 @run402/functions 导入 getRoutedPaymentContext,读取 const paymentContext = getRoutedPaymentContext(req),并使用 paymentContext.paymentId 作为应用侧幂等键。对于启用了收据的路由,只有在响应表示交付已完成后,才返回 payment.fulfilled(response);在已结算、当前且启用了收据的路由调用之外,该辅助函数会安全失败关闭。上下文辅助函数会读取网关确认的 x-run402-payment-* 标头,并在未计费或直接调用时返回 null。原始的 run402.routed_http.v1 信封属于内部实现;不要据此编写路由处理函数。

配方:静态主页 + SPA 外壳。 SPA 网站会将 index.html 作为外壳,为所有未匹配的路由提供服务(匹配 spa_fallback),因此默认情况下,GET / 也会提供该外壳。若要在保留应用路由外壳的同时,让 / 提供真正的静态主页,请在网站根目录的 index.html 旁放置 home.html,并添加精确的根静态路由别名:"routes": { "replace": [ { "pattern": "/", "target": { "type": "static", "file": "home.html" } } ] }。路由匹配会在所有静态资源解析之前运行(包括隐式的 / → index.html 根映射),而 SPA 回退的推导独立于路由表,因此 GET / 会提供 home.html(route_static_alias),未匹配的应用路由(如 /dashboard)仍会提供外壳(spa_fallback),具名静态页面则保持原样(static_exact)。预计会出现两个非阻塞的计划 lint:STATIC_ALIAS_SHADOWS_STATIC_PATH(警告:该别名覆盖了 / 原本会提供的内容;此处提示准确且符合预期)和 STATIC_ALIAS_DUPLICATE_CANONICAL_URL(信息:在隐式公开路径模式下,/home.html 仍可直接访问;如果重复内容会影响 SEO,请在 home.html 中添加 <link rel="canonical">)。后续部署若省略 routes,别名会沿用;routes.replace 是全量替换,因此发送该字段的流水线必须每次都包含此别名。使用 run402 deploy resolve --url https://<your-site>/ --method GET(或 r.project(id).apply.resolve)进行验证,并确认 match: "route_static_alias" 且 target_file: "home.html"。

避免为每个静态文件都配置路由、默认使用宽泛的方法列表、使用通配符静态路由目标、使用带前导斜杠的静态文件路径、使用目录简写,以及使用会耗尽路由上限的逐页静态路由目标表。还要留意会遮蔽直接公开静态路径的通配符函数路由。需要处理的警告代码包括 STATIC_ALIAS_SHADOWS_STATIC_PATH、STATIC_ALIAS_RELATIVE_ASSET_RISK、STATIC_ALIAS_DUPLICATE_CANONICAL_URL、STATIC_ALIAS_EXTENSIONLESS_NON_HTML 和 STATIC_ALIAS_TABLE_NEAR_LIMIT;请检查活动路由、static_public_paths 和解析诊断,以区分路由模式与底层的 asset_path。

使用 CLI 或对应的 MCP/SDK 接口解析公开 URL:

run402 deploy resolve https://example.com/events --project prj_123 --method GET
run402 deploy resolve --url https://example.com/events?utm=x#hero --project prj_123 --method GET
run402 deploy resolve --host example.com --path /events --project prj_123 --method GET

r.project(id).apply.resolve({ url, method: "GET" })(来自 MCP,一个 run 片段)会返回 would_serve、diagnostic_status、match、规范化后的请求数据、警告、完整解析 JSON、edge_propagation 以及后续步骤。返回时,asset_path、reachability_authority 和 direct 会说明公开 URL 背后由哪个发布资产支撑,以及可达性是来自隐式文件路径模式、显式 site.public_paths,还是仅路由的静态别名。稳定主机诊断还可能包含 authorization_result、cas_object(sha256、exists、expected_size、actual_size)、主机名特定的 response_variant、路由/静态字段(如 allow、route_pattern、target_type、target_name 和 target_file),以及 edge_propagation(settled、propagating 或 sync_pending)。已知的 match 字面量包括 host_missing、manifest_missing、active_release_missing、unsupported_manifest_version、path_error、none、static_exact、static_index、spa_fallback、spa_fallback_missing、route_function、route_static_alias 和 route_method_miss;请保留未知的未来字符串。已知的 authorization_result 值包括 authorized、not_public、not_applicable、manifest_missing、target_missing、active_release_missing、unsupported_manifest_version、path_error、missing_cas_object、unfinalized_or_deleting_cas_object、size_mismatch 和 unauthorized_cas_object。已知的 fallback_state 值包括 active_release_missing、unsupported_manifest_version 和 negative_cache_hit;请保留未知的未来字符串。result 是诊断正文状态,而不是 SDK 调用的 HTTP 状态,因此主机未命中的调用仍可能是成功的 CLI/MCP/SDK 调用,并带有 would_serve: false。不要将 resolve/diagnose 视为抓取、缓存清除或缓存策略判定器;路由方法未命中应检查 allow,CAS 授权/健康检查失败应检查或重新部署受影响的静态资产,新的主机未命中应检查 edge_propagation 或重新运行 run402 up verify。根据 cache_class 等结构化 JSON 字段进行分支,并保留未知缓存类。

发布可观测性会暴露稳定的资产身份和公开可达性。清单包括 release_generation、static_manifest_sha256、可为空的 static_manifest_metadata(file_count、total_bytes、cache_classes、cache_class_sources、spa_fallback),以及返回时的 static_public_paths[]。site.paths 列出发布静态资产;static_public_paths[] 列出浏览器可见的公开路径,包含 public_path、asset_path、reachability_authority、direct、缓存类和内容类型。计划和发布差异会暴露 static_assets 计数器:unchanged/changed/added/removed、newly_uploaded_cas_bytes、reused_cas_bytes、deployment_copy_bytes_eliminated、legacy_immutable_warnings、previous_immutable_failures 和 cas_authorization_failures。

需要根据其进行分支的运行时路由失败代码:ROUTE_MANIFEST_LOAD_FAILED(清单/传播)、ROUTED_INVOKE_WORKER_SECRET_MISSING(自定义域 Worker 密钥)、ROUTED_INVOKE_AUTH_FAILED(内部调用签名)、ROUTED_ROUTE_STALE(选中的路由未通过发布重新验证)、ROUTE_METHOD_NOT_ALLOWED(方法不匹配)、PAYOUT_WALLET_REQUIRED / PAYOUT_WALLET_AMBIGUOUS / PAYOUT_WALLET_UNRESOLVED(付费路由的收款设置)、PAYMENT_PROOF_MISMATCH(过期或错误的 x402 证明),以及 ROUTED_RESPONSE_TOO_LARGE(正文超过 6 MiB)。

GitHub Actions OIDC 部署:关联一次,使用相同的 CLI 部署

对于由仓库驱动的部署,run402 无需在 GitHub secrets 中保存服务密钥或钱包文件。只需运行一次本地关联命令:

run402 ci link github --project prj_... --manifest run402.deploy.json
# Optional route authority for CI route declarations:
run402 ci link github --project prj_... --manifest run402.deploy.json --route-scope /admin --route-scope /api/*

这会创建一个部署范围的 /ci/v1/* 绑定,并生成一个工作流,该工作流会授予 id-token: write 权限、检出仓库,并运行现有的部署基元:

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to run402
        run: npx --yes [email protected] deploy --manifest 'run402.deploy.json' --project 'prj_...'

CI 部署的范围有意受到限制:仅支持 site、functions、database、缺省或当前的 base,以及仅在绑定包含覆盖范围的 --route-scope 模式时支持路由声明。没有路由范围时,CI 无法发布 routes。密钥、域名、子域名、检查项、非当前的 base 和更广泛的信任变更,都应通过由本地钱包支持的部署完成。如果网关返回 CI_ROUTE_SCOPE_DENIED,请使用精确范围(如 /admin)或末尾带通配符的范围(如 /api/*)重新关联,或者在本地部署。使用 run402 ci list 和 run402 ci revoke 管理绑定。

函数内辅助工具:调用方上下文与 BYPASSRLS

在已部署的函数中,从 @run402/functions 导入。两个不同的数据库客户端可保持 RLS 的整洁:

import { db, adminDb, auth, email, ai } from "@run402/functions";

export default async (req: Request) => {
  const user = await auth.requireUser();

  // Caller-context: db() mints a 60s actor JWT so run402.current_user_id() resolves in RLS.
  // No .eq("user_id", user.id) needed: RLS already binds the visitor's rows; the redundant
  // filter is a deploy-fail (R402_AUTH_REDUNDANT_USER_FILTER) under @run402/functions v3.0+.
  const mine = await db().from("items").select("*");

  // BYPASSRLS: for platform-authored writes (audit logs, cron cleanup, webhook handlers).
  await adminDb().from("audit").insert({ event: "items_read", user_id: user.id });

  // Send mail from the configured default outbound mailbox.
  if (mine.length === 0) {
    await email.send({ to: user.email, subject: "Welcome", html: "<h1>hi</h1>" });
  }

  return Response.json(mine);
};

adminDb().sql(query, params?) 会执行原始参数化 SQL,并始终绕过 RLS。当前运行时返回网关封装结果,其中包含 rows 和 row_count;请读取 result.rows,而非 result[0]。旧版辅助工具类型错误地将其描述为裸数组。请参阅所属运行时参考,并使本地辅助工具类型与部署所用的运行时版本保持一致。

@run402/functions 会自动打包进已部署的代码;在编辑器中安装它即可获得完整的 TypeScript 自动补全(构建时进行静态站点生成也适用,前提是已设置 RUN402_SERVICE_KEY 和 RUN402_PROJECT_ID)。

已部署的函数中可使用 ai.generateImage({ prompt, aspect? }),支持生成头像或 OG 图片等实时应用流程。它会通过 RUN402_SERVICE_KEY 调用项目运行时图像端点,因此已部署的函数无需钱包或 x402 签名代码。可选宽高比为 square、landscape 和 portrait;返回结果为 { image, content_type, aspect },其中包含 base64 编码的图像字节。运行时图像生成会计费、受速率限制,并受项目组织的支出上限约束;公开路由的函数在调用前应对用户进行身份验证和速率限制。

assets.put(key, source, opts?) 会在已部署的函数中上传字节数据,并使用与部署时资源相同的、由 CAS 支持的应用底层机制。它使用 RUN402_SERVICE_KEY,接受字符串、Uint8Array 或 { content | bytes },并返回包含可变和不可变 URL 的、与 SDK 兼容的 AssetRef。

push.send({ title, body?, url?, tag?, to? }) 会向用户从应用页面注册的手机排入 Web 推送通知(<script src="/_run402/push.js">,然后调用 Run402Push.subscribe({ accessToken }));省略 to 可通知所有手机,或传入 { user_id }。送达通知计入组织的每日上限,run402 push status [--message <message_id>] 会显示注册用户以及每条消息是否送达。仅限 Run402 Cloud。

repo 和 repos 会将应用用户编辑的内容保存在应用仓库中:这些是项目拥有的普通 Git 仓库(未加密,不是 KyGit 保险库)。repo.readJson(path) 会读取 default 仓库(每个请求读取一个提交,不存在时返回 null);repo.commit({ [path]: value }) 会以已登录的最终用户为作者提交;repos.open(name) / repos.create(name, { from? }) 可访问其他仓库。Agent 可通过 run402 app-repos log|cat|ls 读取这些仓库,通过 run402 app-repos clone 克隆并推送,还可使用 { type: "repo", repo, path? } 路由目标提供最新提交,无需部署。每个文件最大为 4 MiB;媒体文件请使用 assets.put。

在函数外操作数据: 使用指定了明确项目的 CLI。对于有意进行的 HTTP 集成,原生 HTTP 参考区分了管理 REST 与调用方范围的 REST。切勿向浏览器暴露服务密钥。

run402 projects sql prj_example "SELECT count(*) FROM audit"
repos:你的仓库历史,在离开设备前已加密

run402 repos 是一个 Git 远程仓库,其内容会在你自己的机器上加密,并以一连串已签名、已准入的头部形式存储。它的存在,是为了让你的仓库历史不会随着写入它的机器一起消失——而这并不要求你把明文交给 Run402。线路协议为 r402s/v0。一个名词,十七个动词:KyGit 是品牌,vault 是资源,repo 是你拥有的东西——命令行中使用 run402 repos,SDK 中使用 r.repos。十七个动词中的两对会生成并领取单次使用的不记名密钥:handoff/resume 将已检出的工作树(包括未提交状态)从一个 Agent 交给 ANOTHER,发送方随即停止;invite/join 则让 SECOND 个 Agent 加入 SAME 工作,而第一个 Agent 继续工作,并共享一个协调室。请参阅下文的“Handoff / resume”和“Invite / join”。

三项声明,强度各不相同。以下是此功能获准使用的全部声明:

  1. Run402 无法解密你的 vault 或仓库历史。部署产物仍属于已披露的明文托管边界。 这是针对 Run402 本身的密码学保障:在 vault 通道中,源代码负载和仓库历史内容仅以密文形式存在;底层系统只保留列明的明文元数据,且不持有任何 vault 密钥。部署通道独立且已披露——平台以托管方式持有每次部署的明文产物。它可以读取你部署的内容,但无法读取你未部署的内容。
  2. 默认情况下,激活需要通过 vault 准入;显式且可审计的覆盖操作可以绕过此要求。 这是由平台执行且可审计的运行层不变量,并非针对执行该要求的平台本身的密码学保障。
  3. 数据保留是平台的运行承诺,而非针对平台本身的密码学保证(主机控制时间戳和字节)。

仅使用 vault 的路径——三行命令,保持肌肉记忆,无需付费:

run402 init                                   # once per machine
run402 repos create my-notes                  # project + vault + origin remote, one free call
git push -u origin main                       # publishes, encrypted before it leaves the machine

origin 采用追加式认领:当目录尚无 origin 时,脚手架会将我们的远程命名为 origin——git push origin main 可直接使用,无需记住其他远程名称。已有的 origin 绝不会被触碰;此时会改用 run402,响应会说明实际采用的远程及原因。位于其他仓库内部的应用(单仓库工作区)绝不会被脚手架加入该仓库:跳过操作会提供 create_nested_repo 后续操作;使用 run402 repos create --nested --project <project_id>(或 run402 up --nested)可为应用根目录创建自己的嵌套仓库和 run402 远程,同时只向外层仓库的本地 .git/info/exclude 追加一行,不触碰其中其他内容;repos create 仅会在实际添加了远程时打印 git push 后续操作。免费路径就是完整路径——无需 slug、无需费用、无需繁琐流程。若你想使用美观的 run402::<org-slug>/<name> 地址(按名称克隆、推送以创建),只需认领一次 org slug——这是下文所述的可选命名地址升级。

命名寻址。 run402::<org-slug>/<name> 可与 run402::<org_id>/<project_id> 形式的地址共用同一位置——只需认领一次 org slug(使用 run402 orgs slug <slug>,仅限所有者,需支付少量一次性费用),其下所有 repo 都可使用 run402::<slug>/<name>。推送一个尚不存在的名称即可推送创建:项目和 vault 会以原子方式分配;并发推送中未获胜的一方会顺利解析到获胜者的 repo,而不会报错——其工作不会丢失,只是没有成为创建者。命名远程首次在某个检出目录中解析时,解析出的 id 会固定到该目录的本地 git 配置中——此后每次推送或获取都会直接使用该固定 id,因此日后 org slug 或 repo 名称变更也不会导致现有克隆失效。id 形式的地址无需固定(项目 id 永不变更),并且仍是冷启动恢复路径:即使 Agent 丢失了本地状态,只要仍拥有该项目的权限,就始终可以退回使用 run402::<org_id>/<project_id>。 首先要了解一点:保险库有一组写入者,而不是单个密钥。 每位获准成员或交接接收方都会打开保险库,并使用自己的密钥库密钥推送(run402 repos access 会列出这些密钥)。如果保险库中唯一获准的主体就是这个密钥库,那么它的安全性就与这个密钥库完全相同——请参阅下文的“如果你丢失了密钥库”,或再添加一个主体。

显式、仪式化的形式仍然可用,并且和 git push 一样,会在首次使用时延迟分配——适用于脚本,或需要让收据输出到 JSON 标准输出而不是标准错误的情况:

# 1. Provision. Inside a repository that already exists, this adds the origin
#    remote (run402::<org_id>/<project_id>). Not a repository yet?
#    `run402 init --git-remote` creates one first. It needs a project selected
#    (`run402 projects use <project_id>`, or RUN402_PROJECT_ID).
run402 init

# 2. Allocate the repo's vault explicitly. Separate from `run402 init` on
#    purpose: this is the step that mints key material on this machine and
#    prints a one-shot recovery receipt. Idempotent — an existing vault comes
#    back deduplicated. (Skip this step and git push / repos capture
#    against an unallocated project allocates the SAME way, lazily, on
#    first use — the two paths don't stack; this one just does it now,
#    explicitly, so the receipt lands in JSON stdout instead of stderr.)
run402 repos create --project <project_id>

# 3. Snapshot — capture the working tree, encrypt it, publish a signed head.
run402 repos capture --message "wip: refactor the parser"
git push origin main            # ...or push your own branches, via git-remote-run402

# 4. View, then fsck: walk the head chain from your authenticated pin.
run402 repos view
run402 repos fsck --budget 500

# Restore anywhere, with plain git.
git clone run402::<org_id>/<project_id> restored

克隆需要这台机器上有一个 Run402 主体——一个钱包,以及一个保存着此保险库信封的密钥库——这是加密 git,而不是可分享的链接。

全新克隆会为每个没有分支指向的保留部署捕获提示安装本地 refs/r402/retain/<oid> 引用,因此普通的 git fsck 不会有输出——git for-each-ref refs/r402/ 会列出保留的内容。由比当前版本更旧的客户端创建的克隆(或引用写入降级的工作区)仍可能通过 git fsck 显示悬空提交;这无害,并非损坏——运行一次 run402 repos fsck 即可安装缺失的引用。本地保留引用会固定相关历史,避免其被 git gc 清理,直到保险库清除该捕获;之后下一次 fetch 会撤销该引用。

repos capture 是 CAPTURE 通道——协议部署引用加上 HEAD 目标——因为脏工作区会捕获为一个不在任何分支上的合成提交。你自己的分支和标签通过 git push origin <branch> 推送到保险库。

分配保险库不会限制项目的部署。 vault_policy 在你设置之前始终未设置——通过首次 git push 或 repos create 创建的保险库绝不会悄然改变你的部署方式。对已启用保险库但未设门禁的项目进行部署时,部署会在无门禁状态下继续,其结果会包含一个类型化的 next_actions 条目,建议运行 run402 repos policy required;之后每次未设门禁的部署都会包含一个 warnings[] 条目,指出这一偏差,直到策略被设置为任一状态——这绝不会阻止部署,也绝不会弹出交互式提示。一旦 vault_policy 设为 required,部署就必须提供与提交对应的保险库捕获——任何持有该密钥库的机器都能通过 run402 deploy 自动生成捕获;使用 run402 repos policy grandfathered --reason "<why>" 取消门禁(需要所有者身份验证和增强验证,且会记录审计,可通过 run402 repos policy required 撤销)。无论如何,部署都不会成为将源代码存入保险库的前置条件。run402 doctor 会报告策略、这台机器能否满足要求,以及密钥库的位置。

在 capture 报告任何内容已写入之前,客户端会将每张最终确认收据与本地预期清单进行比对,并从存储中回读获准的 HEAD——仅有 200 响应绝不够。维护只需一个动词:run402 repos gc——它涵盖 git gc 自身的两个阶段(检查点发布 + 清理计划),但绝不会被描述为“与 git gc 完全相同”,因为删除流程更加严格:它在本地制定计划,并且只有在同时提供两张验证器收据时才会提交——一张来自此 CLI,另一张来自独立的 r402s-verify——使用 --submit --intent-core <path> --verifier-receipt <path>;只有控制平面签名的完成确认才会说明删除了什么。

通过 SDK 使用时,语义完全相同——保险库读取可在任何环境中运行,而操作 git 工作树或磁盘上密钥库的动词仅支持 Node:

如需类型化仓库自动化,请参阅 原生 SDK KyGit 参考。 加密的第二个远程仓库——零迁移模式。 保留 GitHub/GitLab 作为主仓库(协作、CI、评审均保持不变),并将 KyGit 添加为第二个远程仓库:git remote add vault run402::<org_id>/<project_id> + git push vault --all,这样就有了一份完整、持续更新的历史副本,连存储提供商本身也无法读取。直截了当地说明这为何重要,并将其视为能力而非指控:能够读取私有仓库的托管方,可能会在未来的政策调整、收购、训练流水线、传票或数据泄露事件中将其编入索引、用于训练模型,或交给他人。Run402 无法解密你的保险库或仓库历史。部署制品仍处于已披露的明文保管边界内。

仅使用保险库的项目也是一等公民。 执行 run402 init(或 run402 repos create <name>),然后 git push origin …,再执行 gc / fsck / access,并且从不部署——这是一种受支持的使用方式,而非降级方案。有一点值得直言:仅使用保险库的项目没有部署通道,因此已披露的明文保管边界为空,也就没有托管恢复路径。

如果丢失密钥库。 只要主密钥库仍然存在,保险库就能保护源代码历史,避免主机端丢失。这里的“只要”至关重要:在 V0-A 中,整台机器或整个密钥库丢失,会导致保险库历史永久无法恢复,直到人类信封功能发布为止,而 run402 repos view 会逐字打印这句话。请备份 run402 repos view 报告为 keystore.root 且打印在终端丢失声明下方的密钥库目录——默认钱包使用 ~/.config/run402/vault,命名钱包使用 ~/.config/run402/profiles/<wallet>/vault。恢复回执是完整性锚点,而非解密密钥——它能证明你获取到的保险库就是你创建的那个,但无法解密任何内容。它不是秘密;副本越多越好。保险库越有价值,提醒就越强:创建时不会打扰你;一旦保险库达到以下任一条件,run402 doctor 就会发出持续存在的警告:代数 ≥10 / 大小 ≥10 MB / 距创建时间 ≥14 天。只有添加第二个主体才能清除此警告,证明文件不能清除,因为 V0 无法验证其真实性。

退出方案:镜像自己的副本。 run402 repos mirror <destination> [--profile <name> | --ambient](S3 或普通目录)用于配置保险库密文的第二份副本,该副本由客户所有——目标位置和凭据名称存放在密钥库旁边的配置文件中,绝不会放在 run402.config.json 中,也不会存放原始机密信息。设置完成后,每次捕获都会自动镜像到该位置,并作为独立的 mirror_push 字段与保险库结果一同报告;镜像失败绝不会阻止、拖慢或改变实际发布。按需运行 run402 repos mirror --backfill(幂等、可续传)即可补齐镜像;run402 repos fsck --mirror 是不需要密钥的完整性探测——它会报告可恢复的代数,且不会接触密钥材料。run402 repos recover <source> --out <dir> 完全不需要服务器:它会读取镜像、验证链,并仅使用本地密钥库解密。命令命名为 recover 而非 restore,因为 restore 在 git 中已有其他含义。依赖它之前,有两点需要了解:它能证明有效性,而非新鲜度——较旧的镜像看起来与实际历史较短的情况完全一样;没有密钥库或等效密钥的镜像无法恢复任何内容,因为镜像密文并不会生成第二把密钥;上文的 V0 终端丢失声明仍然适用。 人工备份路径(vault-recovery-custody)。 在 console.run402.com/account 完成源注册的人类组织成员持有一把等效密钥,但完全没有密钥库:其成员密钥以封装包装器的形式存放(passkey PRF 和/或源恢复码),而 run402 repos recovery-bundle 会下载版本化恢复包(密钥身份 + 包装器密文——平台仍然无法打开其中任何内容)。将该恢复包与保险库镜像一起保存——复制到镜像前缀下的 member-recovery-bundles/<name>.json——该恢复包 + 源恢复码 + 保险库的恢复回执即可恢复仓库,无需 run402 服务,也无需密钥库:run402 repos recover <source> --out <dir> --receipt <pin.json>(恢复码会通过隐藏输入提示输入;若恢复包不在镜像中,则使用 --bundle <file>)。原始 passkey PRF 输出有意不作为恢复输入——人工用户的无服务器恢复路径依赖恢复码。run402 doctor 的 recovery_posture 检查会告知你每个拥有保险库的组织是否已配置此后备方案。

run402 repos mirror s3://acme-vault-mirror --profile acme --region us-east-1
run402 repos mirror --backfill
run402 repos recover s3://acme-vault-mirror --out ./restored --repo src_1a2b3c
run402 repos recovery-bundle --out ./bundle.json     # the member's no-keystore recovery half
run402 repos recover ./mirror-copy --out ./restored --receipt ./recovery-receipt.json --bundle ./bundle.json

交接/恢复——将工作树(包括所有未提交状态)交给另一个 Agent。 run402 repos handoff 会捕获实际工作树——已暂存、未暂存和未跟踪的更改,与 git stash push -u 完全相同——并生成一个仅可使用一次的持有者密钥 kgh1_…,只向 stdout 输出一次(--json 也会确保它不出现在 stderr 中;如果丢失,就没有第二个地方可以找回它)。将该密钥交给另一个 Agent——可以是另一台机器、另一个会话,不需要共享密钥库或共享钱包——然后由对方运行 run402 repos resume kgh1_… 来领取;该命令会克隆一份全新的检出,并通过 git stash apply --index 恢复完全相同的未提交状态。恢复的 Agent 在加入过程中也会创建自己的 run402 钱包:如果没有活跃层级,resume 会像 create 一样执行相同的冷启动流程(钱包 → 水龙头 → 一次 x402 原型支付),然后才领取;--no-init 可选择跳过此流程,且领取操作不会等待它完成。交接说明会随附一份简短的 JSON 摘要,说明已完成事项、进行中事项、失败事项和后续步骤;resume 默认会将其呈现为 Markdown。该密钥具有实际权限——默认具有发送者自己的组织角色——直到被领取或其 TTL 到期(默认 1h,可通过 --ttl <seconds> 设置);生成响应会说明这一点,CLI 也会在打印密钥前回显警告。默认情况下,敏感的未跟踪文件(.env、*.pem、*.key、SSH/AWS/GPG 目录以及另外 18 种模式)不会被纳入捕获;可使用 --include-sensitive <glob> 选择重新纳入其中一项。

run402 repos handoff --note-file handoff.json     # captures the working tree, mints the key, prints it ALONE to stdout
run402 repos resume kgh1_…                         # on the other machine: redeem it, clone, restore, print the note

这两个动词都没有 MCP 工具——handoff 会生成持有者密钥,而 resume 会修改组织成员资格,理由与上文 create/delete 相同:修改操作只能通过 CLI 执行。

多个写入者。 保险库允许来自一组写入者密钥的头部提交,每个密钥都是某位成员自己的密钥库身份(协议版本 47)。resume 会在返回前将接收者设为写入者——接收者可立即使用自己的密钥执行 git push,之后即可删除发送者的环境。通过 run402 orgs members add 添加的任何成员(开发者或更高角色)也会以相同方式成为写入者:添加者的客户端可行时会直接纳入新密钥;无法纳入时则会拒绝添加(VAULT_WRITER_NOT_ADMITTED、request_writer_sync),因此不会有成员只能读取却无法推送。run402 repos view 会列出 writers[] 和 pending_writers[];run402 repos access sync 会按需纳入待处理密钥;移除成员会在下一次纪元轮换时生效,且该密钥之后永远无法重新添加。任何人的种子都不会被复制:写入者通过现有写入者的实时签名,或通过发送者签名的交接授权来获准;接收者会使用自己的密钥完成授权。 邀请 / 加入——把第二个 Agent 带入确切的工作中,包含未提交的脏工作树,并在共享房间中交谈。 Handoff 把工作移交出去;Invite 则让团队壮大。run402 repos invite 会像 handoff 一样精确捕获工作树——邀请者自己的工作树、索引、分支、refs 和访问权限都不受影响,并且会持续推送——在协调房间(项目的默认房间,或使用 --room <key> 指定一个具名组织房间)中注册邀请者自己的在线状态,生成一个一次性 bearer key kgi1_…,仅向 stdout 打印一次,并发布一条房间消息,注明检查点和邀请 id(绝不包含 key)。生成 key 需要一个 ACTIVE writer key,与 handoff 相同(INVITE_MINT_REQUIRES_WRITER 指明 run402 repos access sync 作为修复方法)。把这个 key 交给另一个 Agent,run402 repos join kgi1_… 就会自行付费加入——加入的 Agent 在兑换之前会经历与 resume 相同的冷启动链路(钱包 → faucet → 一次 x402 原型支付),因此它到达时已经拥有自己的、已付费的 run402 钱包——克隆一份全新的检出,在命令返回之前以其自己的 key 成为 vault 的 writer(不会从邀请者那里复制任何东西),恢复确切的脏状态,在本地固定邀请的房间,注册自己的在线状态,发布一条到达消息,并报告是谁邀请了它(名称、标签、是否仍然在线)、房间中还有谁,以及最近几条消息。两个 Agent 交错推送,各自用自己的 key 签名。此后,run402 messages wait 就是 Agent 的耳朵:它使用网关持有的读取来阻塞,直到另一方发言(或达到有界超时),绝不会因静默而报错,并且无论哪种情况都会报告谁仍然在线。生成的默认角色是 developer,且绝不会超过邀请者自己的角色;Invite Note(与 Handoff Note 形状相同)会一同传递,并在 join 时默认渲染为 Markdown。收回访问权限的方式是 run402 orgs members rm,它会轮换 vault 的 epoch,使被移除的 key 无法再推送,而所有其余 Agent 继续工作。

run402 repos invite --note-file invite.json       # captures the working tree, mints the key, prints it ALONE to stdout
run402 repos join kgi1_…                           # on the other machine: pay in, redeem, become a writer, clone, restore
run402 messages wait                               # then: block until the other agent speaks (or the timeout elapses)

与 handoff/resume 一样,invite 和 join 都没有 MCP 工具——invite 生成 bearer secret,join 会修改组织成员关系并写入工作树,理由与上文的 create/delete/handoff/resume 相同。

在无需信任我们客户端的情况下验证它。 r402s-verify 是同一协议的一个独立谱系验证器——不同的语言、不同的作者身份、不同的原语栈,刻意不与 SDK 共享任何实现代码。这种不共享正是关键:一个复用了它所检查的代码的差分验证器什么都验证不了。它位于本仓库的 r402s-verify 分支上,拥有自己的工作流,发布预构建的 release 二进制文件,也可以用 cargo build --release 构建。它所验证的完整协议规范和威胁模型发布在 docs/kygit/,冻结的一致性测试向量发布在 test-vectors/r402s-v0/。

成本。 不存在单独的 repos 价格——vault 的字节数计入组织池化的 vault 配额(sourceBytes:prototype 1 GB,hobby 10 GB,team 50 GB),这是一个与你项目共享的存储不同的池,按每个唯一对象计费一次,具有 4 KiB 的每对象计费下限和 1 MiB 的每 vault 最小值。

SDK:@run402/sdk

npm install @run402/sdk

两个入口点:

  • @run402/sdk:同构版本。自行提供 CredentialsProvider(会话令牌适配层、远程保险库,或任何可解析项目密钥和认证标头的方式)。适用于 Node 22、Deno、Bun 和 V8 隔离环境。
  • @run402/sdk/node:仅适用于 Node 的便捷版本。读取本地配置文件状态以及项目密钥凭据缓存(credentials/project-keys.v1.json),并从一个确定性来源为 x402 支付签名:显式的非透明 paymentSigner、显式的 walletPath、所提供的 provider 的 readWallet(),或默认的活动配置文件钱包。认证身份和付款方可以有意不同;所选付款来源绝不会回退到当前环境中的钱包。r.paymentPayer() 仅报告安全的公开付款方/来源信息。还提供 sites.deployDir(...)、fileSetFromDir(...)、类型化部署清单辅助函数(loadDeployManifest、normalizeDeployManifest),以及 resolveRun402TargetProfile(),供需要使用与 CLI 相同 Core/Cloud 目标的应用构建脚本调用。
import { run402 } from "@run402/sdk/node";

const r = run402();
// Prepare the complete first-deploy manifest and referenced app files.
const result = await r.up({ name: "my-app", manifest: "run402.json" }, { approval: "yes" });
console.log(result);

SDK 由多个专用命名空间组成:actions(Node 递归操作运行器)、pay(有界任意 URL 的 x402 购买方)、projects、snapshots、branches、archives、assets、cache、ci、sites、functions、jobs、secrets、subdomains、domains、projects.stripe(组织自己的 Stripe 账户:account、onboardingLinks、checkoutSessions、portalSessions)、push(Web 推送通知状态和逐条消息的投递记录:status、getMessage)、appRepos(应用拥有的普通 git 仓库:list、create、get、delete、log、readFile、tree、diff、commit、revert;不包括加密的 repos 保险库)、email(含 webhooks)、auth、apps、tier、billing、contracts、ai、wallets(本地钱包:status、create、export、faucet;以及服务器标签)、service、admin、session(个人登录会话:run402 login 的环回与 --device 接口,以及浏览器界面)、writeApproval(run402 approve 背后的通行密钥写入审批)、me(账户概览和状态)、wallets(已签名的服务器端钱包标签)、orgs(组织拥有的控制平面及 r.org(id) 子客户端)、grants(每个项目的能力授权)和 identityLinks(公开且按协议区分的人类/Agent Nostr 归属关联);此外还支持 const project = await r.project(id); await project.apply(spec),用于分阶段的多资源写入(通过 /apply/v1/* 提交发布切片和资源切片)。每项操作失败时都会抛出类型化的 Run402Error 子类:PaymentRequired、PaymentBuyerError、ProjectNotFound、Unauthorized、ApiError、NetworkError、LocalError、Run402DeployError。apply() 会自动重新规划安全的、基于当前基准版本的 BASE_RELEASE_CONFLICT 冲突,并发出 apply.retry 进度事件。请参阅 sdk/README.md。

Buzz/Nostr 身份关联

人类和 Agent 可以公开声明其各自持有的 Buzz/Nostr 身份与自己的 Run402 主体相关联。Agent 关联使用 EOA 加 kind-1 协议;人类关联使用普通浏览器、新建的通行密钥,以及在 https://console.run402.com/identity-links/connect 上完成的 Buzz 同意流程。两者都会生成相同的公开 idlnk_… 资源结构,并通过 proof_protocol 区分协议。一个主体可以有多个活动的 Nostr 身份,而一个活动的 Nostr 身份只能属于一个主体。这仅用于归属声明:Nostr 身份绝不会用于认证、授权、付款、部署或接收转账。Run402 从不接受或推导 nsec、Nostr 私钥、助记词、种子、通行密钥、会话凭据或派生路径。

面向人类用户的安装方式是一条 Buzz 消息——无需终端:

Please install the run402.com skill.

这就是完整的人工指令。在受管 Buzz 环境中,第一方发现机制会将其路由到 run402-buzz;Agent 会读取顶层安装路由,并将独立技能安装到其工作区(通常是用户主目录下的 .buzz 目录)。该请求意味着安装并连接:验证这些惰性文件后,Agent 会直接加载已安装的技能,并在同一轮中继续完成预检、设置和身份关联。它不会停在“下轮即可使用”,也不会再询问第二个设置问题。对于 Codex 运行时,最好将工作目录和环境分别提供给 Agent 的命令运行器:

working_directory: <user-home>/.buzz
environment: { "DO_NOT_TRACK": "1" }
command: npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

仅限 POSIX Shell 的环境使用:

cd "$HOME/.buzz"
DO_NOT_TRACK=1 npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

Windows PowerShell 使用:

Set-Location (Join-Path $HOME '.buzz')
$env:DO_NOT_TRACK = '1'
npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

Claude Code 使用 -a claude-code,Goose 使用 -a goose,已确认支持 .agents/skills 的使用方可以使用 -a universal,Claude Code 和 Codex 则使用 -a claude-code codex。universal 是共享路径,并不适用于所有运行时;不要使用无效的显式目标 -a claude。技能文件来自 run402.com 上经不可变摘要验证的制品;首次运行时,npx 仍可能需要 npm。GitHub 是唯一仅用于可用性回退的来源;任何完整性验证失败都会在设置前停止。成功时会报告观测到的第一方摘要和精确的受管工作区路径;绝不会把 GitHub 来源或全局运行时路径误标为第一方来源。

文件安装阶段不会执行任何操作。继续引导流程会发布一条持久的公开 kind-1 Nostr 事件和持久的 Run402 证明,将两个公开身份关联起来;撤销不会抹去其历史记录,Buzz 管理的事件还可能公开其所有者的 NIP-OA 证明。Agent 仅在需要时初始化,创建或复用关联,并独立验证;随后立即提供一项与上下文相关的快速测试或演示,同时在展开后的回执中保留 Deployment: none。在 Windows 上,设置助手会通过 shell: false 使用精确的受管 Node 运行时执行 npm 和 Run402 的 JavaScript 入口点,避免 .cmd 进程边界故障。它会等待明确批准后才进行构建或部署。独立验证线上应用后,它会创建一个惰性持久邀约,并发布普通的 HTTPS“成为所有者”交接页面。浏览器负责人工登录、通行密钥以及现有 Buzz 六位数同意回调;无需人工运行终端命令,也无需更改 Buzz。

请参阅 buzz/ 指南,了解前置条件、无密钥签名者模型、已发布客户端测试夹具、迁移指南和完整工作流;也可以查看 skills.sh 上的 run402-buzz 条目。底层 CLI 命令仍可用于调试,但它们不是另一条并行的引导路径。

社区控制平面将四个概念分别处理:安装技能是惰性的共享能力;安装社区是在双方同意后,将 Buzz 中继社区关联到 Run402 组织;人工接管会记录终端同意回执、创建该人员的公开 Buzz 身份关联,并添加普通所有者成员资格,同时不降低创始 Agent 的权限;Agent 注册则仅向之后的每个 Agent 主体授予针对指定现有项目、范围受限且会过期的授权。已完成的回执、公开归属信息和成员资格彼此独立:撤销关联不会移除组织权限,移除成员资格也不会撤销关联或改写回执。Buzz 本身保持不变:审批使用已发布的浏览器片段/kind-1 行为以及已发布的 NIP-11/NIP-43 证据;邀约、组织、描述符发现和生命周期则由 Run402 负责。run402 buzz status 会检测较旧网关的功能;MCP 只会呈现精确的 HTTPS/CLI 后续步骤。请参阅 Fizz/Honey 工作流。 Astro SSR + ISR 缓存。 对于 Astro 应用,请使用 @run402/astro 1.0+:在 astro.config.mjs 中使用 export default run402();,会返回一个 AstroUserConfig,组合 SSR 适配器(Lambda + ISR 缓存 + AsyncLocalStorage 请求上下文)、图像集成和构建时检测器。在 ReleaseSpec 中通过 FunctionSpec.class: "ssr",函数即可选择 SSR 类;网关会按 (host, path, search, method, locale, release_id) 缓存 HTML 响应。默认绕过缓存(除非 Cache-Control 明确允许,且没有 Set-Cookie,也没有来自 auth.* 助手/支付原语的 auth-taint 标记,否则使用 no-store)。可在函数代码内或带外执行失效操作:r.cache.invalidate(url) / r.cache.invalidatePrefix({ host, prefix }) / r.cache.invalidateAll({ host })(SDK),run402 cache invalidate <url>(CLI)。使用 r.cache.inspect(url) / run402 cache inspect <url> 检查缓存状态。CLI 中还提供 Agent 开发体验助手:run402 doctor(5 项健康检查)、run402 dev(使用 .env.local 启动 Astro 开发环境)、run402 logs --request-id req_...(跨函数关联日志)。完整参考请参阅 astro/README.md 和 cli/llms-cli.txt(R402_* SSR 运行时错误代码部分)。

CLI:run402

npm install -g run402@latest

每个子命令都会将 JSON 输出到 stdout,将 JSON 错误输出到 stderr;成功时退出码为 0,失败时为 1:专为 Agent shell 设计,而非面向人类。完整参考:cli/llms-cli.txt(也可访问 https://docs.run402.com/llms-cli.txt)——这是一个索引,包含完整的首次部署约定,以及可获取主题片段的表格(/llms-cli-deploy.txt、/llms-cli-commands.txt、/llms-cli-functions.txt,……);/llms-cli-full.txt 则将所有内容汇集在一个文档中。

run402 up --name my-app -y                # recursive SDK action runner: init/tier/project/link/deploy
run402 up verify                          # rerun app HTTP verification without a deploy
run402 up --nested -y                     # app root inside another repo: its own nested repo + encrypted remote
run402 doctor                             # { ok, blocking[], warnings[], checks[] }: ok means this agent can ship
run402 logs --request-id req_abc123       # every function in the project; app output only (--all for the raw stream)
run402 init                              # one-shot wallet + faucet + tier check
run402 pay https://seller.example/resource --max-usd 0.05 --require-receipt
run402 status                            # organization snapshot (wallet, rail, balances, tier, projects)
run402 projects provision --name my-app
run402 projects sql <project_id> "CREATE TABLE …"
run402 projects validate-expose <project_id> --file manifest.json
run402 projects apply-expose <project_id> --file manifest.json
run402 sites deploy-dir ./dist
run402 deploy verify op_... --project <project_id> --wait  # confirm gateway/edge release coherence
run402 deploy releases active --project <project_id>  # inspect current-live release inventory
run402 deploy resolve https://example.com/events --project <project_id> --method GET
run402 deploy --manifest app.json --json     # deploy only; rehearses automatically when a live release has migrations to protect
run402 snapshots list prj_...
run402 branches create prj_... --ttl-days 7 --json
run402 functions deploy <project_id> <name> --file fn.ts
run402 functions runs create <project_id> <name> --event-type reminder.send --idempotency-key reminder:123 --delay 10m
run402 ci link github --project <project_id>       # GitHub Actions OIDC deploy binding (--route-scope for CI routes)
run402 assets put ./asset.png --immutable
run402 assets diagnose <url>             # inspect live CDN state for a public URL
run402 cdn wait-fresh <url> --sha <hex>  # poll until a mutable URL serves the new SHA

up 是唯一的复合 CLI 命令:它会调用 SDK 操作运行器,输出 steps[],并在需要记住工作区项目时写入 .run402/project.json。针对 run402 Core 时,它会跳过 Cloud 钱包/层级前置条件;如果未选择 Core 项目,则会安全失败。

自动执行演练:当项目已有在线发布版本时,包含迁移的 run402 up / run402 deploy 会在隔离分支上进行演练,并且只有报告通过后才提交(result.deploy.rehearsal);首次部署没有需要保护的内容,因此会直接提交(reason: "no_live_release");如果重新部署中的所有迁移都已应用且校验和相同,则会标记为 migrations_unchanged 并跳过,因此即使仅重新部署页面、但仍携带迁移,也能在数秒内发布。--no-rehearse 可跳过演练。高级用法:run402 deploy rehearse [<plan_id>] [--manifest <path>] 可进行演练而不提交——既可使用已持久化的计划,也可使用当前目录中的清单(计划、上传、演练)。手动还原点位于 run402 snapshots create|list|get|restore|delete 下;还原采用两步的计划/确认流程。分支项目通过 run402 branches create|list|renew|delete 管理,默认 TTL 为 7 天,默认使用沙箱邮箱,并标记为 noindex;如果父项目没有在线发布版本,则会生成空分支。 你的 HTML 永远不需要粘贴密钥:每个 Run402 主机都会为其解析到的项目提供 /_run402/config.js(window.RUN402 = { project_id, api_base, anon_key })。当 run402 up 没有主体名称时,也会为其命名 —— 如果你的运行时声明了名称,则用 RUN402_AGENT_NAME(它会覆盖已有名称),否则用检测到的客户端(claude-code、codex、cursor、grok;RUN402_CLIENT=<name> 声明一个没有自身标记的客户端,优先检查);当两者都未知时不写入任何内容 —— 这样推广积分就会记到你名下;可随时用 run402 whoami --set-name <name> 设置。result.identity 始终报告 detected(本次运行所见到的客户端)和 detection: { applied, reason },其中 name_already_set 表示检测到了客户端,但该主体已有名称。projects provision 从不触碰 git;up 仅在应用根目录上搭建一个 run402 远程仓库,而位于另一个仓库内部的应用根目录会被跳过,并给出 create_nested_repo 后续操作,除非你传入 --nested,这会使其成为自己的嵌套仓库(在所属仓库的本地 .git/info/exclude 中追加一行,其他一概不动)。

可移植归档会导出 Cloud 项目所支持的 run402 Core 运行时切片,以便本地 Core 导入。这是无锁定的信任路径,与额度/支出上限的财务风险控制相互独立。

run402 archives create <project_id> --target cloud --scope portable-runtime-v1 --auth stubs --consistency pause-writes --wait --output ./project.r402ar --json
run402 archives verify ./project.r402ar --json
run402 archives import ./project.r402ar --target core --name imported-project --env-file ./required.env --json

--target 指定每个动词所对话的部署:create、status 和 download 从 Run402 Cloud 导出(--target cloud,默认值);import 加载到本地 Run402 Core(--target core,默认值)。归档 v1 不包含密钥值、认证凭据、日志、计费/额度状态、Cloud 运维元数据、Cloud 导入以及现有项目合并导入。验证是本地/离线的,检查完整性与兼容性;在 Core 导入验证并暂存它们之前,归档始终属于不可信输入。

当前项目是粘性的:run402 projects use <project_id> 会在服务端验证 <project_id>,并将其存储为后续接受 <project_id> 的子命令的默认值,因此大多数命令无需再传它即可工作。本地密钥材料在 run402 credentials project-keys ... 下单独管理;该缓存永远不是项目清单。

MCP 服务:run402-mcp

npx -y run402-mcp                        # standalone test

八个工具,在主机上下文中仅有几 KB 的 schema:up 和 deploy 用于首次部署,status、whoami、doctor、docs、run 和 expand_result。其他一切都是针对 r(Node SDK 客户端)的 run 代码片段,在 QuickJS-in-WebAssembly 沙箱中执行,自身没有文件系统、进程或网络。需要 Node.js 22.13 或更高版本。本地运行,所以才能真正付款: x402 支付需要签名密钥,因此没有钱包的远程服务无法发起支付。

远程端点(无需安装)

一个托管的 streamable-HTTP MCP 服务运行在 https://mcp.run402.com/mcp,仅提供免费发现类工具:run402_quickstart、x402_price_check(解码任意 URL 的 x402 挑战,不付费)和 experiment_scoreboard。它从不处理资金 —— 付费能力(图像生成、部署、支付)需要下面持有你的钱包的本地服务。注册表条目 com.run402/mcp 列出了两者(packages[] npm + remotes[])。远程服务本身作为 run402 函数运行 —— 平台托管着自己的 MCP 服务。

Stdio MCP 传输必须将 stdout 保留给 JSON-RPC。请使用包 bin(npx -y run402-mcp)或在已构建的检出目录中使用 node dist/index.js。如果主机坚持使用 npm start,请设置 npm_config_loglevel=silent;npm 的生命周期横幅会输出到 stdout,否则会作为非 JSON 前奏出现。仓库的 .npmrc 和 Docker 镜像已为源码/容器主机设置了此项。

Claude Desktop

添加到 ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
Cursor

添加到 .cursor/mcp.json:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
Cline

添加到你的 Cline MCP 设置:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}
Claude Code
claude mcp add run402 -- npx -y run402-mcp

OpenClaw skill

cp -r openclaw ~/.openclaw/skills/run402
cd ~/.openclaw/skills/run402/scripts && npm install

每个脚本都从 cli/lib/*.mjs 重新导出:从构造上看,OpenClaw 的命令接口与 CLI 的命令接口完全一致。参见 openclaw/README.md。

MCP 工具

工具作用
up首次部署:补齐任何缺失的设置(钱包、层级、项目、工作区链接),然后执行部署。返回 run402.up.result 信封。
deploy将 ReleaseSpec 应用到项目(r.project(id).apply):数据库、函数、站点、site.public_paths、资产、子域名、routes.replace。返回 DeployResult。
statusr.status():服务所代表的钱包(local_label、server_label、地址)、层级与租约、配额、项目、当前项目。
whoamir.orgs.whoami():远程主体、其认证器、组织成员身份以及登录会话等级。
doctorr.doctor():{ ok, blocking[], warnings[], checks[] }。
docs随包分发的 SDK 参考和 run 入门指南:topic(命名空间或章节)或 search。
run在沙箱中对 SDK 运行一段 TypeScript 代码片段;返回该值、捕获的日志以及它发起的 SDK 调用。
expand_result对存储的结果分页读取:run 的值或其日志、docs 的答案、up 的详情。

代码片段是 async 函数的主体;其最后一个表达式的值即为结果:

{ "code": "const { projects } = await r.projects.list();\nprojects.filter((p) => !p.site_url).map((p) => p.id)" }

结果为 { status, value, value_ref, shown, total, logs, logs_ref, calls, duration_ms, wallet, error? }:较大的值会按项完整存储(数组的元素、结果的 rows),其开头的完整项会出现在 value_window 中,包括在 structuredContent 里(expand_result 按项对其余部分分页),calls[] 列出每次 SDK 调用及其结果,而超时(默认 60 秒,最多 300 秒)仍会列出已完成的调用。SDK 错误会连同其自身的 code 和 next_actions 一并传递。

结构化结果。 每个工具还会在声明的 outputSchema 下以 structuredContent 返回其结果,因此宿主读取字段而非解析文本。该对象包含 status: "ok" | "error";固定工具将 SDK 对象放在 result 下,而每个错误都带有 error.code、error.message 和 error.next_actions。文本中的围栏 JSON 就是同一个对象。

一次性密钥保留在 CLI 中。 任何返回或消耗一次性密钥的操作(生成或轮换授权密钥、Handoff 或 Invite Key、Room Invite Key、配置项目或轮换其凭据、项目令牌、创建、导入或导出钱包、Lightning 配对)在 run 内部都会被拒绝,并给出 SECRET_REQUIRES_CLI 和一个后续操作 { "type": "run_cli_command", "command": "run402 …" },指明要交给该人员的准确命令。该拒绝发生在 SDK 方法本身,在任何请求之前,因此不会有任何机密进入结果。

完整参考:llms-mcp.txt。

配置

变量默认值用途
RUN402_API_BASEhttps://api.run402.comAPI 基础 URL(可在预发布环境中覆盖)
RUN402_CONFIG_DIR~/.config/run402本地凭据存储的基础目录(命名钱包位于 profiles/<name>/ 下)
RUN402_WALLETdefault当前激活的命名钱包(配置档)。可被 --wallet <name> 和目录级 .run402.json 覆盖;RUN402_PROFILE 是其别名。参见 run402 wallets。
RUN402_WALLET_PATH{config_dir}/wallet.json自定义钱包文件路径
RUN402_GRANT_KEY(未设置)来自 run402 grants create --key 的授权密钥 bearer。设置后它是唯一发送的凭据,因此没有钱包的进程也能进行部署。

本地状态位于:

  • 配置档 state.json:当前活动项目指针和配置档状态
  • 配置档 credentials/project-keys.v1.json(0600):本地匿名/服务密钥缓存,用于明确需要凭据的操作
  • ~/.config/run402/wallet.json(0600):用于 x402 / MPP 签名的钱包

旧版 projects.json 文件仅作为单向迁移输入。anon_key 和 service_key 没有过期时间;租约强制在服务端执行。使用 run402 credentials project-keys status --project <project_id> 检查缓存状态,并且仅使用 run402 credentials project-keys export --project <project_id> --reveal 导出密钥。

开发

npm run build           # builds core/, sdk/, then the MCP server
npm test                # SKILL + sync + unit tests
npm run test:e2e        # builds generated CLI SDK mirrors, then runs CLI end-to-end tests
npm run test:sync       # checks MCP/CLI/OpenClaw/SDK stay in sync
npm run test:skill      # validates SKILL.md frontmatter + body

架构:每个工具 / 子命令 / 技能脚本都是对 @run402/sdk 调用的薄封装。core/ 存放仅限 Node 的文件系统原语(keystore、wallet、SIWE 签名),由 SDK 的 Node provider 包装。完整布局参见 CLAUDE.md。

链接

许可证

本仓库采用 MIT 许可证(Agent 相关接口:SDK、命令行、MCP 服务、Astro 集成、OpenClaw 技能)。完整后端 run402-core 采用 Apache-2.0 许可证。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。