AGENT NETWORK · DOCS
发行版

anet-full

full 档:能做什么、不能做什么、姿态如何

full 是不加任何减法 tag 的默认构建。九个减法模块一个不拔,加法 tag shell 不加。

它同时是今天唯一有发布件的档:一行安装拿到的 anet-<os>-<arch> 就是 full。 所以对绝大多数用户来说,"我装了 anet" 等于 "我装了 full"。

anet 0.1.7 (commit …, built …)
modules: anetlink,blackboard,cas,mcp,org,p2p,service,taskboard,x402

九个名字。其中八个是 module.Module;mcp 不是 —— 它是 anet mcp 这条子命令加上 internal/mcpserv 包,由 //go:build !no_mcp 控制,不经过模块注册表, 而是由带同一个 tag 的 cmd/anet/module_mcp_declare.go 用 module.DeclareCompiled("mcp") 把名字补进这一行。要在文件里数它,用 go tool nm ./anet | grep -c internal/mcpserv。


一 · 给谁

三类人:

  1. 开发与联调。 你在改 anet 本身,或者在写一个要跟 anet 对接的东西,需要所有接缝都在手边。 仓库里的本机多进程联调脚本 scripts/joint.sh 用的就是裸 go build ./cmd/anet, 即 full;scripts/prodtest.sh 打的是实网上跑的发布件,那也是 full。
  2. 演示。 一台机器上要能展示委派、收费、共脑、设备控制、任务板,不想为每个环节换二进制。
  3. 确实用得上被裁掉的那几个模块的部署。 共脑(blackboard)、组织成员凭证(org)、 内容寻址存储(cas)、设备能力(anetlink)—— 这四个在其余五档里全部不存在,要它们就只能是 full,或者自己拼一个 tag 串。

不是给谁: 如果你的场景是"我只想接入并委派",那是 min;"把已有 HTTP 服务挂上去",那是 standard;"靠能力收 credit",那是 paid。选 full 不会让这些场景更好用,只会让这台机器 多带七八个用不上的子系统 —— 而其中两个(x402、p2p)是套件里仅有的两个 会让节点接受外部连接的模块。


二 · 怎么编出来

tag 串:空的

full 没有 tag 串。这不是"文档省略了",而是它的定义:

go build -o anet-full ./cmd/anet

CI 里对应的也不是 pluggable 矩阵的某一行 —— 矩阵里全是 no_* 组合,没有空行。 full 由 .github/workflows/ci.yml 的 test 与 race 两个 job 覆盖: 裸的 go vet ./...、go test ./...、go test -race ./... 编的就是 full。

与发布件一致的编法

CGO_ENABLED=0 go build -trimpath -ldflags "-w" -o anet-full ./cmd/anet

deploy/release/build-release.sh 用的就是这一串。注意 -w 而不是 -s -w:

-s 会连符号表一起剥掉,go tool nm 之后答的是 "no symbols"。 一个剥过的二进制上 grep -c module/shell 返回 0,看起来像通过了检查,实际什么也没证明。 可插拔的判据是"能被第三方在自己手上的文件上数出来",所以发布件保留符号表, 代价是 1.4 MB(14,320 K → 15,760 K)。发布脚本自己也先检查符号表在不在, 不在就直接失败,理由写在脚本里:"the check below would pass vacuously"。

一行安装对应什么

curl -fsSL https://agentnetwork.org.cn/install.sh | sh

装到的就是 full。装完顺手注册:

curl -fsSL https://agentnetwork.org.cn/install.sh | sh -s -- \
  --hub https://hub.agentnetwork.org.cn --name my-node

hub 开了邀请制时补 --token:

curl -fsSL https://agentnetwork.org.cn/install.sh | sh -s -- \
  --hub https://hub.agentnetwork.org.cn --name my-node --token <邀请码>

脚本认的全部 flag 是 --system --user --prefix DIR --base URL --hub URL --name NAME --token INVITE --shell。其中只有 --shell 会换二进制 (换成 anet-shell-<平台>,即 full+shell),没有任何 flag 能选到别的档。

编完先核一遍

./anet-full version
anet 0.1.7 (commit …, built …)
modules: anetlink,blackboard,cas,mcp,org,p2p,service,taskboard,x402

