关于 amanat
Amanat 将天气风险挖掘器、基于测量值的评分模块和链上应用结合起来。其 MCP 服务供客户端查询天气与风暴风险,并提供可独立核验的 Ed25519 签名证明。
主要功能
- 01获取天气、海况和气旋数据
- 02综合多项条件计算风暴风险
- 03同时返回文本与合约可用数值
- 04使用 Ed25519 签名响应
- 05通过 WASM 模块评分数值测量结果
适用场景
- 01查询指定地理位置的风暴风险
- 02向智能合约任务提供天气读数
- 03使用评分器比较天气测量答案
MCP 相关
amanat-mcp 向 MCP 客户端提供 Amanat 的天气与风暴风险能力。响应包含可独立核验的签名证明。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
4收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| hours | integer | 否 | 提前小时数,范围为 0 到 168。省略时从问题中读取;指定 0 表示当前。 |
| place | string | 是 | 地点名称、包含地点名称的问题,或 "lat,lon"。 |
详细介绍
Amanat
可由合约自动执行的签名天气情报。
签名的,而非“已验证”:网络自身的 verified: true 无法从外部检查(错误报告),因此这里的每个答复都带有对合约结算所依据字段进行签名的 Ed25519 签名,任何人只需使用 Node 即可验证:
node -e 'const c=require("node:crypto");fetch("https://amanat-miner.vercel.app/forecast?lat=10.32&lon=123.89&hours=6").then(r=>r.json()).then(({attestation:a})=>console.log(c.verify(null,Buffer.from(a.canonical),c.createPublicKey({key:Buffer.from(a.public_key,"base64"),format:"der",type:"spki"}),Buffer.from(a.signature,"base64"))))'
验证所用的密钥发布于
/.well-known/amanat.json。
amanat 是一条受托传递的消息——用旧时电报局的语言来说,就是电报本身。这就是本项目的整体构想:矿工发送 amanat,评分模块对其进行测试,合约则无需任何人手动决策即可执行。
为 Telegraph Hackathon Season I 打造。一个代码库,三个参赛项目:
在线体验: amanat-miner.vercel.app — 查询任意地点的风暴风险,无需钱包,无需注册。这与合约在花费任何资产前所进行的调用相同。
一行命令,适用于任何 MCP 客户端: npx -y amanat-mcp — 已发布到 npm,并列入官方 MCP 注册表,条目为 io.github.PugarHuda/amanat。
三分钟了解: 演示文稿 ·
影片(84 秒,剪辑自实时会话——没有模型图) ·
/api/jobable,用于统计有多少意图已关联链上任务、有多少尚未关联,以及有多少完全无法审计,因为其领导者将注册信息发布在
127.0.0.1。添加 ?intent=STORM_ALERT,即可查看针对你自己的判定结果。
| 赛道 | 内容 | 位置 |
|---|---|---|
| 1 — 矿工 | 天气与风暴风险矿工,其答复对文本评分器和智能合约都清晰易读 | miner/ |
| 2 — 脚本作者 | 基于测量数据、适用于 Tier A 意图的 WASM 评分模块 | scorer/ |
| 3 — 应用 | 通过 ERC-8183 链上任务结算的参数化保障、CLI 和 MCP 服务 | onchain/、agent/、app/、mcp/ |
围绕构建的三个问题
这里的一切都来自对实时网络的测量,而非猜测。下方数据可通过此仓库中的脚本复现。
1. 确定性意图被当作散文来评分。 在第 240 轮中,WEATHER_CHECK 的排名第 1 的矿工得分为 0.0206,STORM_ALERT 的得分为 0.0067。矿工并不差;网络上的每个评分模块都在比较文本,而这些矿工给出的却是数字。基于这些分数构建的排行榜毫无意义——而路由正是依据排行榜进行的。
2. 链上通道确实存在,却无人使用。 Diamond 对 getJobBasePrice() 的响应是 1000000;截至 8 月 21 日,其整个生命周期内有 139 个矿工注册,却只有 6 个 ERC-8183 任务——最后两个由同一名参与者在 8 月 17 日创建。在 77 小时的时间窗口内,出现了 44 个 MinerRegistered 事件和 2 个 JobCreated 事件。与此同时,组织者称链上智能流水线是最有价值的构建方向。
8 月 31 日再看:341 个矿工注册,但任务仍只有 14 个。 其中 8 个是我们创建的。5 个已结算;最后 3 个没有进展。
3. 几乎没有矿工能接收任务。 任务会向节点传递原始的
OnChainData 数组;如果 YAML 中没有 on_chain.request 块,节点就无法将其转换为 HTTP 调用。npm run audit 会获取每份已注册的 YAML 并进行检查。8 月 31 日:125 个在线矿工中,只有 29 个声明了 on_chain 块;名称可哈希的意图只有 1 到 3 个可处理任务的矿工——STORM_ALERT 恰好只有一个,就是本项目。这个瓶颈正是问题 2 的根源,而且情况只是有所缓解,并未解决:8 月 21 日有 63 个在线矿工,每个名称可哈希的意图都只有一个可处理任务的矿工。
网络的实际情况(读取自 /api/epochs、/api/validators 和 Diamond,数据截至 8 月 21 日):测试网的轮次每小时运行一次,而非文档所述的 24 小时。每轮会对 17 个意图中的 70 个结果以及66 个已注册矿工中的 29 个进行评分——超过半数的矿工从未获得评分。只有一个活跃验证器,即 telegraph-node-1,因此 43/64 的 BFT 阈值是主网的属性,并非当前正在运行的机制。
网络的格局维持不变,规模却变了。8 月 30 日的第 294 轮中,网络对 42 个意图的 242 个结果以及全部 125 个在线矿工进行了评分;轮次也不再按小时运行——最近五轮的间隔为 3 到 9 小时。验证器仍然只有一个。
赛道 1 — 矿工
miner/server.mjs 会读取 3 个免费、无需密钥的数据源,并同时以两种形式返回所有答案。Open-Meteo 的天气模型提供空气数据;其海洋模型提供有效波高——这才是真正会阻碍船只航行的数据,也是航线看板缺少的数据;GDACS 则提供地球上所有正在活动的具名热带气旋及其位置和最大风速,因此,热带风暴 Dolly 影响下的读数会直接点名,而不是只报告“38 km/h 的风”。风速、阵风、降雨、海浪和气旋距离中风险最高的一项决定风暴风险——海浪高达 4 m,或台风正好在头顶,都足以触及风险上限。
{
"summary": "Weather forecast for -6.20, 106.85: the temperature is 27.8 °C (82 °F) and it feels like 32.3 °C, humidity 73%, Clear sky, cloud cover 0%, wind 4.4 km/h (1.2 m/s) from the south-east, gusts 11.9 km/h, precipitation 0.0 mm (2% chance of rain), valid at 2026-08-30T17:00Z. … Storm risk is low (0.132); across 51 ensemble runs it ranges 0.08 to 0.14, 0% of them over the trigger.",
"temp_c": 27.8, "wind_kmh": 4.4, "gust_kmh": 11.9, "precip_mm": 0,
"wave_cm": 0, "cyclone_name": null, "cyclone_km": 0,
"risk": 0.132, "breach": false, "valid_at": "2026-08-30T17:00Z", "source": "open-meteo",
"risk_band": { "model": "ecmwf_ifs025", "members": 51, "p10": 0.076, "p50": 0.104, "p90": 0.14, "max": 0.164, "breach_probability": 0 },
"attestation": { "algorithm": "ed25519", "sha256": "9e991dc5…", "signature": "B3dtTlG1…", "public_key": "MCowBQYDK2VwAyEAJf1zypC6…", "key_persistent": true }
}
简而言之:实时响应包含 36 个字段。其余字段供报告使用,合约会忽略——湿度、露点、风向、云量、降雨概率、未来两天的最高温和最低温、海平面、具名气旋及其距离——另外还有带签名的 canonical 载荷和 Open-Meteo 的归属信息。运行 curl -s -X POST https://amanat-miner.vercel.app/forecast -H 'content-type: application/json' -d '{"lat":-6.2,"lon":106.85,"hours":0}' 即可查看完整内容。
文本比较评分器可以评估这段文字。Amanat.sol 则会使用标量数据,这些数据通过 amanat-miner.yaml 中的 on_chain.fields 映射。网络目前存在这样的缺口,原因就在于只提供了其中一种形式。
YAML 还包含完整的 on_chain.request 区块,这是矿工能够被任务访问的关键;它还声明了 Open-Meteo 的实际配额,因此节点会在向调用者收费之前拒绝会耗尽配额的请求。
npm run miner # http://127.0.0.1:8787
node miner/test.mjs # self-check, hits the real upstream
托管
矿工没有依赖项,因此镜像只包含运行时和一个文件。
docker build -t amanat-miner miner/ && docker run -p 8080:8080 amanat-miner
fly deploy -c miner/fly.toml # or
cd miner && vercel deploy --prod # serverless, via miner/api/*
无论选择哪种方式,主机都必须在 registerMiner 之前上线:节点会针对真实上游对所有已声明的端点进行沙箱测试;如果注册所用的 YAML 未通过验证,注册将被直接拒绝,不会重试。
第 2 轨——评分模块
scorer/ 是一个 no_std Rust 模块,编译为 wasm32-unknown-unknown:
15.5 KB,零导入,导出 alloc、dealloc、rank_answer 和
breakdown_answer。
它会提取答案中的量值,并将其作为测量值评分:
38.2 °C、100.8 F 和 311.35 K 是同一读数;10 m/s 和 36 km/h 是
同一风速;偏差 0.3° 是正确的,偏差 30° 则是错误的。文本重叠仍然会参与评分,但只用于承载答案中的非数值部分。
两条规则基本解决了所有投机问题:
- 问题中已经给出的数字,重复回答时不得分。 对“会超过 40 °C 吗?”回答“40 °C”,这只是复述提示词,而非测量读数—— 除非标准答案也写了 40。
- 明确作答优于罗列选项。 列出所有候选值会被扣分,因此含糊其辞的答案无法胜过明确的判断。
所有浮点运算都只有 + - * / 和比较,IEEE-754 对这些运算有明确规定,因此不同主机上的两个验证器会返回完全相同的位。
npm run build:scorer
npm run bench # against the real champion binaries
npm run attacks
cd scorer && cargo test # 27 tests, native
在 scorer/bench.json 上进行测量(涵盖 31 个意图的 38 个正反案例,以及 14 种攻击),对比从各自公布的 wasm_url
下载的冠军二进制文件;于 8 月 31 日重新运行:
| 模块 | 差值 | 胜场 | 最差自匹配 | 标准差 |
|---|---|---|---|---|
| game_result reg 1265 — 当前冠军 | 0.6524 | 37/38 | 1.0000 | 0.4525 |
| amanat_scorer | 0.5977 | 37/38 | 1.0000 | 0.4343 |
| urlscan reg 28 — 8 月 23 日前的冠军 | 0.5015 | 34/38 | 1.0000 | 0.3320 |
| weathercheck reg 134 — 已被取代 | 0.4688 | 34/38 | 1.0000 | 0.3228 |
| weather_forecast reg 636 — 当前冠军 | 0.4438 | 33/38 | 1.0000 | 0.4277 |
| financial reg 122 — 已被取代 | 0.3886 | 29/38 | 1.0000 | 0.4191 |
| weather_check reg 510 — 当前冠军 | 0.3827 | 33/38 | 0.9952 | 0.3900 |
| storm_alert reg 453 — 当前冠军 | 0.3071 | 35/38 | 0.9830 | 0.3586 |
| text_auth reg 1882 — 当前冠军 | 0.2895 | 29/38 | 1.0000 | 0.4472 |
第 2 阶段要求两项指标都达标——差值和排序胜场数至少与冠军持平——因此胜场数和差值同样重要。
先看最上面一行:注册号 1265 的模块,也就是在四十分钟后从我们手中夺回 GAME_RESULT 名额的模块,在这组语料上胜过了本模块。它是个不错的模块。占据天气意图位置的三个模块则不然,而这正是本项目要解决的问题。
要复现此表,有一步操作无法由仓库代劳。
scorer/champions/*.wasm 被 gitignore 忽略——它们是其他人编译的二进制文件,其中一些有 25 MB——所以全新克隆的仓库中没有可供比较的文件,npm run bench 只会报告我们自己的结果。请先从每个注册条目公布的 wasm_url 下载文件,地址为 https://devnode.telegraphprotocol.com/api/wasm,然后将其放入 scorer/champions/。npm run attacks 会读取
scorer/target/…/amanat_scorer.wasm,因此需要先运行 npm run build:scorer。
还要客观看待其余结果:这组语料是我们自己构建的,它说明此方法在我们能看到的案例上有效,但并不意味着它能在协议自己的隐藏测试中胜出。
将语料从 20 个案例扩充到 38 个,才发现了真正的错误。它揭示了“812.4 million”被解析为 812.4,而“812.4M”却能正确解析;正确的改述因省略一个数字,得分竟低于引用了该数字的错误答案;在 AI 检测问题中,“not a human”竟被当作否定判断。每个问题都源于规则错误,而不是缺少特殊处理。
无人提及的门槛
在承载流量的意图上,超过冠军的分数优势还不够:节点还会检查你的模块对真实矿工回答的排序是否大致与冠军一致,低于约 0.60 就会拒绝。因此,一个分数优势为 0.68 的模块在 WEB_SEARCH 上被拒绝,而一个分数优势为 0.388 的模块却上线了。
node scorer/harness.mjs --agreement <ours.wasm> <champion.wasm>
node scorer/harness.mjs --diff <ours.wasm> <champion.wasm> # where we lose
在对阵当时卫冕的 URL_SCAN 冠军(注册号 28)时,我们的平均排序一致率为 0.92;这是 8 月 21 日测得的,而分歧最大的案例是 WEATHER_CHECK 和 WEATHER_FORECAST——恰恰是我们有意做出不同判断的地方。该二进制文件于 8 月 23 日被取代;我们尚未针对目前入驻的模块重新测量这一数值。
反作弊:14/14 次攻击均被抵御。 最后一次被击破的攻击需要第三种信号——判断词。否定词会翻转其所在分句中的下一个判断词,因此“no malicious behaviour”会被读作正面,而“No, it is a phishing page”本身就是判断,因为分句边界会结束否定词的作用范围。无论答案包含多少正确词汇,只要它给出的判断与事实真相相反,就只能保留 15% 的分数。这堵住了关键词灌水的漏洞(对诚实答案的分数为 0.53 时,关键词灌水答案从 0.84 降至 0.13),同时也提高了基准测试的分数优势,这体现的是真实信号,而非临时修补。由此还得出第二条规则:同时断言两个对立面——“valid ... however it is invalid”——属于含糊其辞,而非作答,因此会受到惩罚。
注册号 28 的二进制文件在 8 月 23 日之前一直是冠军,却暴露了本模块旨在捕捉的案例——wrong dimension, same number:它给“12 °C”打出 0.80 分,而诚实回答“12 millimetres”只有 0.66 分。
Track 3 — 应用
onchain/src/Amanat.sol 是一种参数化天气保障合约,其中
合约是智能服务的使用方,而不是由前端调用
API:
openPolicy() → requestCheck() → createJob(keccak256("STORM_ALERT"), params, this)
↓ protocol routes to the best-ranked miner
↓ validators finalise
subnetMessage() → pay the holder, or decline
有两个设计约束决定了这一方案,而且都没有变通办法:
- 答案来自一个我们谁都没有选择的矿工。
OnChainData按照该矿工的 YAML 打包,因此合约会验证收到的数据,并拒绝无法解析其结构的理赔。它绝不会猜测赔付金额。 - 交付是异步的,也无法保证。 资金会针对保单保持托管状态;如果 24 小时后仍未收到答案,
expire()会释放资金,因此沉默的通道无法让整个账簿陷入僵局。
agent/ 是为其提供数据的循环,优先使用成本最低的通道——守护进程数据源免费,Engine 调用费用为 $0.01,job 费用为 $1.00,因此只有廉价通道判定某份保单值得结算后,才会将请求发送到链上。
npm run agent:dry # read-only: no wallet, no funds, no spend
npm run agent # opens jobs for policies that pass screening
同一矿工还有三个其他使用方,因此合约并不是唯一能根据读数采取行动的组件。
-
app/storm.mjs— 零依赖 CLI。storm "Cebu"读取风险;storm route "Cebu" "Manila"读取航程;--telegraph会请求网络而不是这个矿工,并显示哪个矿工作出了响应。 -
mcp/server.mjs— 一个 MCP 服务,已发布到 npm,并以io.github.PugarHuda/amanat列入官方注册表。提供四个工具,无需密钥,也无需钱包:{ "mcpServers": { "amanat": { "command": "npx", "args": ["-y", "amanat-mcp"] } } } -
风暴看板 — 每六小时筛查十条航运航线,发布到一个分支,并通过
/api/board提供服务。8 月 26 日至 9 月 6 日期间,定时运行通过 Telegraph Engine 购买读数,每次运行付费调用 30 到 86 次,由节点路由到其评分最高的矿工——通常不是这个矿工;看板会发布每次运行的调用统计。9 月 6 日后,它停止购买读数。 该协议的联合创始人在 Discord 上要求大家停止脚本化的自动调用,因为这些调用导致支付服务商宕机,并表示评审时不会计算脚本调用,只计算自然产生的调用。现在,定时运行会读取免费通道,也就是这个矿工自己的
/forecast,因此定时任务不会给节点增加负载,也不会产生购买。board.json中的rail会说明某次运行使用了哪个通道;未购买读数时,telegraph为null。在 Actions 选项卡中,只需点击一次并设置paid=true,仍可执行付费扫描。这不会影响合约:链上检查保单时,会通过 Engine 自行购买读数,因为这是真实义务所产生的真实请求,而不是定时任务虚增计数。
为什么我们只使用名称哈希意图
agent/run.mjs 刻意只针对名称哈希意图
(keccak256("STORM_ALERT")),让协议来选择矿工。使用基于注册信息生成的 intentId 会将 job 固定到我们自己的矿工,这正是组织者警告不要采用的利益输送循环。
项目布局
miner/ server.mjs, amanat-miner.yaml, test.mjs
scorer/ src/lib.rs, harness.mjs, bench.json, champions/
onchain/ Amanat.sol
agent/ telegraph.mjs, run.mjs, audit-jobable.mjs
scorer/harness.mjs 会像验证器一样加载任意 Telegraph 评分模块——不使用导入,而是通过模块自身的 alloc 写入字符串——并使用 Node 内置的 WebAssembly,因此与冠军模块比较无需额外工具链。
迄今的链上进展
闭环已完成。 Amanat.sol 已上线,地址为
0x4A5ECEBd…9893
——页面读取的正是这个地址,其中持有 3.00 USDC 的任务预算和两条开放策略。同一份源码此前也已在 Sourcify 上通过验证,地址为
0x0700c930…590c,
创建字节码和运行时字节码均完全匹配,因此链上运行的内容与本仓库所示一致。它无需任何人介入即可结算理赔:
openPolicy -> requestCheck -> createJob(keccak256("STORM_ALERT"))
-> protocol routes, validators finalise -> job Terminal
-> subnetMessage -> risk below the 0.75 trigger -> Declined
任务 7、8、9 和 10。在此之前,这条链在其整个生命周期中只见过 6 个 ERC-8183 任务。
三条轨道全部运行,先走成本最低的。 这个顺序是成本设计,并非描述:守护进程数据流免费,通过 x402 发起 Engine 调用需 $0.01,一个任务需 $1.00,因此 Agent 会先问 100 个低成本问题,再问一个高成本问题。它会筛查合约仍需履行的每条开放策略,只有低成本回答显示某条策略接近触发条件时,才会上链。一轮运行:42 次付费 Engine 调用,升级处理 1 次,费用 $1.42。
第二个任务揭示的问题
策略 2 的纬度设为 1,策略 3 的纬度设为 10.32。两者返回的结果都是风险值 0.382,预测坐标为 0.00, 0.00。合约正确存储了坐标,并将它们传入 strings[0..1];YAML 会从 strings.0 和 strings.1 中映射坐标。但这些坐标未能通过节点的 on_chain.request 映射。
在任务 9 中,这改变了处理结果。Engine 筛查对马尼拉给出的风险值为 0.488,高于升级处理阈值;为同一策略创建的任务返回 0.361,即“零岛”的对应值,合约因此拒绝了理赔。合约花 1 美元获取信号,依据的却是其他地点的数据。详见 docs/bug-report.md。
此前的部署。 0x1649ce04B8b9D56285a62Afb2b442602EE0bBc6e 是采用 SafeERC20 之前运行相同合约的部署;它的 11 条策略和 5 个已结算任务(7 到 11)仍可在 Base Sepolia 上读取。我们选择替换而非保留旧部署,因为测试只能证明本仓库中的代码,而部署运行的代码与受测代码不一致,正是本项目不断在他人系统中发现的那种漏洞。
两件必须花一笔交易费才能弄明白的事
createJob 使用调用者的托管余额——调用者是合约,而不是部署它的钱包。因此需要 fundEscrow() 和 jobBudget()。
公共 Base Sepolia RPC 会将过时的写入状态返回给自身。 一笔已确认的转账读出的余额为零,一笔已确认的 approve 在模拟时却报错 exceeds allowance;requestCheck 在 estimateGas 时回滚,但仅仅过一个命令后,直接模拟同一调用却返回了 jobId 7。现在 BASE_SEPOLIA_RPC 指向 publicnode。
Miner 和评分模块
Miner:8 月 24 日首次注册,编号为 179;当前注册编号为 280。
amanat-weather-risk,通过 https://amanat-miner.vercel.app 提供 WEATHER_FORECAST、WEATHER_CHECK 和
STORM_ALERT,底价为 0.01 USDC。每次
updateMiner 都会取代之前的编号,因此 179 和 256 现在显示为 deregistered,节点路由到的编号是 280。
这个结果来之不易,其中一次终端拒绝值得记录下来:每个
on_chain.fields 条目都必须包含 description。架构对此有强制要求,而文档中自带的直接转换示例却遗漏了它;因缺少该字段而遭拒的 Miner 不会重试——修复方式是 updateMiner,而不是重新调用 registerMiner。现在,agent/register-miner.mjs 中的预检会拒绝使用缺少该字段的 YAML 进行注册。
此前还遇到过三次 Vercel 部署失败,原因各不相同:部署根目录中的每个 .mjs 都会被视为函数入口点,因此辅助模块会导致部署崩溃;将其排除会彻底移除入口点,因为这是一个 Node 服务端项目,而 server.mjs 正是 Vercel 查找的文件;此外,Vercel 要求该文件的 default 导出必须是服务端。
评分模块:曾占据四个冠军名额,随后因协议变更而失去。
| 意图 | 注册编号 | 我们的优势 | 被超越者 |
|---|---|---|---|
CRYPTO_PRICE | 201 | 0.8449 | 0.7961 |
WEATHER_CHECK | 191 | 0.8445 | 0.7928 |
STORM_ALERT | 188 | 0.8355 | 0.7585 |
URL_SCAN | 202 | 0.8257 | 0.7892 |
8 月 23 日,协议发布了一项修复:每个模块都根据其自身注册的意图进行评估,而非使用一套共享的测试样例。随着所有人重新注册,整个网络的注册数量从 187 增至 574,我们的四个名额也全部被取代。现在,测试样例集明显按意图区分:WEATHER_CHECK 报告 12 个用例,WEATHER_FORECAST 报告 15 个,而过去所有意图都报告 32 个。
这是正确的改动,也使本仓库此前记录的发现失效了——当时的结论是,所有 45 个意图共用一套测试样例;依据是同一个二进制在不同意图上的得分精确到小数点后七位都相同。这个结论当时属实,但现在已经不再成立。
拒绝结果带来的启示
**186 在 STORM_ALERT 上以 0.017 分之差落败。**它在排序上与冠军打平,只在区分度上落败,因此修复方式不是改进判断,而是增强对比:重复应用 smoothstep。严格递增的曲线无法改变一对结果的排序,因此从原理上保证排序和排名一致性不变,同时拉大好结果与坏结果之间的差距。得分从 0.7415 提升至 0.8355。
**二进制文件会与注册它的地址绑定。**即使条目已被取代或拒绝,重新注册我们已用过的字节也会因 duplicate wasm hash 而回滚。新名额需要新的构建产物。
**203 因为判断正确而在 WEATHER_FORECAST 上被拒绝。**它在测试样例上的表现优于冠军——0.8349 对比 0.7859——却因对 91 个真实 Miner 答案的排序方式不同而遭拒:
disagreed with the champion on real traffic: agreement -0.2585, need at least 0.60
它与一名现任模块呈负相关;该模块对天气答案给出的分数接近零——在 epoch 240 中,WEATHER_CHECK 排名第一的答案得分为 0.0206。与噪声意见相左,就会与噪声呈负相关。WEATHER_CHECK 以 0.6111 勉强超过 0.60 的门槛;同属一个领域的 WEATHER_FORECAST 面对的是不同的现任模块,因此未能通过。这个门槛奖励与当前在位模块保持一致,而当在位模块较弱时,这种做法恰恰是本末倒置。
配置
一种方案:按意图类型分别调优,并为每个二进制文件编译——模块会接收三个字符串,却不会被告知正在评估哪种意图,因此领域知识必须写入构建中。
| 配置 | 差异原因 | 差值 | 胜场 |
|---|---|---|---|
finance | 价格或持有人数量必须精确:误差 0.2% 可得满分,偏差 15% 就是错误数字 | 0.6008 | 37/38 |
weather | 当前读数:39 °C 并不是 38.2 °C 的近似值 | 0.5988 | 37/38 |
| (默认) | 通用 | 0.5977 | 37/38 |
forecast | 预测本就存在合理的不确定性:提前三小时预测偏差 2 °C 仍是不错的预报 | 0.5828 | 37/38 |
verdict | 答案本身就是结论,因此矛盾答案会扣 95%,数字的影响较小 | 0.5782 | 37/38 |
prose | 无可衡量之处;答案体现在措辞中 | 0.5512 | 37/38 |
authenticity | 对完全不含数字的文本作出判定 | 0.4950 | 38/38 |
authenticity2 | 相同配置,但少一轮对比处理——针对排序门槛,而非区分门槛 | 0.4782 | 38/38 |
八种配置中有六种取得 38 场排序胜利中的 37 场,另外两种 authenticity 构建则赢下全部 38 场;每一种的最差自匹配分数均为 1.0,且 14 次攻击全部拦截。相比之下,在天气意图上表现最好的三个版本,在同一语料库上的得分分别为 0.4438、0.3827 和 0.3071,胜场分别为 33、33 和 35。
对比技巧有上限
重复应用严格递增曲线会拉大好答案与坏答案之间的差距,却不会改变它们的排序——这正是注册版本 186 晋级为冠军版本 188 的原因。随后,注册版本 676 将全部 15 个 WEATHER_FORECAST 测试用例排对了,却只在区分度上失利,得分为 0.4625,对比目标 0.5955;这看起来像是在邀请我们继续加大同一机制的力度。
并非如此。应用五轮后,语料库的排序胜场从 37 降至 34,而且开始漏过攻击;四轮时,胜场为 35,仍会漏过攻击。单调性在算术上保序,却无法保证浮点数保序:重复应用会使数值趋近 0 和 1,原本可区分的答案会变得相等。填充攻击之所以漏过,是因为它与诚实答案恰好都达到 1.0000。
三轮就是这里的上限,这也淘汰了一种配置。meteo 的设计目的是让它在较小的测试用例集上比 forecast 更温和;温和会减少排序胜场,更激进则会趋于饱和,而应用三轮后,它编译出的字节与 forecast 完全相同——无论如何,注册表都不会接受这种情况。原先的理由不成立,因此删除了该配置,而不是保留并为其辩解。
其余七种配置通过三种信号达到了这个结果;每种信号都是针对某个具体失败案例添加的:
- 顺序。 “先存款,再打电话”和“先打电话,再存款”包含相同的内容词,却表达相反的意思。词汇得分的四分之一取决于答案保留了多少真值文本的词序——刻意只占四分之一,因为改述可能会合理地调整顺序,也因为对比曲线会应用三次,所以曲线处理前的一处凹陷会变成一个深坑。
- 以第一个判定为准。 “不,这段文字转述了来源,并且正确引用了它”是一个带有正面细节的否定判定。直接将两者相加,会让它恰好呈现中性,因此矛盾答案就能完全通过检查。
- 错误数字的代价与错误判定相同。
prose和authenticity会有意降低数字的权重,因此真值为 10.0 时,它们会放过“CVSS 为 2.0”——因为权重较小,而不是证据不足。权重决定正确数字值多少分;它不能决定错误数字要付出多少代价。
已知的失败案例仍保留在语料库中,并未删除:默认配置仍会在 AGENT_TASK 排序案例中失利。冠军版本也会输掉这一项。
npm run build:profiles # builds all eight, fails if any two produce identical bytes
该区块已解除限制。registerMiner 曾一度受阻,因为 Vercel Authentication 后面的 base_url 返回了 302,而 agent/register-miner.mjs 在收到 200 之前拒绝消耗注册次数;部署保护已关闭,自 8 月 24 日起,/health 一直返回 200。
状态
截至 9 月 7 日,从链上和节点读取;以下每项数据都可通过给出的地址核验。
Track 1 — 矿工。 注册号 280,amanat-weather-risk,id 20260821,在 WEATHER_FORECAST、WEATHER_CHECK 和 STORM_ALERT 上活跃,服务部署于
https://amanat-miner.vercel.app。**截至 9 月 7 日已处理 822 个请求,在 132 个已注册矿工中排名第 6**——但有四个矿工的请求数相差不超过 24 个(846、834、827、我们的 822),所以即使我们的流量不变,排名也会在一夜之间变动。DegenLens 以 3,049 个请求领先。第 311 轮:
WEATHER_CHECK 得分 7/11,为 0.014653;WEATHER_FORECAST 得分 6/14,为 0.000424;STORM_ALERT 得分 3/7,为 0.015969。归一化后分别为 0.948、0.000 和 0.943——总分为 1.891,在 130 个矿工中排名第 12;平均分为 0.630,在处理三个或更多意图的矿工中排名第 4/21。零分才是关键:livecert 在 WEATHER_FORECAST 上以整整 1.0000 的分数通过,而该意图中的其他人都在 0.0004 的噪声区间内,这说明有一个矿工找到了其他人都没能回答的真实答案。这里的每项数据都可通过 node agent/standing.mjs 重现,这也是它们会变动的原因。
请把 WEATHER_CHECK 和 STORM_ALERT 的两种天气检查排名视为噪声,而非实际排名:这两个意图上没有矿工通过评分区间,因此即使代码完全相同,排名也会随轮次变动。WEATHER_FORECAST 已不再如此,这正是 9 月 6 日发生变化的地方。
最初,第 285 轮:STORM_ALERT 得分 3/4,WEATHER_FORECAST 得分 9/11。此后,每个天气意图上的参与者数量大约增加了两倍,排名也随之变化。
排名上限是算术决定的,值得直说。 每个意图的归一化分数最高为 1.0,因此按总分计算,处理三个意图的矿工最高只能达到 3.0。8 月 31 日,第三名需要 3.863,而榜首在十四个意图上拿到 9.048。因此,按这种计算方式,处理三个意图的矿工无论得分多高,都不可能进入前三——能提高总分的唯一办法是增加意图数量,而 45 个意图中并没有第四个天气矿工能够诚实作答的意图。按平均分计算,同样的三个意图若都达到 1.000,则可并列第一。适用哪种计算方式,npm run standing 会打印出来,却不会给出答案。
这些排名是真实的,但表现并不好;查明原因后,我们在错误报告中发现了第一个问题。在本地运行真正的 champion 二进制文件——使用 scorer/harness.mjs --case,方式与验证器运行它相同——我们的答案在天气真实答案下得分 0.9934,而在真实答案就是问题本身时得分 0.0086。当时每个天气矿工的得分都处于 0.0050 到 0.0089 的区间。固定真实答案为问题本身、只改变答案时,一句完全不包含任何信息的话竟能得 1.0000。
因此,现在摘要会先复述实际收到的问题,再作答——这本来也是谨慎作答的方式。固定模板还不够:对于措辞恰好与模板相符的问题,它得分 0.9943;对于措辞不同的问题,得分只有 0.0117。复述问题内容的做法适用于试过的所有措辞,也适用于全部三个天气冠军:
| 意图 | 冠军 | 得分 |
|---|---|---|
WEATHER_FORECAST | reg 636 | 0.9945 - 0.9964 |
STORM_ALERT | reg 453 | 0.9978 |
WEATHER_CHECK | reg 510 | 0.9989 |
| 这些是以问题为标准答案、针对冠军二进制文件得出的本地测量结果。若以 天气报告 为标准答案——也就是矿工排名第一的那种答案形式——同一个二进制文件给所有真实的天气答案打出的分数都是 0.008 到 0.016,包括我们的答案:这就是当时的区间,现重新列出。 | ||
| 因此,摘要现在包含天气报告会提供的信息:湿度、体感温度、风向、降雨概率,以及以两种单位表示的每日最高和最低温度;每个数值都与 Open-Meteo 同一时刻的数据一致。网络对第一项更改的实际评定出现在第 286 轮: | ||
STORM_ALERT 0.005034 → 0.007864,排名 3 → 2;WEATHER_FORECAST | ||
0.005182 → 0.005971,排名 9 → 8;WEATHER_CHECK 首次获得评分 0.014812, | ||
| 排名 5。结果真实可信,却比本地测试预测的低一个数量级——因此节点评分时依据的并非单纯的问题,错误报告也如实记录了这一点,没有悄悄掩盖。没有增删任何数值,合约确定的每个标量都保持不变。这两部分均已写入 | ||
| ["重述问题的答案得分为 1.0000;正确答案得分为 | ||
| 0.0086"](docs/bug-report.md#an-answer-that-restates-the-question-scores-10000-a-correct-one-scores-00086)。 |
随后,评分区间被打破了,但并非我们所为。 8 月 30 日的第 294 轮中,
isobar-weather 在 WEATHER_CHECK 上得分 0.9728,而该意图下的所有其他矿工——我们的得分为 0.014199,两家商业天气 API 的得分分别为 0.0157 和 0.0156——都仍处于旧区间内。有人已经找到了节点实际采用的标准答案,而它既不是单纯的问题,也不是这个矿工给出的天气报告。因此,评分上限确实存在且可以达到,但我们还没有达到;上面两段所述的是我们在 8 月 27 日了解到的情况,并非最终结论。
现在的答案除了读数还包含什么。 定价保险所需的三项内容,而网络自己的答案并不具备:
- 置信度。 在同一时刻,使用 ECMWF 的 51 个集合成员运行相同的风险评分:
risk_band提供 p10、p50、p90、最糟糕的运行结果,以及超过触发阈值的运行占比——也就是模型预测保险赔付的概率。句子中也会说明:“在 51 次集合运行中,数值范围为 0.32 至 0.43,其中 0% 超过触发阈值”。 - 是否会赔付。
GET /api/backtest?lat&lon&start&end会将实时阈值应用于再分析档案进行计算。台风 Rai,2021 年 12 月 16 日:Cebu 峰值为 1.000,阵风达 170 km/h,且有 13 个小时超过触发阈值;Surigao 为 1.000;Manila 为 0.568,Hong Kong 为 0.576,Singapore 为 0.418——保险只在风暴经过的地方赔付,其他地方均不赔付。页面上的“是否会赔付?”数据实时读取自档案。 - 信息来源。 每个答案都带有一个 Ed25519
attestation,用于验证合约确定的字段——规范化载荷、其 SHA-256、签名和公钥。验证只需 Node 的crypto.verify;公钥位于/.well-known/amanat.json。网络自己的signal_hash无法从外部重新推导("无法重新推导的信号承诺"); 但这个可以。
供调用前会先阅读的 Agent 使用:/openapi.json(OpenAPI 3.1,包含每个路由和架构)以及 /llms.txt。
赛道 2——评分模块。我们没有冠军席位。 我们于 8 月 27 日拿下了
GAME_RESULT 席位,并保持了约 40 分钟。 截至 8 月 31 日,我们的 23 个注册项中,有 5 个显示为 superseded,18 个显示为 rejected,没有一个处于激活状态。这才是诚实的头条;有意思的是事情是如何发展的。
注册 1253 以 0.7008 的分数在 0.4175 的门槛下激活,排序为 15/15,一致性为 0.6868。04:21,原作者试图夺回该位置,但在 0.4175 的门槛下被拒绝。04:40:17,他们在同一秒注册了两个模块,分数分别为 0.7089 和 0.7150,较高的一个占据了该位置,成为注册 1265,并一直保持至今。该作者在榜单上的份额从 45 个意图中的 44 个降至 43 个,又回到 44 个;到 8 月 31 日,随着其他作者注册,已降至 45 个中的 33 个。
这不是抱怨——在新冠军之上追加注册是合理的做法,而且他们的分数更高。这只是衡量新来者的席位能在一位密切关注的原有作者面前维持多久,答案是不到一小时。编写 npm run impact 正是为了监测这种情况,并在首次运行时就报告了它。
那天早上提交的五个模块才更能说明问题,因为只有一个是由模块本身决定结果的:
| 注册 | 意图 | 读取时的门槛 | 评估时的门槛 | 我们的分差 | 结果 |
|---|---|---|---|---|---|
| 1253 | GAME_RESULT | 0.5459 | 0.4175 | 0.7008 | 已激活,40 分钟后被取代 |
| 1250 | GAS_PRICE | 0.4851 | 0.4851 | 0.6446 | 被拒绝——一致性 0.1288 |
| 1251 | TVL_LOOKUP | 0.4989 | 0.5042 | 0.4885 | 被拒绝——区分度 |
| 1249 | ACADEMIC_SEARCH | 0.3344 | 0.5909 | 0.4707 | 被拒绝——区分度 |
| 1252 | IP_GEOLOCATION | 0.4935 | 0.9920 | 0.6677 | 被拒绝——区分度 |
IP_GEOLOCATION 超过了 API 半小时前报告的门槛,却没能达到实际评估所用的门槛;期间门槛翻了一倍。GAS_PRICE 的区分度超过了现有模块,排序与其持平,却因对真实答案的排序不同而遭拒——而该意图上表现最好的在线矿工,得分仅为 0.0054。
在 8 月 23 日评估器变更取代它们之前,已有四个席位被保留。八个配置文件中,六个在 38 次排序中赢了 37 次,另外两个 authenticity 构建在 38 次中全胜;14 次攻击也全部被抵御。npm run survey 会为目标排序,但无法预测评审结果,而且对此有明确说明:
已发布的 eval_score 是一个冻结数值,而 WEATHER_FORECAST 显示 0.5302,实际测得的却是 0.9898。
Track 3——应用。 这个网络上总共创建过 16 个 ERC-8183 任务。其中 10 个是我们的——任务 7 到 16,跨越三个合约。
任务 7–11 通过回调结算并到达 Terminal;12、13 和 14 则停留在 Funded 状态,再也没有推进。
8 月 31 日的任务 15 和 16 终于揭示了原因,而答案与这个仓库三周以来的假设截然不同。 两者都声明了 keccak256("STORM_ALERT")——这是 Diamond 上的规范意图——并携带纬度、经度和时间窗口。两者都收到了 TLS 证书矿工逐字节完全相同的答复:
error:invalid_domain / domain:<nil> / verdict:unknown
reason:No hostname was supplied with this request, so the TLS/SSL certificate
could not be analyzed. … Supply a domain such as example.com.
随后,任务 17 使用 WEATHER_FORECAST 重新检查了同一策略——这是另一个意图 ID,livecert 也为该意图提供服务,路径为 /weather-forecast——并收到了同样的证书错误。三个任务,两个意图,一个答复。
路由正确,但端点根本没有读取这些信息。 那段文本来自 livecert,而 livecert 已在 STORM_ALERT 上注册——这完全合理,它还注册了另外九个意图,包括 SSL_VERIFICATION。它为每个意图发布一个端点。任务声明了 STORM_ALERT,到达了提供该意图服务的矿工,随后却调用了 /ssl-check。
两部分各只需一条命令,而第二部分正是第一部分的代价:
curl -s https://miner-wine.vercel.app/ssl-check
# … "error":"invalid_domain" <- byte for byte what job 15 delivered
curl -s "https://miner-wine.vercel.app/storm-alert?location=14.60,120.98"
# … "risk_score":0.79, "max_wind_gust_kmh":71.3, "thunderstorm":true
0.79 高于该合约支付的 0.75 触发阈值。协议选定的矿工已经有答案,且该答案位于它专为这个意图发布的端点上;策略 1 本可以获得支付。 而且 livecert 根本没有声明 on_chain 区块——因此节点无从将纬度映射到对应端点,只能回退到它的第一个端点 /ssl-check,且不带任何参数。路由路径上没有任何环节会检查这一点,而 livecert 在 STORM_ALERT 上排名第 1。
Job 19 通过落到别处证明了这一点。 第五次尝试发往 txlens
——它也注册在 STORM_ALERT 下,也没有 on_chain 区块,有 15 个端点,
第一个是 /check-tx——结果返回:“由于未提供交易哈希,我无法查询这笔交易。”
两名矿工,两个不同的首选端点,两种不同的抱怨,一条规则。
现在,npm run audit 会衡量这能覆盖网络的多少部分。以下数据读取于
2026-09-06T20:07Z——这是一次采样,不是持续成立的声明,因为各意图所属的类别
会随读取而变化:
Intents whose rank-1 miner cannot receive an ERC-8183 job — confirmed:
FACT_CHECK rank 1 is livecert (12 endpoints, no on_chain.request)
STORM_ALERT rank 1 is livecert (12 endpoints, no on_chain.request)
WEATHER_CHECK rank 1 is chainsight-oracle (14 endpoints, no on_chain.request)
WEATHER_FORECAST rank 1 is livecert (12 endpoints, no on_chain.request)
Open — the rank-1 miner declares on_chain.request and can receive a job:
WEB_SEARCH rank 1 is telegraph-ai-miner-node
No leader in this read — the scoreboard returned no rank-1 row:
TASK_COMPLETION
4 confirmed closed, 9 unknown, 1 open, 1 with no leader, of 15
在这次读取中,五个能查到领头矿工的意图里,四个已覆盖,一个尚未覆盖——
WEB_SEARCH 的排名第 1 矿工声明了 on_chain.request,而发给它的任务也带有
相应参数。另外九个意图,节点外没有人能核查:
130 个已注册矿工中,有 32 个将其 YAML 发布在
http://127.0.0.1:8099/,因此任何人都无法审计它们的链上能力,包括它们的作者。
各意图所属的类别会随读取而变化,所以应查看这些计数,不要引用它们。
本节已经错了三次,每次错的方向都相同,原因也都是工具发布的信息少于正文所声称的。
它曾说十五个中有十四个,把“无法访问 YAML”算作“无法接收任务”。
拆分后,它又说每个可查到的领头矿工都已覆盖——但工具从未计算过这一点,因为它
只统计了已覆盖的情况。该句下方的数字,则是把 /api/miners 中出现的最低排名
当作排名第 1:该响应会随读取变化,因此领头矿工缺席时,排名第 2 的矿工会被悄然
抬上来;有一次读取甚至因此把 STORM_ALERT 从已覆盖翻成未覆盖。现在,工具要求
必须有实际的排名第 1 矿工,报告尚未覆盖的领头矿工,并列出它未找到领头矿工的意图,
而不再猜测。
它能查到的覆盖缺口,源于排名机制:排名是在链下轨道上取得的,服务十五种意图的
通才表现出色,而同一排名随后又会把链上任务路由给无法处理其中一种意图的矿工。
解决方法是加一道筛选——只在声明了 on_chain.request 的矿工中路由链上任务;
根据公开的 YAML,这个集合本来就可以计算出来。相反,合约收到证书错误后拒绝了请求
——而且拒绝得正确,因为不执行自己未要求提供的情报,是参数化保险绝不能做的事:
Declined(policyId, "unreadable answer shape")。这两个任务展示的正是这一特性,它的价值胜过一笔赔付。
参数一直都在传递,只是发到了用不上它们的端点:
/ssl-check 需要主机名,却收到了纬度,因此明确说明了这一点。从外部看来,这恰好
像 docs/bug-report.md 前几节所追查的映射故障。最终是通过
手动解码回调 calldata 才找到问题——ERC-8183 流量不会出现在
daemon/api/questions 中,因此链上轨道在网络唯一的公开信息流里不可见。
80 多次付费 Engine 调用。
合约自身的轨道经受住了考验。 针对最终未返回结果的任务,策略 1 和策略 2 已经
开启。25 小时后,npm run expire 对两者调用了 expire()——分别在
0xebaacad3…
和
0x073e9155…
释放——随后 sweep() 将 2 USDC 浮动资金返还给承保方,交易记录见
0x9876f5b7…。
编写超时机制所针对的故障在 8 月 26 日之前从未发生;它发生后,资金也没有被扣押。
相比之下,Diamond 的托管资金仍然没有退出途径。
**9 月 6 日,轨道的另一半也首次运行。**台风 KROVANH-26 位于那霸以东 95 km 处时,策略
3 在那霸开放,作业 35 返回了结果——它并非来自天气数据挖掘器,而是来自一个区块浏览器,它询问的是交易哈希。此前的每个作业都一直处于 Funded 状态,直到过期,因此在此之前,合约从未真正读取过已交付的答案。它读取了这次的答案,发现其中没有可解析的风险字段,于是发出了
Declined(3, "unreadable answer shape")
且未付款。该策略仍保持开放,以便之后再检查。
一小时后,在合约自身的重试窗口允许的最早时间,作业 36 再次发起请求,返回的结果逐字逐字符都相同。两个作业,间隔一小时,一个答案:对于这个意图,链上轨道不会绕开它。
这就是一笔交易所体现的设计:**合约不会猜测。**如果支付了款项,那就等于根据区块浏览器拒绝回答这一结果结算了风暴索赔。其背后的路由故障记录在一个链上作业找到了正确的挖掘器,却连到了错误的端点。
问题同样也是结果的一部分。链上轨道结算了五个作业后便停止了;从外部看,作业记录只显示 Funded,从不说明原因——除非你亲自解码回调,而这正是为什么到第十六个作业才发现上述错误路由,而不是第七个。
页面、演示文稿和影片
这个网站呈现的是夜海上的一张博福特风级图:左侧是风险等级及各等级对应的情况,航线标注在图上,页面上唯一的红色用于标示 0.75 以上的等级。其视觉系统记录在 DESIGN.md 中,所服务的产品原则记录在 PRODUCT.md 中;设计方向通过 Impeccable 的 roll(种子 5db15dc1)选定,页面通过其检测器检查,未发现问题,只有一项数值被有意豁免——指针超调的缓动效果及其原因记录在 .impeccable/config.json 中。
/slides 用九张幻灯片呈现同一个世界,适合只有三分钟而非三十分钟的评审。没有框架:九个章节、滚动吸附、方向键,以及一条可将其转换为讲义的打印规则。一项测试会逐字节检查这三个页面是否使用完全相同的样式块。
media/amanat-demo.mp4 时长 84 秒,由五段针对在线挖掘器的真实会话剪辑而成——读取、路由、链上账本、审计和演示文稿。没有任何模拟内容,也没有调整时间:
node scripts/record-demo.mjs # drives a real browser, one clip per scene
node scripts/probe-clips.mjs # measures what it recorded
npx remotion render video/index.jsx demo media/amanat-demo.mp4 --public-dir=media/raw
时长是从文件中读取的,而不是手动记录的,因为片段有多长,取决于页面用了多久才作答,而我们无法选择这一点。只有影片会纳入版本控制;重新录制后,得到的就是今天的数字,这正是重点。
复现其中任何内容
npm install
cp .env.example .env # fill in a funded Base Sepolia key
npm run miner # the miner, locally
npm test # miner self-check + 34 scorer tests
npm run build:profiles # ten binaries, fails if any two match
npm run bench && npm run attacks # champions are gitignored — fetch them first, see Track 2
npx -y amanat-mcp # the MCP server from npm: four tools, no wallet
npm run agent:dry # the loop, read-only, spends nothing
npm run survey # the scoring board: measured bar vs displayed score, free
npm run audit # which intents an on-chain job cannot survive, free
npm run impact # what changed on any intent our module scores, free
npm run expire # release policies the network never answered, gas only
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。