快速上手:Agent 数据面——接入你的后端(中文版)
English version: Quickstart: Agent Data Plane。 两份内容同步维护;以英文版为准绳,中文版跟随更新。
这是让 Pouchy Agent 实时、安全地访问你的系统的端到端路径:读你的权威
状态(View)、经用户同意改变它(Action)、在它变化时被通知
(Event)。示例用电商后端——Order 视图、refund_order 动作、
order.shipped 事件——但没有任何领域限定:同样七步适用于游戏服务器、
门诊排班、物流跟踪或 IoT 集群。
兼容任何数据库——或者没有数据库。 集成边界是纯 HTTPS:你托管两个
Pouchy 会调用的端点(View 走 GET、Action 走 POST),事件由你 POST
给 Pouchy。端点背后是什么——Postgres、Mongo、Redis、SQL Server、一张
Excel、另一个 API——平台在结构上看不见。没有 connector、没有驱动、没有
查询语言:Agent 只按名字选用你声明过的能力,从不自己写查询。
前置:一个 Pouchy 项目(dashboard 权限)、一个你可控的 HTTPS 域名下可达的
后端(即 audience)、用 kit 的话 Node ≥18。完整线协议:
API Reference(§Data capabilities)。可运行
的接收端在仓库 examples/data-plane-receiver/。
系统在内网(ERP/内部数据库)? Pouchy 从不向内连——你把一层先验签的 薄 shim 放在 Pouchy 够得着的地方(云/DMZ 主机、反向隧道、或你 API 网关的 公网边缘),由它经你的私有网络与内部系统通信;数据库凭据一步不出内网。 部署形态与安全姿势见 Integration FAQ §9。
0)心智模型,60 秒
- 你声明能力(dashboard → 数据能力,或用 Admin API /
@pouchy_ai/admin-sdk无头操作)。已发布的修订不可变——新内容铸下 一个版本,运行中的会话继续用各自钉定的修订。发布从来不是撤销;每 Agent 的data开关和每能力的停用位才是实时闸门。 - Pouchy 对发给你的每个请求签名(POUCHY-ACTOR-V1)。先验签、后信任 已验证的主体——顺序绝不能反。
- Action 上,你的 HTTP 应答就是结果声明:2xx = committed,
4xx = rejected,5xx/超时 =
unknown(可能已生效;只能靠对账解决, 永不盲目重试)。
1)先铸签名密钥——先于其它一切
Dashboard → 数据能力 → 签名卡片 → 签发(或
POST /v1/projects/{id}/capabilities/signing {"action":"provision"})。
立刻记下 pcsk_… 明文——只显示这一次。
顺序有讲究。 若尚无密钥,平台会在第一次能力调用(包括
test-read) 时自动铸一对——而自动铸出的明文任何地方都不会展示,之后再签发只会得到409。真踩了这个坑:rotate(拿到一次新明文)→ 切换你的接收端 →retire_previous。
2)声明并发布三种能力
Dashboard → 数据能力 → 发布,或无头(pchy_admin_… 密钥):
import { createAdminClient } from '@pouchy_ai/admin-sdk'; // ≥0.12.0 — typed declarations
const admin = createAdminClient({ adminKey: process.env.POUCHY_ADMIN_KEY! });
await admin.publishCapability({
kind: 'view', name: 'Order',
description: 'One customer order: status, total, delivery ETA.',
audience: 'api.shop.example.com',
endpoint: 'https://api.shop.example.com/pouchy/order',
fields: ['orderId', 'status', 'total', 'eta'],
filters: ['orderId']
});
await admin.publishCapability({
kind: 'action', name: 'refund_order',
description: 'Refund one order to the original payment method.',
sensitivity: 'personal',
audience: 'api.shop.example.com',
endpoint: 'https://api.shop.example.com/pouchy/refund',
args: ['orderId', 'reason'],
idempotency: 'action_id',
reconcile: { endpoint: 'https://api.shop.example.com/pouchy/refund-status' }
});
await admin.publishCapability({
kind: 'event', name: 'order.shipped',
description: 'An order left the warehouse.',
source: 'shop-backend',
schemaVersion: 1,
subjectField: 'customerId'
});
名字是 Agent 的词汇(Order,不是 tbl_order_v2)——模型用它们做
计划。fields 是输出白名单:你端点多返回的任何字段都在到达 Agent 前被
裁掉。
3)实现接收端(线协议交给 backend-kit)
npm install @pouchy_ai/backend-kit # ≥0.3.1 — Node ≥18, zero deps, typed
import {
verifyPouchyRequest, idempotentAction, reconcileFrom,
ActionRejected, verifyWebhookSignature
} from '@pouchy_ai/backend-kit';
const SECRETS = { [process.env.POUCHY_KID]: process.env.POUCHY_SIGNING_SECRET };
const AUDIENCE = 'api.shop.example.com';
// Every route: verify the EXACT raw bytes first, trust claims only after.
function verified(req, rawBody) {
return verifyPouchyRequest(
{ method: req.method, url: req.url, headers: req.headers, body: rawBody },
{ secrets: SECRETS, audience: AUDIENCE }
);
}
// GET /pouchy/order — the View. Filters arrive as query params (signed).
// → 200 flat JSON; Pouchy curates to the declared fields.
// POST /pouchy/refund — the Action. Body: {"actionId","action","args"}.
const refund = idempotentAction(store, async (args, ctx) => {
const order = await db.orders.get(args.orderId);
if (!order) throw new ActionRejected({ error: 'no such order' }, 422);
if (order.status !== 'paid') throw new ActionRejected({ error: 'not refundable' }, 409);
return await payments.refund(order, args.reason); // your durable receipt
});
// GET /pouchy/refund-status?actionId=… — reconciliation, three-state,
// answered from the SAME store (never by re-reading business state).
const refundStatus = reconcileFrom(store);
// POST /webhooks/pouchy — platform webhooks (agent.event_reply etc.):
const w = verifyWebhookSignature({
body: rawBody, header: req.headers['x-pouchy-signature'],
secret: process.env.POUCHY_WEBHOOK_SECRET });
三条 kit 替你守不住的规矩:
- 原始字节。 每个签名都覆盖线上的精确字节——这些路由要挂在 raw body 上,不能是解析再序列化过的。
- 拒绝用
ActionRejected(持久化、可重放的 4xx =rejected)。意外 崩溃就让它抛——500 会诚实地归类为unknown。 store是你的——满足{ get(actionId), put(actionId, row) }的任何 存储都行,SQL 表到 Map 皆可。
不用 Node? 线协议语言中立,随包发布的 vectors 让任何移植都能自证。
仓库 examples/csharp-receiver/ 有零依赖的 C#/.NET 参考实现
(POUCHY-ACTOR-V1 + webhook 验签 + 来源签名 + intent hash,内置向量自测——
dotnet run 全绿再上线)。
4)先无头自证——在任何 Agent 开口之前
await admin.testReadCapability('Order', { externalUserId: 'cust_42', filters: { orderId: 'o_1001' } });
// → the curated rows exactly as an agent would see them, plus the pinned revision
await admin.testActionCapability('refund_order', {
confirmDuplicates: true, // phases B/C intentionally re-deliver — real side effects
args: { orderId: 'o_test', reason: 'integration test' }
});
// → four phases: connectivity, duplicate same-intent, intent mismatch, reconcile.
// Protocol verdicts are reported SEPARATELY from outcomes — "Protocol: PASS,
// outcome: unknown" is a valid result, resolved by phase D.
const { executions } = await admin.listActionExecutions({ limit: 10 }); // read back what ran
用一张草稿订单——action 测试是真实投递。
5)在 Agent 上开启
Dashboard → Agents → 你的 agent → 数据 tab,或无头:
await admin.updateAgent(agentId, {
data: { enabled: true, actions: { enabled: true }, events: { enabled: true } }
});
enabled 给实例 read_data + 钉定的能力菜单;actions.enabled 加上
确认门控的 run_action;events.enabled 允许你的后端唤醒这个 agent。
每个开关关向都是实时的——关掉一个,运行中的会话下一轮就失去那个平面。
6)发事件
import { PouchyClient } from '@pouchy_ai/backend-kit';
const pouchy = new PouchyClient({ projectId, token: process.env.POUCHY_SECRET_KEY });
await pouchy.emitEvent({
agentId, // omit → subscription fan-out
name: 'order.shipped',
eventId: `shipped:${orderId}`, // stable domain fact — Pouchy dedupes on it
data: { customerId, orderId }
});
事件是信号,不是载荷运输:data 被挖出声明的 subjectField 后即被
丢弃——Agent 只被告知"order.shipped 发生了",当前事实仍从你的 View 读。
没有订阅 agent.event_reply webhook(第 3 步)时,已接受的事件顺延到
用户下次会话,而不是唤醒一次——要主动触达就把 webhook 订上。要证明事件
确实来自声明的来源,发布时加 signing: 'required' 并签发按来源的 pesk_
密钥(kit 传 source + sourceKeys 即自动签名)。
7)上线检查单
- 签名密钥最先签发并妥存(第 1 步)——轮换预案记录在案
-
test-read返回裁剪后的行;test-action四相全绿(第 4 步) - 拒绝一律
ActionRejected/ 干净 4xx——绝不用兜底 200 或 500 - 对账端点按 actionId 从幂等存储作答
-
agent.event_replywebhook 已订阅并验签(事件主动触达) - Agent
data开关就位;自动化(autoRun)只在真需要时开 - 知道两个 ground-truth 读口:Action 执行日志与事件回执
(dashboard → 数据能力,或
listActionExecutions/listEventReceipts——同样的行,admin-key 镜像可读)
更深入:API Reference — Data capabilities (精确的规范化字符串、拒绝语义、自动化双钥、来源签名向量)、 Capabilities Map(数据面在其它能力平面中的 位置)。