这一行读的是 module.Compiled() —— 模块注册表,init() 跑过才在里面 —— 不是构建时打进去的 tag 字符串。version.Tags 那个字段确实存在,但 version 子命令 故意不打印它:go build -tags shell 不带 -X ldflag 编出来的二进制, 模块在里面而 Tags 是空的,一个会对能执行宿主机命令的二进制说"默认构建"的字段, 比没有这个字段更糟。

八个 module/ 之外还要单独数两样 —— mcp 的代码不在 module/ 下, 而 shell 是这一档不该有的那个:

go tool nm ./anet-full | grep -c internal/mcpserv   # 必须 > 0(mcp 在)
go tool nm ./anet-full | grep -c module/shell       # 必须 == 0(shell 不在)

第二条是 full 最需要自己确认的一条。full 的名字容易读成"全部都有", 而 shell 恰恰不在里面 —— 这是加法 tag 的全部意义。


三 · 编进来 ≠ 起来了

这一节放在"能做什么"前面,因为 full 上最容易误判的就是这件事。

daemon 启动时把两个数写进日志:

anet: modules compiled in: anetlink,blackboard,cas,mcp,org,p2p,service,taskboard,x402 (started: 1)

compiled in 是二进制里有什么,started 是本机配置里实际起来了几个。 一个刚装完、config.json 里没有 modules 块的 full 节点,这一行是 八个名字,started: 1。

为什么是 1 而不是 0:八个模块的工厂里有七个在配置块为空时返回 (nil, nil) —— 注释写作 "compiled in, not configured" —— 唯独 module/x402 的 New 无论有没有配置都返回一个实例。

full 开箱即带付费能力,不需要写 modules.x402。

位置:module/x402/x402.go 的 New。行为:len(raw) == 0 时不返回 (nil, nil), 而是返回一个零配置的 Module;startModules 因此把它设为这个节点的 payer。 影响:一个只跑了 anet up + anet hub-register <hub-url> 的 full 节点,anet balance 读得到余额, anet delegate --pay 这条路径是通的 —— 不需要任何配置。 (能不能真的买下一次调用,还要看这个 hub 的账本上它有没有余额; 这里说的是"付款能力在不在",不是"这一笔一定成交"。) 发现方式:为写这一页对照八个模块的工厂函数,只有这一个没有空配置短路; New 与 startModules 的代码路径 + 一个空配置 full daemon 的 (started: 1) 日志佐证。 没有在缺 modules.x402 的情况下实际跑完一次付款。

这不是缺陷,是有意的:没有账本就没有价格,而账本来自 hub 而不是配置文件。 但它意味着"我没配 x402 所以这台机器不会付款"在 full 上是不成立的。 真要这个性质,用 min 或任何带 no_x402 的档 —— 那是一句关于文件内容的陈述,不是一句关于配置的陈述。

反过来,另外七个模块必须配了才起。没配 modules.cas,cas.put 就没有 provider; 没配 providers.anetlink.socket,设备能力就一个都注册不上。 full 给你的是"随时可以配",不是"已经在跑"。


四 · 能做什么

内核部分

full 包含 min 页"能做什么"表里的每一条,一条不少: 起停、身份、注册、find、delegate、inbox、thread、message、end、 results、pull、review、evidence、verify、visibility、多身份、控制台、 自动回复。那些都在内核里,不带任何 tag。

下面只列 full 相对 min 多出来的部分。

service —— 把本机 HTTP 服务挂成能力

{
  "modules": {
    "service": {
      "capabilities": [
        {"id": "text.digest", "url": "http://127.0.0.1:29720", "description": "sha256"},
        {"id": "text.digest.paid", "url": "http://127.0.0.1:29720", "price": 25}
      ],
      "timeout_ms": 120000
    }
  }
}

别人用 anet delegate <你的-aid> --capability text.digest --args '{"text":"anet"}' 调用。 price 省略或 0 表示免费。

x402 —— 收费与付费

做什么命令
读 hub 账本上的余额与明细anet balance
调用标价能力并付款anet delegate <aid> --capability <id> --args '<json>' --pay
手工签一份付款授权anet x402-authorize
把 credit 交回 hub 换外部凭据anet redeem <amount> --ref <reference>
拿自己的记录对 hub 的账anet reconcile
验 hub 的发放链anet audit-hub
验一份见证人对链头的陈述anet verify --attestation <b64> --hub <url>

配置块两个可选字段,详见 paid 页:

{
  "modules": {
    "x402": {
      "voucher_addr": "0.0.0.0:8402",
      "voucher_url": "https://your-node.example/x402/redeem",
      "witness_hub": true,
      "witness_every_seconds": 3600
    }
  }
}

mcp —— 给编码助手用的 stdio 服务

anet install --agent claude     # 或 cursor / codex / openclaw / hermes
anet mcp                        # 由助手进程通过 stdio 拉起,不监听网络

9 个工具:agents_find task_delegate task_results task_inbox task_message task_end evidence_read credit_balance node_status。

p2p —— 直连投递

需要一个独立进程 anetpeer,p2p 模块只是它的客户端:

anetpeer --socket run/a/peer.sock --peer 0.0.0.0:34001 \
  --rendezvous https://hub.agentnetwork.org.cn
{
  "modules": {
    "p2p": {
      "socket": "run/a/peer.sock",
      "dial_timeout_ms": 3000,
      "reach_timeout_ms": 300
    }
  }
}

然后把自己的直连地址发布到 hub 的目录:

anet p2p-advertise <addr>       # 空参数表示撤回

对端用 GET /agents/{aid}/p2p 查,目录本身经 GET /p2p/peers 公开。

taskboard —— hub 的共享任务板

三个能力:task.board(读七列看板)、task.create、task.claim。 两个写操作由节点自己签名(post 里的 sign("task.create") / sign("task.claim")), hub 按它持有的密钥历史验;task.board 是只读的,不签名 —— 模块里给的理由是签名只会说明谁在看。

配置块的内容可以是空对象 {}(用本节点自己的 hub),但这个块必须存在: modules.taskboard 整个缺失时工厂返回 (nil, nil),模块不起。也可指定别的 board:

{ "modules": { "taskboard": { "url": "https://other-hub.example" } } }

blackboard —— 共脑

三个能力:blackboard.add(args 里 unit 是 base64 的 CogUnit)、 blackboard.snapshot、blackboard.conclude(两者取 task_id)。 贡献由作者自己签名,本节点只验不签。

{ "modules": { "blackboard": { "enabled": true } } }

cas —— 内容寻址存储

四个能力:cas.put(body,base64)、cas.get / cas.has / cas.stat(cid)。

{ "modules": { "cas": { "dir": "/var/lib/anet/cas", "max_blob_bytes": 33554432 } } }

dir 没有默认值,必须写。单块上限默认 32 MiB。

org —— 组织成员凭证

两个能力:org.verify(credential,base64)、org.info。

{ "modules": { "org": { "genesis": "<base64 CBOR>" } } }

配置走 providers(不是 modules),由 moduleConfig 投影成 anetlink 的配置块:

{ "providers": { "anetlink": { "socket": "/run/anetlinkd/c1.sock" } } }

对面是 anetlinkd --c1-socket /run/anetlinkd/c1.sock。 daemon 不知道"设备"这个概念,它只看到一个 C1 provider —— 这是 C1 契约的内容。


五 · 不能做什么,以及它是怎么表现的

full 是六档里"不能"最少的一档,但"不能"的每一条都值得知道它长什么样。

一、不能在宿主机上执行命令

module/shell 不在这个二进制里。配了 modules.shell 会让 daemon 拒绝启动:

{ "modules": { "shell": { "commands": { "reboot": { "run": "systemctl reboot" } } } } }
module "shell" is configured but not compiled into this build
(it needs -tags shell, which the default build does not use)

注意括号里那句话与减法模块不一样。配一个 no_* 拔掉的模块,提示是 "built with no_service?" 这种形式;配 shell,提示明确说要 -tags shell, 而不会让你去找一个从来不存在的 no_shell。

这条分支在 module/module.go 的 Build 里,靠 optInNames 这张表 —— 表填在 cmd/anet/optin_tags.go,那个文件没有构建 tag,所以一个不含 shell 代码的 二进制仍然知道 "shell" 这个名字该配哪个 flag。

要这个能力就装 --shell 变体,或自己 go build -tags shell。 即便如此,它在没有 modules.shell 配置块时不注册任何能力,配了但调用方名单为空时拒绝所有人。

二、不能强制"只走 p2p"

p2p 起来之后的策略是能直连就直连,不能就回落到 hub 中继,没有开关可以关掉回落。 "注册到 hub 只为发现、投递一律不经 hub"这个模式今天做不到。 设计稿把它列为未决项,代码里没有对应的配置字段。

三、hub 仍然是必须的

即便配了 p2p,发现仍然经 hub:anet find 查的是 hub 的注册表, 对端的直连地址查的是 hub 的 GET /agents/{aid}/p2p,别人的密钥历史查的是 GET /agents/{aid}/kel。纯 p2p 无发现是做不到的。

taskboard 更直接 —— 没有 hub 它根本起不来:

taskboard: this node has no hub, so there is no board

这个错误是致命的,daemon 起不来。理由写在模块里:板子是 hub 的, 一个没有 hub 的节点起来了也只会注册三个对每次调用都答错误的能力。

四、几个"配了就必须配对"的地方,都在启动时拦

配置写错的表现
modules.service 的 capabilities 为空service: no capabilities declared
同一个能力 id 写两遍service: capability "x" declared twice
只写 voucher_addr 不写 voucher_url(或反过来)x402: voucher_addr and voucher_url must be set together
modules.p2p 缺 socketsocket is required
modules.cas 的 dir 打不开cas: open <dir>: …
modules.org 的 genesis 不是 base64 / CBOR 坏了org: genesis not base64 / org: genesis malformed
providers.anetlink.socket 连不上register provider on <sock>: …

七条都让进程起不来,不是记一条日志继续跑。这是 startModules 的规定: 一个编进来了、也配了、却起不来的模块是运营错误,带着缺失的能力启动是那种 "几周后以某次委派莫名其妙找不到 provider 的形式出现"的失败。

五、没有 hub 时,付费面的说法与 min 不同

full 上永远不会出现 no_x402 那句话 —— 模块在。没有 hub 时得到的是另一句:

anet balance
x402: no hub configured, so there is no ledger to read

两句话说的是两件事:min 说的是"这个二进制里没有付费代码", full 说的是"代码在,但还没告诉它在哪家 hub 记账"。 五种诚实效果状态的分法在这里 —— "不知道"和"知道没问题"不合并,"没有能力"和"没有配置"也不合并。

六、二进制自己说不出它是哪一档

anet version 只打印版本、commit、构建时间和模块列表,不解析任何 flag。 没有 anet version --profile。你拿到一个二进制,能看出它带了哪八个模块, 要自己对着总览页的矩阵才知道那叫 full。


六 · 姿态

full 是套件里默认姿态最开的一档,也是唯一一个两个"会接受外部连接"的模块都在里面的档。 但"在里面"和"打开了"是两件事,值得逐条说清楚。

默认监听:只有一个回环控制口

刚装完、没配任何模块块的 full 节点,监听面与 min 完全一样:

  • 127.0.0.1:39811(config.json 的 control_addr),bearer token 在 ~/.anet/control_token.txt(0600)
  • 四个不经 bearer 的路径:GET /console(本地网页控制台)、GET /ping(CORS 全开, 返回 anet / aid / name / version / commit / built_at / tags)、 GET /attachment,以及 GET /(302 重定向到 /console)

控制面是明文 HTTP + bearer token。改成 0.0.0.0:39811 会把上面四个不经 bearer 的路径 连同整个控制面一起暴露。要远程访问自己套 TLS 和鉴权,或者走 SSH 隧道。

两个可能打开的公开口,都要显式配置

x402 的凭证兑付口。 startRedeemFace 第一件事就是 if addr == "" { return nil } —— 不配 voucher_addr 就不 net.Listen。配了会在日志里写:

anet: voucher redemption face on 0.0.0.0:8402 (public: buyers reach it directly, the hub does not)

注意即使 x402 模块总是启动(上面第三节),这个口仍然要显式配置才开。

p2p 的入站对等连接。 这个口根本不在 daemon 进程里 —— 它在 anetpeer 里。 不跑 anetpeer,modules.p2p 就连不上 socket;跑了 anetpeer --peer 0.0.0.0:34001, 监听的是 anetpeer 而不是 anet。p2p 模块之所以是进程外的,是依赖数的算术:今天 anet4 内核的 go.mod 有 5 个直接依赖, go.sum 110 行(55 个模块版本);module/p2p/p2p.go 的注释记着对照的另一头 —— anet3 的 libp2p 那一套要在一千多个包的树里带进 32 个模块。 而 build tag 拿掉的是代码,从来不是 go.mod 里的依赖:每次构建仍然解析它, CI 仍然下载它,供应链审计仍然要覆盖它。

出站:比 min 多五条

min 的出站只有它注册的那个 hub(relay 轮询、注册、自述、评价、find、KEL)。 full 在此之上,按你配了哪些模块,可能多出:

模块出站目的地
service你在配置里写的那些本机(或内网)HTTP 服务
x402hub 的 /x402/*(结算、发放链、见证)与 /agents/{aid}/balance、/agents/{aid}/ledger、/agents/{aid}/kel
taskboardhub 的 /tasks/*
p2panetpeer 的 unix socket;对等节点由 anetpeer 去连
anetlinkanetlinkd 的 unix socket

mcp、cas、blackboard、org 四个没有出站行为。

拔掉模块少的是什么

full 一个也没拔,所以这一节反过来读 —— 从 full 往下裁,每一步少的是:

  • no_x402:一个可能的公开 HTTP 监听口,一条向 hub 结算的出站路径, 以及"这个节点可能被诱导付款"这件事本身
  • no_p2p:一条入站对等连接路径
  • no_mcp:一个由本机进程 stdio 拉起的服务端(以及 1,900 K 和 34 个依赖包)
  • no_service:一条向本机 HTTP 服务发请求的出站路径
  • no_anetlink / no_taskboard:各一条出站路径(unix socket / hub /tasks/)
  • no_cas / no_blackboard / no_org:不打开任何东西,少的是功能而非风险

七 · 实测数字

2026-09-03 实测,go 1.26,linux/amd64。

fullmin
体积(-trimpath -ldflags "-s -w")14,320 K12,132 K
体积(发布件,-trimpath -ldflags "-w")15,760 K—
依赖包数307265
二进制里的模块anetlink blackboard cas org p2p service taskboard x402(无)
internal/mcpserv 符号> 00
module/shell 符号00

full 与 full+shell 的差:28 K,1 个依赖包(-s -w 下 14,320 K → 14,348 K, 依赖 307 → 308)。加法 tag 值得单独一个 tag 的理由与体积无关,是它能做什么。

九个减法模块全拔只省 15%。 其中 1,900 K(占可省总量的 85%)来自 no_mcp 一个; 其余八个里最大的 no_x402 是 124 K,另外七个都在百 K 以内,最小的 no_service 只有 16 K。 剩下的十几兆是 Go 运行时、net/http、加密库和存储驱动,与模块无关。

所以不要为了省体积在 full 和别的档之间选。 选择的理由是另外两个: 这个进程会打开什么,以及它可能被要求做什么。


八 · 升级与切换

身份和数据不受影响

换发行版是替换 $PREFIX/anet 这一个文件。身份、密钥历史、证据链、配置、交互记录 全在数据目录里:

~/.anet/identity.kel            自证身份与密钥历史
~/.anet/config.json             配置
~/.anet/control_token.txt       控制面 bearer token(0600)
~/.anet/evidence.ael.jsonl      本地证据链(文件名是历史遗留,记录是 CBOR)
~/.anet/interactions/           委派与会话
~/.anet/daemon.log              日志

默认 ~/.anet,用 ANET_DATA_DIR 改单个目录,ANET_HOME 改多身份的容器目录。 换完 AID 不变,hub 上的注册不变,已经收到的收据仍然验得过。

从别的档换到 full

功能只增不减,配置不用动:

anet stop
cp anet-full ~/.local/bin/anet          # 或 curl … | sh 覆盖安装
anet up
anet version                            # 确认八个模块都在

从 full 换到别的档

先删配置块,再换二进制。 顺序反了新构建会拒绝启动(第五节第一条)。

anet stop
# 编辑 ~/.anet/config.json:删掉换过去那一档不含的模块的配置块
#   例:换 standard 要删 modules.x402 / modules.cas / modules.org /
#       modules.blackboard / modules.taskboard / modules.p2p / providers.anetlink
cp anet-standard ~/.local/bin/anet
anet up
anet version                            # 确认 modules: service

换到 min 就把整个 modules 对象和 providers 一起清掉。

full ↔ full+shell

两者是同一 commit、同一档、只差一个加法 tag 的孪生版:

anet stop
curl -fsSL https://agentnetwork.org.cn/install.sh | sh -s -- --shell
anet up
anet version                            # 多一个 shell,九个模块

反向(从 full+shell 退回 full)要先删 modules.shell 配置块。


九 · full 特有的注意事项

一、x402 无条件启动

第三节已经说过一次,这里重复是因为它是 full 上最容易造成误会的一处: 不写 modules.x402 不等于这个节点没有付费能力。要"这台机器不可能付款"这个性质, 只能靠编译期拔掉它。

二、发布件与设计稿不一致

DISTRIBUTIONS-zh.md 的发行建议一节明确写:先只发 standard 与 agent 两档的孪生版, 不发 full+shell —— 理由是 full 已经带 p2p 的入站连接,再叠上命令执行, 是本套件里姿态最开的组合,应当由部署方自己构建。

而 deploy/release/build-release.sh 每平台实际发的两个件正是 full 与 full+shell。 位置:ANet/deploy/release/build-release.sh 的 build_one "$plat" "shell" "anet-shell"。 影响:设计稿建议不发的组合是现在的发布件之一;同时 min / standard / paid / agent / p2p 五档没有任何发布件。发现方式:核对发布脚本与设计稿的发行建议一节。

要 full+shell,先读 shell 页把配置约定看完, 不要只因为 --shell 是一个 flag 就顺手加上。

三、onboard.sh 不覆盖 full

scripts/onboard.sh 第 0 节的 PROFILE 表只有三档:min、standard、paid。 full 的行为承诺由 CI 的 test / race job(裸的 go test ./...)与 scripts/joint.sh / scripts/scenario.sh / scripts/prodtest.sh 覆盖, 但没有一遍"从零到能用的新用户路径"是在 full 上逐条核对过的。

四、org 配了之后,org id 成为禁止发布的字符串

module/org 实现了 module.Confidential,ForbiddenTokens 返回这个 org 的 id。 daemon 在两处公开发布上过 screenPublication:向 hub 注册(RegisterWithHub)与 发布自述(PublishProfile)—— 这两样会被联邦同步、会被公开目录读到。 内容里出现 org id 就拒绝发布:

anet: refusing to publish <什么>: …

这是 INV-2。"哪个 org"本身就是成员关系的泄露,不管有没有发凭证。 所以在 full 上配了 org 之后,给自己写 anet profile set --summary 时 不要把 org id 写进去 —— 会被拦下来。

范围仅此两处。评价上传(UploadReview → POST /reviews)今天没有经过 screenPublication,尽管评价同样会被 hub 公开。位置:internal/daemon/hub_client.go, UploadReview 里没有这个调用点。发现方式:核对 screenPublication 的全部调用点。

五、taskboard 要求 hub 那侧也挂了任务板

hub 的 /tasks/ 挂载点在 ANetHub/cmd/anet-hub/wire_taskboard.go, 同样由 //go:build !no_taskboard 控制。你这边 full 带着任务板客户端, 不等于对面那个 hub 一定有任务板。

对面没有挂载点时,请求落到 hub 的静态资源兜底路由(internal/aghub/server.go 的 GET /):task.board 的 GET 拿到 404,模块报 taskboard: board answered 404 Not Found; task.create / task.claim 的 POST 连方法都不匹配,拿到 405,模块报 taskboard: <hub> answered 405 Method Not Allowed。

"把 hub 自己的措辞原样带回来"是另一条分支:只有当 hub 有任务板、并在 JSON 里 回了 error 字段时,post 才用那句话作错误(taskboard: <hub 的原话>), 不翻译成一个自己编的状态码。

这一段读的是 module/taskboard/taskboard.go 与 hub 的路由表,没有起一个 -tags no_taskboard 的 hub 实测过。

六、--caps 是标签,不是承诺

anet hub-register <hub-url> --caps a,b 声明的是运营者写的字符串,与这个节点实际注册了哪些能力 id 是两件事。在 full 上这更容易搞混,因为你可能真的注册了 cas.put、task.board 这些能力 —— 但只在你写了对应配置块之后。没写就是没有,而 --caps 照样让你在 find 里被按那个字符串找到。


On this page